Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
x.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include "x.h"
10
11#include <assert.h>
12#include <cairo.h>
13#include <cairo-xlib.h>
14#include <glib-object.h>
15#include <locale.h>
16#include <math.h>
17#include <stdbool.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#include <X11/extensions/shape.h>
24#include <X11/Xatom.h>
25#include <X11/X.h>
26#include <X11/XKBlib.h>
27#include <X11/Xlib.h>
28#include <X11/Xresource.h>
29#include <X11/Xutil.h>
30#if defined(__linux__) || defined(__FreeBSD__)
31#include <linux/input-event-codes.h>
32#else
33#define BTN_LEFT (0x110)
34#define BTN_RIGHT (0x111)
35#define BTN_MIDDLE (0x112)
36#define BTN_TOUCH (0x14a)
37#endif
38
39#include "../dbus.h"
40#include "../draw.h"
41#include "../dunst.h"
42#include "../log.h"
43#include "../markup.h"
44#include "../menu.h"
45#include "../notification.h"
46#include "../queues.h"
47#include "../settings.h"
48#include "../utils.h"
49#include "../input.h"
50
51#define WIDTH 400
52#define HEIGHT 400
53
54struct window_x11 {
55 Window xwin;
56 cairo_surface_t *root_surface;
57 cairo_t *c_ctx;
58 GSource *esrc;
59 int cur_screen;
60 bool visible;
61 struct dimensions dim;
62};
63
64struct x11_source {
65 GSource source;
66 struct window_x11 *win;
67};
68
69struct x_context xctx;
70bool dunst_grab_errored = false;
71
72static bool fullscreen_last = false;
73
74static void XRM_update_db(void);
75
76static void x_shortcut_init(struct keyboard_shortcut *ks);
77static int x_shortcut_grab(struct keyboard_shortcut *ks);
78static void x_shortcut_ungrab(struct keyboard_shortcut *ks);
79/* FIXME refactor setup teardown handlers into one setup and one teardown */
80static void x_shortcut_setup_error_handler(void);
81static int x_shortcut_tear_down_error_handler(void);
82static void setopacity(Window win, unsigned long opacity);
83static void x_handle_click(XEvent ev);
84
85static void x_win_move(window winptr, int x, int y, int width, int height)
86{
87 struct window_x11 *win = (struct window_x11*)winptr;
88
89 /* move and resize */
90 if (x != win->dim.x || y != win->dim.y) {
91 XMoveWindow(xctx.dpy, win->xwin, x, y);
92
93 win->dim.x = x;
94 win->dim.y = y;
95 }
96
97 if (width != win->dim.w || height != win->dim.h) {
98 XResizeWindow(xctx.dpy, win->xwin, width, height);
99
100 win->dim.h = height;
101 win->dim.w = width;
102 }
103}
104
105static void x_win_corners_shape(struct window_x11 *win, const int rad)
106{
107 const int width = win->dim.w;
108 const int height = win->dim.h;
109
110 Pixmap mask;
111 cairo_surface_t * cxbm;
112 cairo_t * cr;
113 Screen * scr;
114
115 mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1);
116 scr = ScreenOfDisplay(xctx.dpy, win->cur_screen);
117 cxbm = cairo_xlib_surface_create_for_bitmap(xctx.dpy, mask, scr, width, height);
118 cr = cairo_create(cxbm);
119
120 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
121 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
122
123 cairo_set_source_rgba(cr, 0, 0, 0, 0);
124 cairo_paint(cr);
125 cairo_set_source_rgba(cr, 1, 1, 1, 1);
126
127 draw_rounded_rect(cr, 0, 0,
128 width, height,
129 rad, 1,
130 settings.corners);
131 cairo_fill(cr);
132
133 cairo_show_page(cr);
134 cairo_destroy(cr);
135 cairo_surface_flush(cxbm);
136 cairo_surface_destroy(cxbm);
137
138 XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet);
139
140 XFreePixmap(xctx.dpy, mask);
141
142 XShapeSelectInput(xctx.dpy,
143 win->xwin, ShapeNotifyMask);
144}
145
146static void x_win_corners_unshape(window winptr)
147{
148 struct window_x11 *win = (struct window_x11*)winptr;
149 XRectangle rect = {
150 .x = 0,
151 .y = 0,
152 .width = win->dim.w,
153 .height = win->dim.h };
154 XShapeCombineRectangles(xctx.dpy, win->xwin, ShapeBounding, 0, 0, &rect, 1, ShapeSet, 1);
155 XShapeSelectInput(xctx.dpy,
156 win->xwin, ShapeNotifyMask);
157}
158
159static bool x_win_composited(struct window_x11 *win)
160{
161 char astr[sizeof("_NET_WM_CM_S") / sizeof(char) + 8];
162 Atom cm_sel;
163
164 sprintf(astr, "_NET_WM_CM_S%i", win->cur_screen);
165 cm_sel = XInternAtom(xctx.dpy, astr, true);
166
167 if (cm_sel == None) {
168 return false;
169 } else {
170 return XGetSelectionOwner(xctx.dpy, cm_sel) != None;
171 }
172}
173
174void x_display_surface(cairo_surface_t *srf, window winptr, const struct dimensions *dim)
175{
176 struct window_x11 *win = (struct window_x11*)winptr;
177 const struct screen_info *scr = get_active_screen();
178 double scale = x_get_scale();
179 int x, y;
180
181 calc_window_pos(scr, round(dim->w * scale), round(dim->h * scale), &x, &y);
182
183 x_win_move(win, x, y, round(dim->w * scale), round(dim->h * scale));
184 cairo_xlib_surface_set_size(win->root_surface, round(dim->w * scale), round(dim->h * scale));
185
186 XClearWindow(xctx.dpy, win->xwin);
187
188 cairo_set_source_surface(win->c_ctx, srf, 0, 0);
189 cairo_paint(win->c_ctx);
190 cairo_show_page(win->c_ctx);
191
192 if (settings.corner_radius != 0 && ! x_win_composited(win))
193 x_win_corners_shape(win, round(dim->corner_radius * scale));
194 else
195 x_win_corners_unshape(win);
196
197 XFlush(xctx.dpy);
198
199}
200
201cairo_t* x_win_get_context(window winptr)
202{
203 return ((struct window_x11*)win)->c_ctx;
204}
205
206static void setopacity(Window win, unsigned long opacity)
207{
208 Atom _NET_WM_WINDOW_OPACITY =
209 XInternAtom(xctx.dpy, "_NET_WM_WINDOW_OPACITY", false);
210 XChangeProperty(xctx.dpy,
211 win,
212 _NET_WM_WINDOW_OPACITY,
213 XA_CARDINAL,
214 32,
215 PropModeReplace,
216 (unsigned char *)&opacity,
217 1L);
218}
219
220/*
221 * Returns the modifier which is NumLock.
222 */
223static KeySym x_numlock_mod(void)
224{
225 static KeyCode nl = 0;
226 KeySym sym = 0;
227 XModifierKeymap *map = XGetModifierMapping(xctx.dpy);
228
229 if (!nl)
230 nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym("Num_Lock"));
231
232 for (int mod = 0; mod < 8; mod++) {
233 for (int j = 0; j < map->max_keypermod; j++) {
234 if (map->modifiermap[mod*map->max_keypermod+j] == nl) {
235 /* In theory, one could use `1 << mod`, but this
236 * could count as 'using implementation details',
237 * so use this large switch. */
238 switch (mod) {
239 case ShiftMapIndex:
240 sym = ShiftMask;
241 goto end;
242 case LockMapIndex:
243 sym = LockMask;
244 goto end;
245 case ControlMapIndex:
246 sym = ControlMask;
247 goto end;
248 case Mod1MapIndex:
249 sym = Mod1Mask;
250 goto end;
251 case Mod2MapIndex:
252 sym = Mod2Mask;
253 goto end;
254 case Mod3MapIndex:
255 sym = Mod3Mask;
256 goto end;
257 case Mod4MapIndex:
258 sym = Mod4Mask;
259 goto end;
260 case Mod5MapIndex:
261 sym = Mod5Mask;
262 goto end;
263 }
264 }
265 }
266 }
267
268end:
269 XFreeModifiermap(map);
270 return sym;
271}
272
273/*
274 * Helper function to use glib's mainloop mechanic
275 * with Xlib
276 */
277gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout)
278{
279 if (timeout)
280 *timeout = -1;
281 return false;
282}
283
284/*
285 * Helper function to use glib's mainloop mechanic
286 * with Xlib
287 */
288gboolean x_mainloop_fd_check(GSource *source)
289{
290 return XPending(xctx.dpy) > 0;
291}
292
293/*
294 * Main Dispatcher for XEvents
295 */
296gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
297{
298 struct window_x11 *win = ((struct x11_source*) source)->win;
299
300 bool fullscreen_now;
301 const struct screen_info *scr;
302 XEvent ev;
303 unsigned int state;
304 while (XPending(xctx.dpy) > 0) {
305 XNextEvent(xctx.dpy, &ev);
306
307 switch (ev.type) {
308 case Expose:
309 LOG_D("XEvent: processing 'Expose'");
310 if (ev.xexpose.count == 0 && win->visible) {
311 draw();
312 }
313 break;
314 case ButtonRelease:
315 LOG_D("XEvent: processing 'ButtonRelease'");
316 if (ev.xbutton.window == win->xwin) {
317 x_handle_click(ev);
318 wake_up();
319 }
320 break;
321 case KeyPress:
322 LOG_D("XEvent: processing 'KeyPress'");
323 state = ev.xkey.state;
324 /* NumLock is also encoded in the state. Remove it. */
325 state &= ~x_numlock_mod();
326 if (settings.close_ks.str
327 && XLookupKeysym(&ev.xkey,
328 0) == settings.close_ks.sym
329 && settings.close_ks.mask == state) {
330 const GList *displayed = queues_get_displayed();
331 if (displayed && displayed->data) {
333 wake_up();
334 }
335 }
336 if (settings.history_ks.str
337 && XLookupKeysym(&ev.xkey,
338 0) == settings.history_ks.sym
339 && settings.history_ks.mask == state) {
341 wake_up();
342 }
343 if (settings.close_all_ks.str
344 && XLookupKeysym(&ev.xkey,
345 0) == settings.close_all_ks.sym
346 && settings.close_all_ks.mask == state) {
348 wake_up();
349 }
350 if (settings.context_ks.str
351 && XLookupKeysym(&ev.xkey,
352 0) == settings.context_ks.sym
353 && settings.context_ks.mask == state) {
354 context_menu();
355 wake_up();
356 }
357 break;
358 case CreateNotify:
359 LOG_D("XEvent: processing 'CreateNotify'");
360 if (win->visible &&
361 ev.xcreatewindow.override_redirect == 0)
362 XRaiseWindow(xctx.dpy, win->xwin);
363 break;
364 case PropertyNotify:
365 if (ev.xproperty.atom == XA_RESOURCE_MANAGER) {
366 LOG_D("XEvent: processing PropertyNotify for Resource manager");
367 XRM_update_db();
368 screen_dpi_xft_cache_purge();
369
370 if (win->visible) {
371 draw();
372 }
373 break;
374 }
375 /* Explicitly fallthrough. Other PropertyNotify events, e.g. catching
376 * _NET_WM get handled in the Focus(In|Out) section */
377 /* fall through */
378 case ConfigureNotify:
379 case FocusIn:
380 case FocusOut:
381 LOG_D("XEvent: Checking for active screen changes");
382 fullscreen_now = have_fullscreen_window();
383 scr = get_active_screen();
384
385 if (fullscreen_now != fullscreen_last) {
386 fullscreen_last = fullscreen_now;
387 wake_up();
388 } else if ( settings.f_mode != FOLLOW_NONE
389 /* Ignore PropertyNotify, when we're still on the
390 * same screen. PropertyNotify is only necessary
391 * to detect a focus change to another screen
392 */
393 && win->visible
394 && scr->id != win->cur_screen) {
395 draw();
396 win->cur_screen = scr->id;
397 }
398 break;
399 default:
400 if (!screen_check_event(&ev)) {
401 LOG_D("XEvent: Ignoring '%d'", ev.type);
402 }
403
404 break;
405 }
406 }
407 return G_SOURCE_CONTINUE;
408}
409
410/*
411 * Check whether the user is currently idle.
412 */
413bool x_is_idle(void)
414{
415 XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy),
416 xctx.screensaver_info);
417 if (settings.idle_threshold == 0) {
418 return false;
419 }
420 return xctx.screensaver_info->idle > settings.idle_threshold / 1000;
421}
422
423/*
424 * Convert x button code to linux event code
425 * Returns 0 if button is not recognized.
426 */
427static unsigned int x_mouse_button_to_linux_event_code(unsigned int x_button)
428{
429 switch (x_button) {
430 case Button1:
431 return BTN_LEFT;
432 case Button2:
433 return BTN_MIDDLE;
434 case Button3:
435 return BTN_RIGHT;
436 default:
437 LOG_W("Unsupported mouse button: '%d'", x_button);
438 return 0;
439 }
440}
441
442/* TODO move to x_mainloop_* */
443/*
444 * Handle incoming mouse click events
445 */
446static void x_handle_click(XEvent ev)
447{
448 unsigned int linux_code = x_mouse_button_to_linux_event_code(ev.xbutton.button);
449
450 if (linux_code == 0) {
451 return;
452 }
453
454 bool button_state;
455 if(ev.type == ButtonRelease) {
456 button_state = false; // button is up
457 } else {
458 // this shouldn't happen, because this function
459 // is only called when it'a a ButtonRelease event
460 button_state = true; // button is down
461 }
462
463 float scale = x_get_scale();
464 input_handle_click(linux_code, button_state, ev.xbutton.x/scale,
465 ev.xbutton.y/scale);
466}
467
468void x_free(void)
469{
470 if (xctx.screensaver_info)
471 XFree(xctx.screensaver_info);
472
473 if (xctx.dpy)
474 XCloseDisplay(xctx.dpy);
475}
476
477static int XErrorHandlerDB(Display *display, XErrorEvent *e)
478{
479 char err_buf[BUFSIZ];
480 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
481 LOG_W("%s", err_buf);
482 return 0;
483}
484
485static void XRM_update_db(void)
486{
487 XrmDatabase db;
488 XTextProperty prop;
489 Window root;
490 // We shouldn't destroy the first DB coming
491 // from the display object itself
492 static bool runonce = false;
493
494 XFlush(xctx.dpy);
495 XSetErrorHandler(XErrorHandlerDB);
496
497 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
498
499 XLockDisplay(xctx.dpy);
500 if (XGetTextProperty(xctx.dpy, root, &prop, XA_RESOURCE_MANAGER)) {
501 if (runonce) {
502 db = XrmGetDatabase(xctx.dpy);
503 XrmDestroyDatabase(db);
504 }
505
506 // Despite what the XrmSetDatabase docs say, it may try to free
507 // the database, resulting in memory corruption. Prevent that
508 // by making sure it has no db to act on. If it's past the
509 // first run, we will have done XrmDestroyDatabase above
510 // anyway.
511 xctx.dpy->db = NULL;
512
513 db = XrmGetStringDatabase((const char*)prop.value);
514 XrmSetDatabase(xctx.dpy, db);
515 }
516 XUnlockDisplay(xctx.dpy);
517
518 runonce = true;
519
520 XFlush(xctx.dpy);
521 XSync(xctx.dpy, false);
522 XSetErrorHandler(NULL);
523}
524
525/*
526 * Setup X11 stuff
527 */
528bool x_setup(void)
529{
530
531 /* initialize xctx.dc, font, keyboard, colors */
532 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
533 LOG_W("No locale support");
534
535 if (!(xctx.dpy = XOpenDisplay(NULL))) {
536 LOG_W("Cannot open X11 display.");
537 return false;
538 }
539
540 x_shortcut_init(&settings.close_ks);
541 x_shortcut_init(&settings.close_all_ks);
542 x_shortcut_init(&settings.history_ks);
543 x_shortcut_init(&settings.context_ks);
544
545 x_shortcut_grab(&settings.close_ks);
546 x_shortcut_ungrab(&settings.close_ks);
547 x_shortcut_grab(&settings.close_all_ks);
548 x_shortcut_ungrab(&settings.close_all_ks);
549 x_shortcut_grab(&settings.history_ks);
550 x_shortcut_ungrab(&settings.history_ks);
551 x_shortcut_grab(&settings.context_ks);
552 x_shortcut_ungrab(&settings.context_ks);
553
554 xctx.screensaver_info = XScreenSaverAllocInfo();
555
556 XrmInitialize();
557 XRM_update_db();
558
559 init_screens();
560 x_shortcut_grab(&settings.history_ks);
561 return true;
562}
563
564static void x_set_wm(Window win)
565{
566
567 Atom data[2];
568
569 /* set window title */
570 char *title = settings.title != NULL ? settings.title : "Dunst";
571 Atom _net_wm_title =
572 XInternAtom(xctx.dpy, "_NET_WM_NAME", false);
573
574 XStoreName(xctx.dpy, win, title);
575 XChangeProperty(xctx.dpy,
576 win,
577 _net_wm_title,
578 XInternAtom(xctx.dpy, "UTF8_STRING", false),
579 8,
580 PropModeReplace,
581 (unsigned char *)title,
582 strlen(title));
583
584 /* set window class */
585 char *class = settings.class != NULL ? settings.class : "Dunst";
586 XClassHint classhint = { class, "Dunst" };
587
588 XSetClassHint(xctx.dpy, win, &classhint);
589
590 /* set window type */
591 Atom net_wm_window_type =
592 XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE", false);
593
594 data[0] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", false);
595 data[1] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false);
596
597 XChangeProperty(xctx.dpy,
598 win,
599 net_wm_window_type,
600 XA_ATOM,
601 32,
602 PropModeReplace,
603 (unsigned char *)data,
604 2L);
605
606 /* set state above */
607 Atom net_wm_state =
608 XInternAtom(xctx.dpy, "_NET_WM_STATE", false);
609
610 data[0] = XInternAtom(xctx.dpy, "_NET_WM_STATE_ABOVE", false);
611
612 XChangeProperty(xctx.dpy, win, net_wm_state, XA_ATOM, 32,
613 PropModeReplace, (unsigned char *) data, 1L);
614}
615
616GSource* x_win_reg_source(struct window_x11 *win)
617{
618 // Static is necessary here because glib keeps the pointer and we need
619 // to keep the reference alive.
620 static GSourceFuncs xsrc_fn = {
621 x_mainloop_fd_prepare,
622 x_mainloop_fd_check,
623 x_mainloop_fd_dispatch,
624 NULL,
625 NULL,
626 NULL
627 };
628
629 struct x11_source *xsrc = (struct x11_source*) g_source_new(&xsrc_fn,
630 sizeof(struct x11_source));
631
632 xsrc->win = win;
633
634 g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR);
635
636 g_source_attach((GSource*) xsrc, NULL);
637
638 return (GSource*)xsrc;
639}
640
641/*
642 * Setup the window
643 */
644window x_win_create(void)
645{
646 struct window_x11 *win = g_malloc0(sizeof(struct window_x11));
647
648 Window root;
649 int scr_n;
650 int depth;
651 Visual * vis;
652 XVisualInfo vi;
653 XSetWindowAttributes wa;
654
655 scr_n = DefaultScreen(xctx.dpy);
656 root = RootWindow(xctx.dpy, scr_n);
657 if (XMatchVisualInfo(xctx.dpy, scr_n, 32, TrueColor, &vi)) {
658 vis = vi.visual;
659 depth = vi.depth;
660 } else {
661 vis = DefaultVisual(xctx.dpy, scr_n);
662 depth = DefaultDepth(xctx.dpy, scr_n);
663 }
664
665 wa.override_redirect = true;
666 wa.background_pixmap = None;
667 wa.background_pixel = 0;
668 wa.border_pixel = 0;
669 wa.colormap = XCreateColormap(xctx.dpy, root, vis, AllocNone);
670 wa.event_mask =
671 ExposureMask | KeyPressMask | VisibilityChangeMask |
672 ButtonReleaseMask | FocusChangeMask| StructureNotifyMask;
673
674 const struct screen_info *scr = get_active_screen();
675 win->xwin = XCreateWindow(xctx.dpy,
676 root,
677 scr->x,
678 scr->y,
679 scr->w,
680 1,
681 0,
682 depth,
683 CopyFromParent,
684 vis,
685 CWOverrideRedirect | CWBackPixmap | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask,
686 &wa);
687
688 x_set_wm(win->xwin);
689 settings.transparency =
690 settings.transparency > 100 ? 100 : settings.transparency;
691 setopacity(win->xwin,
692 (unsigned long)((100 - settings.transparency) *
693 (0xffffffff / 100)));
694
695 win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin,
696 vis,
697 WIDTH, HEIGHT);
698 win->c_ctx = cairo_create(win->root_surface);
699
700 win->esrc = x_win_reg_source(win);
701
702 /* SubstructureNotifyMask is required for receiving CreateNotify events
703 * in order to raise the window when something covers us. See #160
704 *
705 * PropertyChangeMask is requred for getting screen change events when follow_mode != none
706 * and it's also needed to receive
707 * XA_RESOURCE_MANAGER events to update the dpi when
708 * the xresource value is updated
709 */
710 long root_event_mask = SubstructureNotifyMask | PropertyChangeMask;
711 if (settings.f_mode != FOLLOW_NONE) {
712 root_event_mask |= FocusChangeMask;
713 }
714 XSelectInput(xctx.dpy, root, root_event_mask);
715
716 return (window)win;
717}
718
719void x_win_destroy(window winptr)
720{
721 struct window_x11 *win = (struct window_x11*)winptr;
722
723 g_source_destroy(win->esrc);
724 g_source_unref(win->esrc);
725
726 cairo_destroy(win->c_ctx);
727 cairo_surface_destroy(win->root_surface);
728 XDestroyWindow(xctx.dpy, win->xwin);
729
730 g_free(win);
731}
732
733/*
734 * Show the window and grab shortcuts.
735 */
736void x_win_show(window winptr)
737{
738 struct window_x11 *win = (struct window_x11*)winptr;
739
740 /* window is already mapped or there's nothing to show */
741 if (win->visible)
742 return;
743
744
745 x_shortcut_grab(&settings.close_ks);
746 x_shortcut_grab(&settings.close_all_ks);
747 x_shortcut_grab(&settings.context_ks);
748
749 x_shortcut_setup_error_handler();
750 XGrabButton(xctx.dpy,
751 AnyButton,
752 AnyModifier,
753 win->xwin,
754 false,
755 (ButtonPressMask|ButtonReleaseMask),
756 GrabModeAsync,
757 GrabModeSync,
758 None,
759 None);
760 if (x_shortcut_tear_down_error_handler()) {
761 LOG_W("Unable to grab mouse button(s).");
762 }
763
764 XMapRaised(xctx.dpy, win->xwin);
765 win->visible = true;
766
767 x_display_surface(win->root_surface, win, &win->dim);
768}
769
770/*
771 * Hide the window and ungrab unused keyboard_shortcuts
772 */
773void x_win_hide(window winptr)
774{
775 LOG_I("Hiding X11 window");
776 struct window_x11 *win = (struct window_x11*)winptr;
777 ASSERT_OR_RET(win->visible,);
778
779 x_shortcut_ungrab(&settings.close_ks);
780 x_shortcut_ungrab(&settings.close_all_ks);
781 x_shortcut_ungrab(&settings.context_ks);
782
783 XUngrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin);
784 XUnmapWindow(xctx.dpy, win->xwin);
785 XFlush(xctx.dpy);
786 win->visible = false;
787}
788
789/*
790 * Parse a string into a modifier mask.
791 */
792KeySym x_shortcut_string_to_mask(const char *str)
793{
794 if (STR_EQ(str, "ctrl")) {
795 return ControlMask;
796 } else if (STR_EQ(str, "mod4")) {
797 return Mod4Mask;
798 } else if (STR_EQ(str, "mod3")) {
799 return Mod3Mask;
800 } else if (STR_EQ(str, "mod2")) {
801 return Mod2Mask;
802 } else if (STR_EQ(str, "mod1")) {
803 return Mod1Mask;
804 } else if (STR_EQ(str, "shift")) {
805 return ShiftMask;
806 } else {
807 LOG_W("Unknown Modifier: '%s'", str);
808 return 0;
809 }
810}
811
812/*
813 * Error handler for grabbing mouse and keyboard errors.
814 */
815static int GrabXErrorHandler(Display *display, XErrorEvent *e)
816{
817 dunst_grab_errored = true;
818 char err_buf[BUFSIZ];
819 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
820
821 if (e->error_code != BadAccess) {
822 DIE("%s", err_buf);
823 } else {
824 LOG_W("%s", err_buf);
825 }
826
827 return 0;
828}
829
830/*
831 * Setup the Error handler.
832 */
833static void x_shortcut_setup_error_handler(void)
834{
835 dunst_grab_errored = false;
836
837 XFlush(xctx.dpy);
838 XSetErrorHandler(GrabXErrorHandler);
839}
840
841/*
842 * Tear down the Error handler.
843 */
844static int x_shortcut_tear_down_error_handler(void)
845{
846 XFlush(xctx.dpy);
847 XSync(xctx.dpy, false);
848 XSetErrorHandler(NULL);
849 return dunst_grab_errored;
850}
851
852/*
853 * Grab the given keyboard shortcut.
854 */
855static int x_shortcut_grab(struct keyboard_shortcut *ks)
856{
857 ASSERT_OR_RET(ks->is_valid, 1);
858 Window root;
859 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
860
861 x_shortcut_setup_error_handler();
862
863 if (ks->is_valid) {
864 XGrabKey(xctx.dpy,
865 ks->code,
866 ks->mask,
867 root,
868 true,
869 GrabModeAsync,
870 GrabModeAsync);
871 XGrabKey(xctx.dpy,
872 ks->code,
873 ks->mask | x_numlock_mod(),
874 root,
875 true,
876 GrabModeAsync,
877 GrabModeAsync);
878 }
879
880 if (x_shortcut_tear_down_error_handler()) {
881 LOG_W("Unable to grab key '%s'.", ks->str);
882 ks->is_valid = false;
883 return 1;
884 }
885 return 0;
886}
887
888/*
889 * Ungrab the given keyboard shortcut.
890 */
891static void x_shortcut_ungrab(struct keyboard_shortcut *ks)
892{
893 Window root;
894 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
895 if (ks->is_valid) {
896 XUngrabKey(xctx.dpy, ks->code, ks->mask, root);
897 XUngrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), root);
898 }
899}
900
901/*
902 * Initialize the keyboard shortcut.
903 */
904static void x_shortcut_init(struct keyboard_shortcut *ks)
905{
906 ASSERT_OR_RET(ks && ks->str,);
907
908 if (STR_EQ(ks->str, "none") || (STR_EQ(ks->str, ""))) {
909 ks->is_valid = false;
910 return;
911 }
912
913 char *str = g_strdup(ks->str);
914 char *str_begin = str;
915
916 while (strchr(str, '+')) {
917 char *mod = str;
918 while (*str != '+')
919 str++;
920 *str = '\0';
921 str++;
922 g_strchomp(mod);
923 ks->mask = ks->mask | x_shortcut_string_to_mask(mod);
924 }
925 g_strstrip(str);
926
927 ks->sym = XStringToKeysym(str);
928 /* find matching keycode for ks->sym */
929 int min_keysym, max_keysym;
930 XDisplayKeycodes(xctx.dpy, &min_keysym, &max_keysym);
931
932 ks->code = NoSymbol;
933
934 for (int i = min_keysym; i <= max_keysym; i++) {
935 if (XkbKeycodeToKeysym(xctx.dpy, i, 0, 0) == ks->sym
936 || XkbKeycodeToKeysym(xctx.dpy, i, 0, 1) == ks->sym) {
937 ks->code = i;
938 break;
939 }
940 }
941
942 if (ks->sym == NoSymbol || ks->code == NoSymbol) {
943 LOG_W("Unknown keyboard shortcut: '%s'", ks->str);
944 ks->is_valid = false;
945 } else {
946 ks->is_valid = true;
947 }
948
949 g_free(str_begin);
950}
951
952double x_get_scale(void) {
953 if (settings.scale > 0)
954 return settings.scale;
955
956 const struct screen_info *scr_info = get_active_screen();
957 double scale = MAX(1, scr_info->dpi/96.);
958 LOG_D("X11 dpi: %i", scr_info->dpi);
959 LOG_D("X11 scale: %f", scale);
960 return scale;
961}
DBus support and implementation of the Desktop Notifications Specification.
@ REASON_USER
The user closed the notification.
Definition dbus.h:21
void calc_window_pos(const struct screen_info *scr, int width, int height, int *ret_x, int *ret_y)
Calculates the position the window should be placed at given its width and height and stores them in ...
Definition draw.c:975
void draw_rounded_rect(cairo_t *c, float x, float y, int width, int height, int corner_radius, double scale, enum corner_pos corners)
Create a path on the given cairo context to draw the background of a notification.
Definition draw.c:556
Layout and render notifications.
Main event loop logic.
void input_handle_click(unsigned int button, bool button_down, int mouse_x, int mouse_y)
Handle incoming mouse click events.
Definition input.c:67
Input handling for mouse events.
Logging subsystem and helpers.
Markup handling for notifications body.
void context_menu(void)
Open the context menu that lets the user select urls/actions/etc for all displayed notifications.
Definition menu.c:320
Context menu for actions and helpers.
Notification type definitions.
GList * queues_get_displayed(void)
Receive the current list of displayed notifications.
Definition queues.c:41
void queues_history_pop(void)
Pushes the latest notification of history to the displayed queue and removes it from history.
Definition queues.c:409
static GQueue * displayed
currently displayed notifications
Definition queues.c:24
void queues_notification_close(struct notification *n, enum reason reason)
Close the given notification.
Definition queues.c:380
void queues_history_push_all(void)
Push all waiting and displayed notifications to history.
Definition queues.c:465
Queues for history, waiting and displayed notifications.
bool have_fullscreen_window(void)
Find the currently focused window and check if it's in fullscreen mode.
Definition screen.c:238
Type definitions for settings.
Definition x.c:54
Definition x.c:64
Definition x.h:25
String, time and other various helpers.
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42
Xorg output wrapper.