]> git.lizzy.rs Git - bspwm.git/blob - src/events.c
Handle mapping notify events
[bspwm.git] / src / events.c
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 <stdbool.h>
26 #include "bspwm.h"
27 #include "ewmh.h"
28 #include "monitor.h"
29 #include "query.h"
30 #include "settings.h"
31 #include "subscribe.h"
32 #include "tree.h"
33 #include "window.h"
34 #include "pointer.h"
35 #include "events.h"
36
37 void handle_event(xcb_generic_event_t *evt)
38 {
39         uint8_t resp_type = XCB_EVENT_RESPONSE_TYPE(evt);
40         switch (resp_type) {
41                 case XCB_MAP_REQUEST:
42                         map_request(evt);
43                         break;
44                 case XCB_DESTROY_NOTIFY:
45                         destroy_notify(evt);
46                         break;
47                 case XCB_UNMAP_NOTIFY:
48                         unmap_notify(evt);
49                         break;
50                 case XCB_CLIENT_MESSAGE:
51                         client_message(evt);
52                         break;
53                 case XCB_CONFIGURE_REQUEST:
54                         configure_request(evt);
55                         break;
56                 case XCB_CONFIGURE_NOTIFY:
57                         configure_notify(evt);
58                         break;
59                 case XCB_PROPERTY_NOTIFY:
60                         property_notify(evt);
61                         break;
62                 case XCB_ENTER_NOTIFY:
63                         enter_notify(evt);
64                         break;
65                 case XCB_MOTION_NOTIFY:
66                         motion_notify(evt);
67                         break;
68                 case XCB_BUTTON_PRESS:
69                         button_press(evt);
70                         break;
71                 case XCB_FOCUS_IN:
72                         focus_in(evt);
73                         break;
74                 case XCB_MAPPING_NOTIFY:
75                         mapping_notify(evt);
76                         break;
77                 case 0:
78                         process_error(evt);
79                         break;
80                 default:
81                         if (randr && resp_type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
82                                 update_monitors();
83                         }
84                         break;
85         }
86 }
87
88 void map_request(xcb_generic_event_t *evt)
89 {
90         xcb_map_request_event_t *e = (xcb_map_request_event_t *) evt;
91
92         schedule_window(e->window);
93 }
94
95 void configure_request(xcb_generic_event_t *evt)
96 {
97         xcb_configure_request_event_t *e = (xcb_configure_request_event_t *) evt;
98
99         coordinates_t loc;
100         bool is_managed = locate_window(e->window, &loc);
101         client_t *c = (is_managed ? loc.node->client : NULL);
102         uint16_t width, height;
103
104         if (!is_managed) {
105                 uint16_t mask = 0;
106                 uint32_t values[7];
107                 unsigned short i = 0;
108
109                 if (e->value_mask & XCB_CONFIG_WINDOW_X) {
110                         mask |= XCB_CONFIG_WINDOW_X;
111                         values[i++] = e->x;
112                 }
113
114                 if (e->value_mask & XCB_CONFIG_WINDOW_Y) {
115                         mask |= XCB_CONFIG_WINDOW_Y;
116                         values[i++] = e->y;
117                 }
118
119                 if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
120                         mask |= XCB_CONFIG_WINDOW_WIDTH;
121                         values[i++] = e->width;
122                 }
123
124                 if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
125                         mask |= XCB_CONFIG_WINDOW_HEIGHT;
126                         values[i++] = e->height;
127                 }
128
129                 if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
130                         mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
131                         values[i++] = e->border_width;
132                 }
133
134                 if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) {
135                         mask |= XCB_CONFIG_WINDOW_SIBLING;
136                         values[i++] = e->sibling;
137                 }
138
139                 if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
140                         mask |= XCB_CONFIG_WINDOW_STACK_MODE;
141                         values[i++] = e->stack_mode;
142                 }
143
144                 xcb_configure_window(dpy, e->window, mask, values);
145
146         } else if (IS_FLOATING(c)) {
147                 width = c->floating_rectangle.width;
148                 height = c->floating_rectangle.height;
149
150                 if (e->value_mask & XCB_CONFIG_WINDOW_X) {
151                         c->floating_rectangle.x = e->x;
152                 }
153
154                 if (e->value_mask & XCB_CONFIG_WINDOW_Y) {
155                         c->floating_rectangle.y = e->y;
156                 }
157
158                 if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
159                         width = e->width;
160                 }
161
162                 if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
163                         height = e->height;
164                 }
165
166                 apply_size_hints(c, &width, &height);
167                 c->floating_rectangle.width = width;
168                 c->floating_rectangle.height = height;
169                 xcb_rectangle_t r = c->floating_rectangle;
170
171                 window_move_resize(e->window, r.x, r.y, r.width, r.height);
172
173                 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, e->window, r.width, r.height, r.x, r.y);
174
175                 monitor_t *m = monitor_from_client(c);
176                 if (m != loc.monitor) {
177                         transfer_node(loc.monitor, loc.desktop, loc.node, m, m->desk, m->desk->focus, false);
178                 }
179         } else {
180                 if (c->state == STATE_PSEUDO_TILED) {
181                         width = c->floating_rectangle.width;
182                         height = c->floating_rectangle.height;
183                         if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
184                                 width = e->width;
185                         }
186                         if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
187                                 height = e->height;
188                         }
189                         apply_size_hints(c, &width, &height);
190                         if (width != c->floating_rectangle.width || height != c->floating_rectangle.height) {
191                                 c->floating_rectangle.width = width;
192                                 c->floating_rectangle.height = height;
193                                 arrange(loc.monitor, loc.desktop);
194                         }
195                 }
196
197
198                 xcb_configure_notify_event_t evt;
199                 unsigned int bw = c->border_width;
200
201                 xcb_rectangle_t r = IS_FULLSCREEN(c) ? loc.monitor->rectangle : c->tiled_rectangle;
202
203                 evt.response_type = XCB_CONFIGURE_NOTIFY;
204                 evt.event = e->window;
205                 evt.window = e->window;
206                 evt.above_sibling = XCB_NONE;
207                 evt.x = r.x;
208                 evt.y = r.y;
209                 evt.width = r.width;
210                 evt.height = r.height;
211                 evt.border_width = bw;
212                 evt.override_redirect = false;
213
214                 xcb_send_event(dpy, false, e->window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (const char *) &evt);
215         }
216 }
217
218 void configure_notify(xcb_generic_event_t *evt)
219 {
220         xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *) evt;
221
222         if (e->window == root) {
223                 screen_width = e->width;
224                 screen_height = e->height;
225         }
226 }
227
228 void destroy_notify(xcb_generic_event_t *evt)
229 {
230         xcb_destroy_notify_event_t *e = (xcb_destroy_notify_event_t *) evt;
231
232         unmanage_window(e->window);
233 }
234
235 void unmap_notify(xcb_generic_event_t *evt)
236 {
237         xcb_unmap_notify_event_t *e = (xcb_unmap_notify_event_t *) evt;
238
239         if (e->window == motion_recorder.id) {
240                 /* Unmapping the motion recorder in `query_pointer` will produce
241                  * unwanted enter notify events. We can filter those events because
242                  * their sequence number is the same as the sequence number of the
243                  * related unmap notify event. This is a technique used by i3-wm to
244                  * filter enter notify events. */
245                 motion_recorder.sequence = e->sequence;
246                 return;
247         }
248
249         /* Filter out destroyed windows */
250         if (!window_exists(e->window)) {
251                 return;
252         }
253
254         set_window_state(e->window, XCB_ICCCM_WM_STATE_WITHDRAWN);
255         unmanage_window(e->window);
256 }
257
258 void property_notify(xcb_generic_event_t *evt)
259 {
260         xcb_property_notify_event_t *e = (xcb_property_notify_event_t *) evt;
261
262         if (e->atom == ewmh->_NET_WM_STRUT_PARTIAL && ewmh_handle_struts(e->window)) {
263                 for (monitor_t *m = mon_head; m != NULL; m = m->next) {
264                         for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
265                                 arrange(m, d);
266                         }
267                 }
268         }
269
270         if (e->atom != XCB_ATOM_WM_HINTS && e->atom != XCB_ATOM_WM_NORMAL_HINTS) {
271                 return;
272         }
273
274         coordinates_t loc;
275         if (!locate_window(e->window, &loc)) {
276                         return;
277         }
278
279         if (e->atom == XCB_ATOM_WM_HINTS) {
280                 xcb_icccm_wm_hints_t hints;
281                 if (xcb_icccm_get_wm_hints_reply(dpy, xcb_icccm_get_wm_hints(dpy, e->window), &hints, NULL) == 1 &&
282                     (hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY))
283                         set_urgent(loc.monitor, loc.desktop, loc.node, xcb_icccm_wm_hints_get_urgency(&hints));
284         } else if (e->atom == XCB_ATOM_WM_NORMAL_HINTS) {
285                 client_t *c = loc.node->client;
286                 if (xcb_icccm_get_wm_normal_hints_reply(dpy, xcb_icccm_get_wm_normal_hints(dpy, e->window), &c->size_hints, NULL) == 1) {
287                         arrange(loc.monitor, loc.desktop);
288                 }
289         }
290 }
291
292 void client_message(xcb_generic_event_t *evt)
293 {
294         xcb_client_message_event_t *e = (xcb_client_message_event_t *) evt;
295
296         if (e->type == ewmh->_NET_CURRENT_DESKTOP) {
297                 coordinates_t loc;
298                 if (ewmh_locate_desktop(e->data.data32[0], &loc)) {
299                         focus_node(loc.monitor, loc.desktop, loc.desktop->focus);
300                 }
301                 return;
302         }
303
304         coordinates_t loc;
305         if (!locate_window(e->window, &loc)) {
306                 return;
307         }
308
309         if (e->type == ewmh->_NET_WM_STATE) {
310                 handle_state(loc.monitor, loc.desktop, loc.node, e->data.data32[1], e->data.data32[0]);
311                 handle_state(loc.monitor, loc.desktop, loc.node, e->data.data32[2], e->data.data32[0]);
312         } else if (e->type == ewmh->_NET_ACTIVE_WINDOW) {
313                 if ((ignore_ewmh_focus && e->data.data32[0] == XCB_EWMH_CLIENT_SOURCE_TYPE_NORMAL) ||
314                     loc.node == mon->desk->focus) {
315                         return;
316                 }
317                 focus_node(loc.monitor, loc.desktop, loc.node);
318         } else if (e->type == ewmh->_NET_WM_DESKTOP) {
319                 coordinates_t dloc;
320                 if (ewmh_locate_desktop(e->data.data32[0], &dloc)) {
321                         transfer_node(loc.monitor, loc.desktop, loc.node, dloc.monitor, dloc.desktop, dloc.desktop->focus, false);
322                 }
323         } else if (e->type == ewmh->_NET_CLOSE_WINDOW) {
324                 close_node(loc.node);
325         }
326 }
327
328 void focus_in(xcb_generic_event_t *evt)
329 {
330         xcb_focus_in_event_t *e = (xcb_focus_in_event_t *) evt;
331
332         if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB
333             || e->detail == XCB_NOTIFY_DETAIL_POINTER || e->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT
334             || e->detail == XCB_NOTIFY_DETAIL_NONE) {
335                 return;
336         }
337
338         if (mon->desk->focus != NULL && e->event == mon->desk->focus->id) {
339                 return;
340         }
341
342         coordinates_t loc;
343         if (locate_window(e->event, &loc)) {
344                 // prevent input focus stealing
345                 update_input_focus();
346         }
347 }
348
349 void button_press(xcb_generic_event_t *evt)
350 {
351         xcb_button_press_event_t *e = (xcb_button_press_event_t *) evt;
352         bool replay = false;
353         for (unsigned int i = 0; i < LENGTH(BUTTONS); i++) {
354                 if (e->detail != BUTTONS[i]) {
355                         continue;
356                 }
357                 if ((click_to_focus == (int8_t) XCB_BUTTON_INDEX_ANY || click_to_focus == (int8_t) BUTTONS[i]) &&
358                         cleaned_mask(e->state) == XCB_NONE) {
359                         bool pff = pointer_follows_focus;
360                         bool pfm = pointer_follows_monitor;
361                         pointer_follows_focus = false;
362                         pointer_follows_monitor = false;
363                         replay = !grab_pointer(ACTION_FOCUS) || !swallow_first_click;
364                         pointer_follows_focus = pff;
365                         pointer_follows_monitor = pfm;
366                 } else {
367                         grab_pointer(pointer_actions[i]);
368                 }
369         }
370         xcb_allow_events(dpy, replay ? XCB_ALLOW_REPLAY_POINTER : XCB_ALLOW_SYNC_POINTER, e->time);
371         xcb_flush(dpy);
372 }
373
374 void enter_notify(xcb_generic_event_t *evt)
375 {
376         xcb_enter_notify_event_t *e = (xcb_enter_notify_event_t *) evt;
377         xcb_window_t win = e->event;
378
379         if (e->mode != XCB_NOTIFY_MODE_NORMAL || e->detail == XCB_NOTIFY_DETAIL_INFERIOR) {
380                 return;
381         }
382
383         /* Ignore the enter notify events that we generated by unmapping the motion
384          * recorder window in `query_pointer`. */
385         if (motion_recorder.enabled && motion_recorder.sequence == e->sequence) {
386                 return;
387         }
388
389         if (win == mon->root || (mon->desk->focus != NULL &&
390                                  (win == mon->desk->focus->id ||
391                                   (mon->desk->focus->presel != NULL &&
392                                    win == mon->desk->focus->presel->feedback)))) {
393                 return;
394         }
395
396         update_motion_recorder();
397 }
398
399 void motion_notify(xcb_generic_event_t *evt)
400 {
401         xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *) evt;
402
403         static uint16_t last_motion_x = 0, last_motion_y = 0;
404         static xcb_timestamp_t last_motion_time = 0;
405
406         int64_t dtime = e->time - last_motion_time;
407
408         /* Ignore unintentional pointer motions. */
409         if (dtime > 1000) {
410                 last_motion_time = e->time;
411                 last_motion_x = e->event_x;
412                 last_motion_y = e->event_y;
413                 return;
414         }
415         int mdist = abs(e->event_x - last_motion_x) + abs(e->event_y - last_motion_y);
416         if (mdist < 10) {
417                 return;
418         }
419
420         disable_motion_recorder();
421
422         xcb_window_t win = XCB_NONE;
423         query_pointer(&win, NULL);
424         coordinates_t loc;
425         bool pff = pointer_follows_focus;
426         bool pfm = pointer_follows_monitor;
427         pointer_follows_focus = false;
428         pointer_follows_monitor = false;
429         auto_raise = false;
430
431         if (locate_window(win, &loc)) {
432                 if (loc.monitor->desk == loc.desktop && loc.node != mon->desk->focus) {
433                         focus_node(loc.monitor, loc.desktop, loc.node);
434                 }
435         } else {
436                 xcb_point_t pt = {e->root_x, e->root_y};
437                 monitor_t *m = monitor_from_point(pt);
438                 if (m != NULL && m != mon) {
439                         focus_node(m, m->desk, m->desk->focus);
440                 }
441         }
442
443         pointer_follows_focus = pff;
444         pointer_follows_monitor = pfm;
445         auto_raise = true;
446 }
447
448 void handle_state(monitor_t *m, desktop_t *d, node_t *n, xcb_atom_t state, unsigned int action)
449 {
450         if (state == ewmh->_NET_WM_STATE_FULLSCREEN) {
451                 if (action == XCB_EWMH_WM_STATE_ADD) {
452                         set_state(m, d, n, STATE_FULLSCREEN);
453                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
454                         if (n->client->state == STATE_FULLSCREEN) {
455                                 set_state(m, d, n, n->client->last_state);
456                         }
457                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
458                         set_state(m, d, n, IS_FULLSCREEN(n->client) ? n->client->last_state : STATE_FULLSCREEN);
459                 }
460                 arrange(m, d);
461         } else if (state == ewmh->_NET_WM_STATE_BELOW) {
462                 if (action == XCB_EWMH_WM_STATE_ADD) {
463                         set_layer(m, d, n, LAYER_BELOW);
464                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
465                         if (n->client->layer == LAYER_BELOW) {
466                                 set_layer(m, d, n, n->client->last_layer);
467                         }
468                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
469                         set_layer(m, d, n, n->client->layer == LAYER_BELOW ? n->client->last_layer : LAYER_BELOW);
470                 }
471         } else if (state == ewmh->_NET_WM_STATE_ABOVE) {
472                 if (action == XCB_EWMH_WM_STATE_ADD) {
473                         set_layer(m, d, n, LAYER_ABOVE);
474                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
475                         if (n->client->layer == LAYER_ABOVE) {
476                                 set_layer(m, d, n, n->client->last_layer);
477                         }
478                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
479                         set_layer(m, d, n, n->client->layer == LAYER_ABOVE ? n->client->last_layer : LAYER_ABOVE);
480                 }
481         } else if (state == ewmh->_NET_WM_STATE_HIDDEN) {
482                 if (action == XCB_EWMH_WM_STATE_ADD) {
483                         set_hidden(m, d, n, true);
484                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
485                         set_hidden(m, d, n, false);
486                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
487                         set_hidden(m, d, n, !n->hidden);
488                 }
489         } else if (state == ewmh->_NET_WM_STATE_STICKY) {
490                 if (action == XCB_EWMH_WM_STATE_ADD) {
491                         set_sticky(m, d, n, true);
492                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
493                         set_sticky(m, d, n, false);
494                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
495                         set_sticky(m, d, n, !n->sticky);
496                 }
497         } else if (state == ewmh->_NET_WM_STATE_DEMANDS_ATTENTION) {
498                 if (action == XCB_EWMH_WM_STATE_ADD) {
499                         set_urgent(m, d, n, true);
500                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) {
501                         set_urgent(m, d, n, false);
502                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) {
503                         set_urgent(m, d, n, !n->client->urgent);
504                 }
505 #define HANDLE_WM_STATE(s)  \
506         } else if (state == ewmh->_NET_WM_STATE_##s) { \
507                 if (action == XCB_EWMH_WM_STATE_ADD) { \
508                         n->client->wm_flags |= WM_FLAG_##s; \
509                 } else if (action == XCB_EWMH_WM_STATE_REMOVE) { \
510                         n->client->wm_flags &= ~WM_FLAG_##s; \
511                 } else if (action == XCB_EWMH_WM_STATE_TOGGLE) { \
512                         n->client->wm_flags ^= WM_FLAG_##s; \
513                 } \
514                 ewmh_wm_state_update(n);
515         HANDLE_WM_STATE(MODAL)
516         HANDLE_WM_STATE(MAXIMIZED_VERT)
517         HANDLE_WM_STATE(MAXIMIZED_HORZ)
518         HANDLE_WM_STATE(SHADED)
519         HANDLE_WM_STATE(SKIP_TASKBAR)
520         HANDLE_WM_STATE(SKIP_PAGER)
521         }
522 #undef HANDLE_WM_STATE
523 }
524
525 void mapping_notify(xcb_generic_event_t *evt)
526 {
527         if (mapping_events_count == 0) {
528                 return;
529         }
530
531         xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t *) evt;
532
533         if (e->request == XCB_MAPPING_POINTER) {
534                 return;
535         }
536
537         if (mapping_events_count > 0) {
538                 mapping_events_count--;
539         }
540
541         ungrab_buttons();
542         grab_buttons();
543 }
544
545 void process_error(xcb_generic_event_t *evt)
546 {
547         xcb_request_error_t *e = (xcb_request_error_t *) evt;
548         /* Ignore unavoidable failed requests */
549         if (e->error_code == ERROR_CODE_BAD_WINDOW) {
550                 return;
551         }
552         warn("Failed request: %s, %s: 0x%08X.\n", xcb_event_get_request_label(e->major_opcode), xcb_event_get_error_label(e->error_code), e->bad_value);
553 }