13#include <cairo-xlib.h>
14#include <glib-object.h>
23#include <X11/extensions/shape.h>
26#include <X11/XKBlib.h>
28#include <X11/Xresource.h>
30#if defined(__linux__) || defined(__FreeBSD__)
31#include <linux/input-event-codes.h>
33#define BTN_LEFT (0x110)
34#define BTN_RIGHT (0x111)
35#define BTN_MIDDLE (0x112)
36#define BTN_TOUCH (0x14a)
56 cairo_surface_t *root_surface;
70bool dunst_grab_errored =
false;
72static bool fullscreen_last =
false;
74static void XRM_update_db(
void);
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);
85static void x_win_move(window winptr,
int x,
int y,
int width,
int height)
90 if (x != win->dim.x || y != win->dim.y) {
91 XMoveWindow(xctx.dpy, win->xwin, x, y);
97 if (width != win->dim.w || height != win->dim.h) {
98 XResizeWindow(xctx.dpy, win->xwin, width, height);
105static void x_win_corners_shape(
struct window_x11 *win,
const int rad)
107 const int width = win->dim.w;
108 const int height = win->dim.h;
111 cairo_surface_t * cxbm;
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);
120 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
121 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
123 cairo_set_source_rgba(cr, 0, 0, 0, 0);
125 cairo_set_source_rgba(cr, 1, 1, 1, 1);
135 cairo_surface_flush(cxbm);
136 cairo_surface_destroy(cxbm);
138 XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet);
140 XFreePixmap(xctx.dpy, mask);
142 XShapeSelectInput(xctx.dpy,
143 win->xwin, ShapeNotifyMask);
146static void x_win_corners_unshape(window winptr)
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);
159static bool x_win_composited(
struct window_x11 *win)
161 char astr[
sizeof(
"_NET_WM_CM_S") /
sizeof(
char) + 8];
164 sprintf(astr,
"_NET_WM_CM_S%i", win->cur_screen);
165 cm_sel = XInternAtom(xctx.dpy, astr,
true);
167 if (cm_sel == None) {
170 return XGetSelectionOwner(xctx.dpy, cm_sel) != None;
174void x_display_surface(cairo_surface_t *srf, window winptr,
const struct dimensions *dim)
177 const struct screen_info *scr = get_active_screen();
178 double scale = x_get_scale();
181 calc_window_pos(scr, round(dim->w * scale), round(dim->h * scale), &x, &y);
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));
186 XClearWindow(xctx.dpy, win->xwin);
188 cairo_set_source_surface(win->c_ctx, srf, 0, 0);
189 cairo_paint(win->c_ctx);
190 cairo_show_page(win->c_ctx);
192 if (
settings.corner_radius != 0 && ! x_win_composited(win))
193 x_win_corners_shape(win, round(dim->corner_radius * scale));
195 x_win_corners_unshape(win);
201cairo_t* x_win_get_context(window winptr)
206static void setopacity(Window win,
unsigned long opacity)
208 Atom _NET_WM_WINDOW_OPACITY =
209 XInternAtom(xctx.dpy,
"_NET_WM_WINDOW_OPACITY",
false);
210 XChangeProperty(xctx.dpy,
212 _NET_WM_WINDOW_OPACITY,
216 (
unsigned char *)&opacity,
223static KeySym x_numlock_mod(
void)
225 static KeyCode nl = 0;
227 XModifierKeymap *map = XGetModifierMapping(xctx.dpy);
230 nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym(
"Num_Lock"));
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) {
245 case ControlMapIndex:
269 XFreeModifiermap(map);
277gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout)
288gboolean x_mainloop_fd_check(GSource *source)
290 return XPending(xctx.dpy) > 0;
296gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
304 while (XPending(xctx.dpy) > 0) {
305 XNextEvent(xctx.dpy, &ev);
309 LOG_D(
"XEvent: processing 'Expose'");
310 if (ev.xexpose.count == 0 && win->visible) {
315 LOG_D(
"XEvent: processing 'ButtonRelease'");
316 if (ev.xbutton.window == win->xwin) {
322 LOG_D(
"XEvent: processing 'KeyPress'");
323 state = ev.xkey.state;
325 state &= ~x_numlock_mod();
327 && XLookupKeysym(&ev.xkey,
329 &&
settings.close_ks.mask == state) {
337 && XLookupKeysym(&ev.xkey,
339 &&
settings.history_ks.mask == state) {
344 && XLookupKeysym(&ev.xkey,
346 &&
settings.close_all_ks.mask == state) {
351 && XLookupKeysym(&ev.xkey,
353 &&
settings.context_ks.mask == state) {
359 LOG_D(
"XEvent: processing 'CreateNotify'");
361 ev.xcreatewindow.override_redirect == 0)
362 XRaiseWindow(xctx.dpy, win->xwin);
365 if (ev.xproperty.atom == XA_RESOURCE_MANAGER) {
366 LOG_D(
"XEvent: processing PropertyNotify for Resource manager");
368 screen_dpi_xft_cache_purge();
378 case ConfigureNotify:
381 LOG_D(
"XEvent: Checking for active screen changes");
383 scr = get_active_screen();
385 if (fullscreen_now != fullscreen_last) {
386 fullscreen_last = fullscreen_now;
388 }
else if (
settings.f_mode != FOLLOW_NONE
394 && scr->id != win->cur_screen) {
396 win->cur_screen = scr->id;
400 if (!screen_check_event(&ev)) {
401 LOG_D(
"XEvent: Ignoring '%d'", ev.type);
407 return G_SOURCE_CONTINUE;
415 XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy),
416 xctx.screensaver_info);
420 return xctx.screensaver_info->idle >
settings.idle_threshold / 1000;
427static unsigned int x_mouse_button_to_linux_event_code(
unsigned int x_button)
437 LOG_W(
"Unsupported mouse button: '%d'", x_button);
446static void x_handle_click(XEvent ev)
448 unsigned int linux_code = x_mouse_button_to_linux_event_code(ev.xbutton.button);
450 if (linux_code == 0) {
455 if(ev.type == ButtonRelease) {
456 button_state =
false;
463 float scale = x_get_scale();
470 if (xctx.screensaver_info)
471 XFree(xctx.screensaver_info);
474 XCloseDisplay(xctx.dpy);
477static int XErrorHandlerDB(Display *display, XErrorEvent *e)
479 char err_buf[BUFSIZ];
480 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
481 LOG_W(
"%s", err_buf);
485static void XRM_update_db(
void)
492 static bool runonce =
false;
495 XSetErrorHandler(XErrorHandlerDB);
497 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
499 XLockDisplay(xctx.dpy);
500 if (XGetTextProperty(xctx.dpy, root, &prop, XA_RESOURCE_MANAGER)) {
502 db = XrmGetDatabase(xctx.dpy);
503 XrmDestroyDatabase(db);
513 db = XrmGetStringDatabase((
const char*)prop.value);
514 XrmSetDatabase(xctx.dpy, db);
516 XUnlockDisplay(xctx.dpy);
521 XSync(xctx.dpy,
false);
522 XSetErrorHandler(NULL);
532 if (!setlocale(LC_CTYPE,
"") || !XSupportsLocale())
533 LOG_W(
"No locale support");
535 if (!(xctx.dpy = XOpenDisplay(NULL))) {
536 LOG_W(
"Cannot open X11 display.");
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);
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);
554 xctx.screensaver_info = XScreenSaverAllocInfo();
560 x_shortcut_grab(&
settings.history_ks);
564static void x_set_wm(Window win)
572 XInternAtom(xctx.dpy,
"_NET_WM_NAME",
false);
574 XStoreName(xctx.dpy, win, title);
575 XChangeProperty(xctx.dpy,
578 XInternAtom(xctx.dpy,
"UTF8_STRING",
false),
581 (
unsigned char *)title,
585 char *
class = settings.class != NULL ?
settings.class :
"Dunst";
586 XClassHint classhint = {
class,
"Dunst" };
588 XSetClassHint(xctx.dpy, win, &classhint);
591 Atom net_wm_window_type =
592 XInternAtom(xctx.dpy,
"_NET_WM_WINDOW_TYPE",
false);
594 data[0] = XInternAtom(xctx.dpy,
"_NET_WM_WINDOW_TYPE_NOTIFICATION",
false);
595 data[1] = XInternAtom(xctx.dpy,
"_NET_WM_WINDOW_TYPE_UTILITY",
false);
597 XChangeProperty(xctx.dpy,
603 (
unsigned char *)data,
608 XInternAtom(xctx.dpy,
"_NET_WM_STATE",
false);
610 data[0] = XInternAtom(xctx.dpy,
"_NET_WM_STATE_ABOVE",
false);
612 XChangeProperty(xctx.dpy, win, net_wm_state, XA_ATOM, 32,
613 PropModeReplace, (
unsigned char *) data, 1L);
616GSource* x_win_reg_source(
struct window_x11 *win)
620 static GSourceFuncs xsrc_fn = {
621 x_mainloop_fd_prepare,
623 x_mainloop_fd_dispatch,
634 g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR);
636 g_source_attach((GSource*) xsrc, NULL);
638 return (GSource*)xsrc;
644window x_win_create(
void)
653 XSetWindowAttributes wa;
655 scr_n = DefaultScreen(xctx.dpy);
656 root = RootWindow(xctx.dpy, scr_n);
657 if (XMatchVisualInfo(xctx.dpy, scr_n, 32, TrueColor, &vi)) {
661 vis = DefaultVisual(xctx.dpy, scr_n);
662 depth = DefaultDepth(xctx.dpy, scr_n);
665 wa.override_redirect =
true;
666 wa.background_pixmap = None;
667 wa.background_pixel = 0;
669 wa.colormap = XCreateColormap(xctx.dpy, root, vis, AllocNone);
671 ExposureMask | KeyPressMask | VisibilityChangeMask |
672 ButtonReleaseMask | FocusChangeMask| StructureNotifyMask;
674 const struct screen_info *scr = get_active_screen();
675 win->xwin = XCreateWindow(xctx.dpy,
685 CWOverrideRedirect | CWBackPixmap | CWBackPixel | CWBorderPixel | CWColormap | CWEventMask,
691 setopacity(win->xwin,
692 (
unsigned long)((100 -
settings.transparency) *
693 (0xffffffff / 100)));
695 win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin,
698 win->c_ctx = cairo_create(win->root_surface);
700 win->esrc = x_win_reg_source(win);
710 long root_event_mask = SubstructureNotifyMask | PropertyChangeMask;
711 if (
settings.f_mode != FOLLOW_NONE) {
712 root_event_mask |= FocusChangeMask;
714 XSelectInput(xctx.dpy, root, root_event_mask);
719void x_win_destroy(window winptr)
723 g_source_destroy(win->esrc);
724 g_source_unref(win->esrc);
726 cairo_destroy(win->c_ctx);
727 cairo_surface_destroy(win->root_surface);
728 XDestroyWindow(xctx.dpy, win->xwin);
736void x_win_show(window winptr)
745 x_shortcut_grab(&
settings.close_ks);
746 x_shortcut_grab(&
settings.close_all_ks);
747 x_shortcut_grab(&
settings.context_ks);
749 x_shortcut_setup_error_handler();
750 XGrabButton(xctx.dpy,
755 (ButtonPressMask|ButtonReleaseMask),
760 if (x_shortcut_tear_down_error_handler()) {
761 LOG_W(
"Unable to grab mouse button(s).");
764 XMapRaised(xctx.dpy, win->xwin);
767 x_display_surface(win->root_surface, win, &win->dim);
773void x_win_hide(window winptr)
775 LOG_I(
"Hiding X11 window");
779 x_shortcut_ungrab(&
settings.close_ks);
780 x_shortcut_ungrab(&
settings.close_all_ks);
781 x_shortcut_ungrab(&
settings.context_ks);
783 XUngrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin);
784 XUnmapWindow(xctx.dpy, win->xwin);
786 win->visible =
false;
792KeySym x_shortcut_string_to_mask(
const char *str)
794 if (
STR_EQ(str,
"ctrl")) {
796 }
else if (
STR_EQ(str,
"mod4")) {
798 }
else if (
STR_EQ(str,
"mod3")) {
800 }
else if (
STR_EQ(str,
"mod2")) {
802 }
else if (
STR_EQ(str,
"mod1")) {
804 }
else if (
STR_EQ(str,
"shift")) {
807 LOG_W(
"Unknown Modifier: '%s'", str);
815static int GrabXErrorHandler(Display *display, XErrorEvent *e)
817 dunst_grab_errored =
true;
818 char err_buf[BUFSIZ];
819 XGetErrorText(display, e->error_code, err_buf, BUFSIZ);
821 if (e->error_code != BadAccess) {
824 LOG_W(
"%s", err_buf);
833static void x_shortcut_setup_error_handler(
void)
835 dunst_grab_errored =
false;
838 XSetErrorHandler(GrabXErrorHandler);
844static int x_shortcut_tear_down_error_handler(
void)
847 XSync(xctx.dpy,
false);
848 XSetErrorHandler(NULL);
849 return dunst_grab_errored;
859 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
861 x_shortcut_setup_error_handler();
873 ks->mask | x_numlock_mod(),
880 if (x_shortcut_tear_down_error_handler()) {
881 LOG_W(
"Unable to grab key '%s'.", ks->str);
882 ks->is_valid =
false;
894 root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
896 XUngrabKey(xctx.dpy, ks->code, ks->mask, root);
897 XUngrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), root);
909 ks->is_valid =
false;
913 char *str = g_strdup(ks->str);
914 char *str_begin = str;
916 while (strchr(str,
'+')) {
923 ks->mask = ks->mask | x_shortcut_string_to_mask(mod);
927 ks->sym = XStringToKeysym(str);
929 int min_keysym, max_keysym;
930 XDisplayKeycodes(xctx.dpy, &min_keysym, &max_keysym);
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) {
942 if (ks->sym == NoSymbol || ks->code == NoSymbol) {
943 LOG_W(
"Unknown keyboard shortcut: '%s'", ks->str);
944 ks->is_valid =
false;
952double x_get_scale(
void) {
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);
DBus support and implementation of the Desktop Notifications Specification.
@ REASON_USER
The user closed the notification.
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 ...
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.
Layout and render notifications.
Logging subsystem and helpers.
Markup handling for notifications body.
Notification type definitions.
GList * queues_get_displayed(void)
Receive the current list of displayed notifications.
void queues_history_pop(void)
Pushes the latest notification of history to the displayed queue and removes it from history.
static GQueue * displayed
currently displayed notifications
void queues_notification_close(struct notification *n, enum reason reason)
Close the given notification.
void queues_history_push_all(void)
Push all waiting and displayed notifications to history.
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.
Type definitions for settings.
String, time and other various helpers.
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.