1 /* Copyright (c) 2012, Bastien Dejean
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice, this
8  *    list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  *    this list of conditions and the following disclaimer in the documentation
11  *    and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 
25 #include <xcb/xcb_keysyms.h>
26 #include <stdlib.h>
27 #include <stdbool.h>
28 #include "bspwm.h"
29 #include "query.h"
30 #include "settings.h"
31 #include "stack.h"
32 #include "tree.h"
33 #include "monitor.h"
34 #include "subscribe.h"
35 #include "events.h"
36 #include "window.h"
37 #include "pointer.h"
38 
39 uint16_t num_lock;
40 uint16_t caps_lock;
41 uint16_t scroll_lock;
42 
43 bool grabbing;
44 node_t *grabbed_node;
45 
pointer_init(void)46 void pointer_init(void)
47 {
48 	num_lock = modfield_from_keysym(XK_Num_Lock);
49 	caps_lock = modfield_from_keysym(XK_Caps_Lock);
50 	scroll_lock = modfield_from_keysym(XK_Scroll_Lock);
51 	if (caps_lock == XCB_NO_SYMBOL) {
52 		caps_lock = XCB_MOD_MASK_LOCK;
53 	}
54 	grabbing = false;
55 	grabbed_node = NULL;
56 }
57 
window_grab_buttons(xcb_window_t win)58 void window_grab_buttons(xcb_window_t win)
59 {
60 	for (unsigned int i = 0; i < LENGTH(BUTTONS); i++) {
61 		if (click_to_focus == (int8_t) XCB_BUTTON_INDEX_ANY || click_to_focus == (int8_t) BUTTONS[i]) {
62 			window_grab_button(win, BUTTONS[i], XCB_NONE);
63 		}
64 		if (pointer_actions[i] != ACTION_NONE) {
65 			window_grab_button(win, BUTTONS[i], pointer_modifier);
66 		}
67 	}
68 }
69 
window_grab_button(xcb_window_t win,uint8_t button,uint16_t modifier)70 void window_grab_button(xcb_window_t win, uint8_t button, uint16_t modifier)
71 {
72 #define GRAB(b, m) \
73 	xcb_grab_button(dpy, false, win, XCB_EVENT_MASK_BUTTON_PRESS, \
74 	                XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, b, m)
75 		GRAB(button, modifier);
76 		if (num_lock != XCB_NO_SYMBOL && caps_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) {
77 			GRAB(button, modifier | num_lock | caps_lock | scroll_lock);
78 		}
79 		if (num_lock != XCB_NO_SYMBOL && caps_lock != XCB_NO_SYMBOL) {
80 			GRAB(button, modifier | num_lock | caps_lock);
81 		}
82 		if (caps_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) {
83 			GRAB(button, modifier | caps_lock | scroll_lock);
84 		}
85 		if (num_lock != XCB_NO_SYMBOL && scroll_lock != XCB_NO_SYMBOL) {
86 			GRAB(button, modifier | num_lock | scroll_lock);
87 		}
88 		if (num_lock != XCB_NO_SYMBOL) {
89 			GRAB(button, modifier | num_lock);
90 		}
91 		if (caps_lock != XCB_NO_SYMBOL) {
92 			GRAB(button, modifier | caps_lock);
93 		}
94 		if (scroll_lock != XCB_NO_SYMBOL) {
95 			GRAB(button, modifier | scroll_lock);
96 		}
97 #undef GRAB
98 }
99 
grab_buttons(void)100 void grab_buttons(void)
101 {
102 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
103 		for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
104 			for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
105 				window_grab_buttons(n->id);
106 				if (n->presel != NULL) {
107 					window_grab_buttons(n->presel->feedback);
108 				}
109 			}
110 		}
111 	}
112 }
113 
ungrab_buttons(void)114 void ungrab_buttons(void)
115 {
116 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
117 		for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
118 			for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
119 				xcb_ungrab_button(dpy, XCB_BUTTON_INDEX_ANY, n->id, XCB_MOD_MASK_ANY);
120 			}
121 		}
122 	}
123 }
124 
modfield_from_keysym(xcb_keysym_t keysym)125 int16_t modfield_from_keysym(xcb_keysym_t keysym)
126 {
127 	uint16_t modfield = 0;
128 	xcb_keycode_t *keycodes = NULL, *mod_keycodes = NULL;
129 	xcb_get_modifier_mapping_reply_t *reply = NULL;
130 	xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(dpy);
131 
132 	if ((keycodes = xcb_key_symbols_get_keycode(symbols, keysym)) == NULL ||
133 	    (reply = xcb_get_modifier_mapping_reply(dpy, xcb_get_modifier_mapping(dpy), NULL)) == NULL ||
134 	    reply->keycodes_per_modifier < 1 ||
135 	    (mod_keycodes = xcb_get_modifier_mapping_keycodes(reply)) == NULL) {
136 		goto end;
137 	}
138 
139 	unsigned int num_mod = xcb_get_modifier_mapping_keycodes_length(reply) / reply->keycodes_per_modifier;
140 	for (unsigned int i = 0; i < num_mod; i++) {
141 		for (unsigned int j = 0; j < reply->keycodes_per_modifier; j++) {
142 			xcb_keycode_t mk = mod_keycodes[i * reply->keycodes_per_modifier + j];
143 			if (mk == XCB_NO_SYMBOL) {
144 				continue;
145 			}
146 			for (xcb_keycode_t *k = keycodes; *k != XCB_NO_SYMBOL; k++) {
147 				if (*k == mk) {
148 					modfield |= (1 << i);
149 				}
150 			}
151 		}
152 	}
153 
154 end:
155 	xcb_key_symbols_free(symbols);
156 	free(keycodes);
157 	free(reply);
158 	return modfield;
159 }
160 
get_handle(node_t * n,xcb_point_t pos,pointer_action_t pac)161 resize_handle_t get_handle(node_t *n, xcb_point_t pos, pointer_action_t pac)
162 {
163 	resize_handle_t rh = HANDLE_BOTTOM_RIGHT;
164 	xcb_rectangle_t rect = get_rectangle(NULL, NULL, n);
165 	if (pac == ACTION_RESIZE_SIDE) {
166 		float W = rect.width;
167 		float H = rect.height;
168 		float ratio = W / H;
169 		float x = pos.x - rect.x;
170 		float y = pos.y - rect.y;
171 		float diag_a = ratio * y;
172 		float diag_b = W - diag_a;
173 		if (x < diag_a) {
174 			if (x < diag_b) {
175 				rh = HANDLE_LEFT;
176 			} else {
177 				rh = HANDLE_BOTTOM;
178 			}
179 		} else {
180 			if (x < diag_b) {
181 				rh = HANDLE_TOP;
182 			} else {
183 				rh = HANDLE_RIGHT;
184 			}
185 		}
186 	} else if (pac == ACTION_RESIZE_CORNER) {
187 		int16_t mid_x = rect.x + (rect.width / 2);
188 		int16_t mid_y = rect.y + (rect.height / 2);
189 		if (pos.x > mid_x) {
190 			if (pos.y > mid_y) {
191 				rh = HANDLE_BOTTOM_RIGHT;
192 			} else {
193 				rh = HANDLE_TOP_RIGHT;
194 			}
195 		} else {
196 			if (pos.y > mid_y) {
197 				rh = HANDLE_BOTTOM_LEFT;
198 			} else {
199 				rh = HANDLE_TOP_LEFT;
200 			}
201 		}
202 	}
203 	return rh;
204 }
205 
grab_pointer(pointer_action_t pac)206 bool grab_pointer(pointer_action_t pac)
207 {
208 	xcb_window_t win = XCB_NONE;
209 	xcb_point_t pos;
210 
211 	query_pointer(&win, &pos);
212 
213 	coordinates_t loc;
214 
215 	if (!locate_window(win, &loc)) {
216 		if (pac == ACTION_FOCUS) {
217 			monitor_t *m = monitor_from_point(pos);
218 			if (m != NULL && m != mon && (win == XCB_NONE || win == m->root)) {
219 				focus_node(m, m->desk, m->desk->focus);
220 				return true;
221 			}
222 		}
223 		return false;
224 	}
225 
226 	if (pac == ACTION_FOCUS) {
227 		if (loc.node != mon->desk->focus) {
228 			focus_node(loc.monitor, loc.desktop, loc.node);
229 			return true;
230 		} else if (focus_follows_pointer) {
231 			stack(loc.desktop, loc.node, true);
232 		}
233 		return false;
234 	}
235 
236 	if (loc.node->client->state == STATE_FULLSCREEN) {
237 		return true;
238 	}
239 
240 	xcb_grab_pointer_reply_t *reply = xcb_grab_pointer_reply(dpy, xcb_grab_pointer(dpy, 0, root, XCB_EVENT_MASK_BUTTON_RELEASE|XCB_EVENT_MASK_BUTTON_MOTION, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_CURRENT_TIME), NULL);
241 
242 	if (reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) {
243 		free(reply);
244 		return true;
245 	}
246 	free(reply);
247 
248 	if (pac == ACTION_MOVE) {
249 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X move begin\n", loc.monitor->id, loc.desktop->id, loc.node->id);
250 	} else if (pac == ACTION_RESIZE_CORNER) {
251 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_corner begin\n", loc.monitor->id, loc.desktop->id, loc.node->id);
252 	} else if (pac == ACTION_RESIZE_SIDE) {
253 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_side begin\n", loc.monitor->id, loc.desktop->id, loc.node->id);
254 	}
255 
256 	track_pointer(loc, pac, pos);
257 
258 	return true;
259 }
260 
track_pointer(coordinates_t loc,pointer_action_t pac,xcb_point_t pos)261 void track_pointer(coordinates_t loc, pointer_action_t pac, xcb_point_t pos)
262 {
263 	node_t *n = loc.node;
264 	resize_handle_t rh = get_handle(loc.node, pos, pac);
265 
266 	uint16_t last_motion_x = pos.x, last_motion_y = pos.y;
267 	xcb_timestamp_t last_motion_time = 0;
268 
269 	xcb_generic_event_t *evt = NULL;
270 
271 	grabbing = true;
272 	grabbed_node = n;
273 
274 	do {
275 		free(evt);
276 		while ((evt = xcb_wait_for_event(dpy)) == NULL) {
277 			xcb_flush(dpy);
278 		}
279 		uint8_t resp_type = XCB_EVENT_RESPONSE_TYPE(evt);
280 		if (resp_type == XCB_MOTION_NOTIFY) {
281 			xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t*) evt;
282 			uint32_t dtime = e->time - last_motion_time;
283 			if (dtime < pointer_motion_interval) {
284 				continue;
285 			}
286 			last_motion_time = e->time;
287 			int16_t dx = e->root_x - last_motion_x;
288 			int16_t dy = e->root_y - last_motion_y;
289 			if (pac == ACTION_MOVE) {
290 				move_client(&loc, dx, dy);
291 			} else {
292 				if (honor_size_hints) {
293 					resize_client(&loc, rh, e->root_x, e->root_y, false);
294 				} else {
295 					resize_client(&loc, rh, dx, dy, true);
296 				}
297 			}
298 			last_motion_x = e->root_x;
299 			last_motion_y = e->root_y;
300 			xcb_flush(dpy);
301 		} else if (resp_type == XCB_BUTTON_RELEASE) {
302 			grabbing = false;
303 		} else {
304 			handle_event(evt);
305 		}
306 	} while (grabbing && grabbed_node != NULL);
307 	free(evt);
308 
309 	xcb_ungrab_pointer(dpy, XCB_CURRENT_TIME);
310 
311 	if (grabbed_node == NULL) {
312 		grabbing = false;
313 		return;
314 	}
315 
316 	if (pac == ACTION_MOVE) {
317 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X move end\n", loc.monitor->id, loc.desktop->id, n->id);
318 	} else if (pac == ACTION_RESIZE_CORNER) {
319 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_corner end\n", loc.monitor->id, loc.desktop->id, n->id);
320 	} else if (pac == ACTION_RESIZE_SIDE) {
321 		put_status(SBSC_MASK_POINTER_ACTION, "pointer_action 0x%08X 0x%08X 0x%08X resize_side end\n", loc.monitor->id, loc.desktop->id, n->id);
322 	}
323 
324 	xcb_rectangle_t r = get_rectangle(NULL, NULL, n);
325 
326 	put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc.monitor->id, loc.desktop->id, loc.node->id, r.width, r.height, r.x, r.y);
327 
328 	if ((pac == ACTION_MOVE && IS_TILED(n->client)) ||
329 	    ((pac == ACTION_RESIZE_CORNER || pac == ACTION_RESIZE_SIDE) &&
330 	     n->client->state == STATE_TILED)) {
331 		for (node_t *f = first_extrema(loc.desktop->root); f != NULL; f = next_leaf(f, loc.desktop->root)) {
332 			if (f == n || f->client == NULL || !IS_TILED(f->client)) {
333 				continue;
334 			}
335 			xcb_rectangle_t r = f->client->tiled_rectangle;
336 			put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc.monitor->id, loc.desktop->id, f->id, r.width, r.height, r.x, r.y);
337 		}
338 	}
339 }
340