aboutsummaryrefslogtreecommitdiff
path: root/sway/scratchpad.c
blob: b7d6fd9988e37edb4099bc8ea75e8f89defd2ccf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include "sway/scratchpad.h"
#include "sway/input/seat.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#include "list.h"
#include "log.h"

void scratchpad_add_container(struct sway_container *con) {
	if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
		return;
	}
	con->scratchpad = true;
	list_add(root_container.sway_root->scratchpad, con);

	struct sway_container *parent = con->parent;
	container_set_floating(con, true);
	container_remove_child(con);
	arrange_windows(parent);

	struct sway_seat *seat = input_manager_current_seat(input_manager);
	seat_set_focus(seat, seat_get_focus_inactive(seat, parent));
}

void scratchpad_remove_container(struct sway_container *con) {
	if (!sway_assert(con->scratchpad, "Container is not in scratchpad")) {
		return;
	}
	con->scratchpad = false;
	for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
		if (root_container.sway_root->scratchpad->items[i] == con) {
			list_del(root_container.sway_root->scratchpad, i);
			break;
		}
	}
}

/**
 * Show a single scratchpad container.
 * The container might be visible on another workspace already.
 */
static void scratchpad_show(struct sway_container *con) {
	struct sway_seat *seat = input_manager_current_seat(input_manager);
	struct sway_container *ws = seat_get_focus(seat);
	if (ws->type != C_WORKSPACE) {
		ws = container_parent(ws, C_WORKSPACE);
	}

    // If the current con or any of its parents are in fullscreen mode, we
    // first need to disable it before showing the scratchpad con.
	if (ws->sway_workspace->fullscreen) {
		container_set_fullscreen(ws->sway_workspace->fullscreen, false);
	}

	// Show the container
	if (con->parent) {
		container_remove_child(con);
	}
	container_add_child(ws->sway_workspace->floating, con);

	// Make sure the container's center point overlaps this workspace
	double center_lx = con->x + con->width / 2;
	double center_ly = con->y + con->height / 2;

	struct wlr_box workspace_box;
	container_get_box(ws, &workspace_box);
	if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) {
		// Maybe resize it
		if (con->width > ws->width || con->height > ws->height) {
			container_init_floating(con);
		}

		// Center it
		double new_lx = ws->x + (ws->width - con->width) / 2;
		double new_ly = ws->y + (ws->height - con->height) / 2;
		container_floating_move_to(con, new_lx, new_ly);
	}

	arrange_windows(ws);
	seat_set_focus(seat, seat_get_focus_inactive(seat, con));

	container_set_dirty(con->parent);
}

/**
 * Hide a single scratchpad container.
 * The container might not be the focused container (eg. when using criteria).
 */
static void scratchpad_hide(struct sway_container *con) {
	struct sway_seat *seat = input_manager_current_seat(input_manager);
	struct sway_container *focus = seat_get_focus(seat);
	struct sway_container *ws = container_parent(con, C_WORKSPACE);

	container_remove_child(con);
	arrange_windows(ws);
	if (con == focus) {
		seat_set_focus(seat, seat_get_focus_inactive(seat, ws));
	}
	list_move_to_end(root_container.sway_root->scratchpad, con);
}

void scratchpad_toggle_auto(void) {
	struct sway_seat *seat = input_manager_current_seat(input_manager);
	struct sway_container *focus = seat_get_focus(seat);
	struct sway_container *ws = focus->type == C_WORKSPACE ?
		focus : container_parent(focus, C_WORKSPACE);

	// If the focus is in a floating split container,
	// operate on the split container instead of the child.
	if (container_is_floating_or_child(focus)) {
		while (focus->parent->layout != L_FLOATING) {
			focus = focus->parent;
		}
	}


    // Check if the currently focused window is a scratchpad window and should
    // be hidden again.
	if (focus->scratchpad) {
		wlr_log(WLR_DEBUG, "Focus is a scratchpad window - hiding %s",
				focus->name);
		scratchpad_hide(focus);
		return;
	}

    // Check if there is an unfocused scratchpad window on the current workspace
    // and focus it.
	for (int i = 0; i < ws->sway_workspace->floating->children->length; ++i) {
		struct sway_container *floater =
			ws->sway_workspace->floating->children->items[i];
		if (floater->scratchpad && focus != floater) {
			wlr_log(WLR_DEBUG,
					"Focusing other scratchpad window (%s) in this workspace",
					floater->name);
			scratchpad_show(floater);
			return;
		}
	}

    // Check if there is a visible scratchpad window on another workspace.
    // In this case we move it to the current workspace.
	for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
		struct sway_container *con =
			root_container.sway_root->scratchpad->items[i];
		if (con->parent) {
			wlr_log(WLR_DEBUG,
					"Moving a visible scratchpad window (%s) to this workspace",
					con->name);
			scratchpad_show(con);
			return;
		}
	}

	// Take the container at the bottom of the scratchpad list
	if (!sway_assert(root_container.sway_root->scratchpad->length,
				"Scratchpad is empty")) {
		return;
	}
	struct sway_container *con = root_container.sway_root->scratchpad->items[0];
	wlr_log(WLR_DEBUG, "Showing %s from list", con->name);
	scratchpad_show(con);
}

void scratchpad_toggle_container(struct sway_container *con) {
	if (!sway_assert(con->scratchpad, "Container isn't in the scratchpad")) {
		return;
	}

    // Check if it matches a currently visible scratchpad window and hide it.
	if (con->parent) {
		scratchpad_hide(con);
		return;
	}

	scratchpad_show(con);
}