/* MIT/X Consortium License © 2013 Sascha Kruse and contributors © 2010-2011 Connor Lane Smith © 2006-2011 Anselm R Garbe © 2009 Gottox © 2009 Markus Schnalke © 2009 Evan Gates © 2006-2008 Sander van Dijk © 2006-2007 Michał Janeczek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "x.h" #include "utils.h" #include "dunst.h" #include "settings.h" #include "notification.h" xctx_t xctx; bool dunst_grab_errored = false; static void x_shortcut_setup_error_handler(void); static int x_shortcut_tear_down_error_handler(void); void drawrect(DC * dc, int x, int y, unsigned int w, unsigned int h, bool fill, unsigned long color) { XSetForeground(dc->dpy, dc->gc, color); if (fill) XFillRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w, h); else XDrawRectangle(dc->dpy, dc->canvas, dc->gc, dc->x + x, dc->y + y, w - 1, h - 1); } void drawtext(DC * dc, const char *text, ColorSet * col) { char buf[BUFSIZ]; size_t mn, n = strlen(text); /* shorten text if necessary */ for (mn = MIN(n, sizeof buf); textnw(dc, text, mn) + dc->font.height / 2 > dc->w; mn--) if (mn == 0) return; memcpy(buf, text, mn); if (mn < n) for (n = MAX(mn - 3, 0); n < mn; buf[n++] = '.') ; drawrect(dc, 0, 0, dc->w, dc->h, true, col->BG); drawtextn(dc, buf, mn, col); } void drawtextn(DC * dc, const char *text, size_t n, ColorSet * col) { int x = dc->x + dc->font.height / 2; int y = dc->y + dc->font.ascent + 1; XSetForeground(dc->dpy, dc->gc, col->FG); if (dc->font.xft_font) { if (!dc->xftdraw) eprintf("error, xft drawable does not exist"); XftDrawStringUtf8(dc->xftdraw, &col->FG_xft, dc->font.xft_font, x, y, (unsigned char *)text, n); } else if (dc->font.set) { printf("XmbDrawString\n"); XmbDrawString(dc->dpy, dc->canvas, dc->font.set, dc->gc, x, y, text, n); } else { XSetFont(dc->dpy, dc->gc, dc->font.xfont->fid); XDrawString(dc->dpy, dc->canvas, dc->gc, x, y, text, n); } } void eprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == ':') { fputc(' ', stderr); perror(NULL); } exit(EXIT_FAILURE); } void freecol(DC * dc, ColorSet * col) { if (col) { if (&col->FG_xft) XftColorFree(dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), &col->FG_xft); free(col); } } void freedc(DC * dc) { if (dc->font.xft_font) { XftFontClose(dc->dpy, dc->font.xft_font); XftDrawDestroy(dc->xftdraw); } if (dc->font.set) XFreeFontSet(dc->dpy, dc->font.set); if (dc->font.xfont) XFreeFont(dc->dpy, dc->font.xfont); if (dc->canvas) XFreePixmap(dc->dpy, dc->canvas); if (dc->gc) XFreeGC(dc->dpy, dc->gc); if (dc->dpy) XCloseDisplay(dc->dpy); if (dc) free(dc); } unsigned long getcolor(DC * dc, const char *colstr) { Colormap cmap = DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)); XColor color; if (!XAllocNamedColor(dc->dpy, cmap, colstr, &color, &color)) eprintf("cannot allocate color '%s'\n", colstr); return color.pixel; } ColorSet *initcolor(DC * dc, const char *foreground, const char *background) { ColorSet *col = (ColorSet *) malloc(sizeof(ColorSet)); if (!col) { eprintf("error, cannot allocate memory for color set"); exit(EXIT_FAILURE); } col->BG = getcolor(dc, background); col->FG = getcolor(dc, foreground); if (dc->font.xft_font) if (!XftColorAllocName (dc->dpy, DefaultVisual(dc->dpy, DefaultScreen(dc->dpy)), DefaultColormap(dc->dpy, DefaultScreen(dc->dpy)), foreground, &col->FG_xft)) eprintf("error, cannot allocate xft font color '%s'\n", foreground); return col; } DC *initdc(void) { DC *dc; if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("no locale support\n", stderr); if (!(dc = calloc(1, sizeof *dc))) { eprintf("cannot malloc %u bytes:", sizeof *dc); exit(EXIT_FAILURE); } if (!(dc->dpy = XOpenDisplay(NULL))) { eprintf("cannot open display\n"); exit(EXIT_FAILURE); } dc->gc = XCreateGC(dc->dpy, DefaultRootWindow(dc->dpy), 0, NULL); XSetLineAttributes(dc->dpy, dc->gc, 1, LineSolid, CapButt, JoinMiter); return dc; } void initfont(DC * dc, const char *fontstr) { char *def, **missing, **names; int i, n; XFontStruct **xfonts; missing = NULL; if ((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) { dc->font.ascent = dc->font.xfont->ascent; dc->font.descent = dc->font.xfont->descent; dc->font.width = dc->font.xfont->max_bounds.width; } else if ((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) { n = XFontsOfFontSet(dc->font.set, &xfonts, &names); for (i = 0; i < n; i++) { dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent); dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent); dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width); } } else if ((dc->font.xft_font = XftFontOpenName(dc->dpy, DefaultScreen(dc->dpy), fontstr))) { dc->font.ascent = dc->font.xft_font->ascent; dc->font.descent = dc->font.xft_font->descent; dc->font.width = dc->font.xft_font->max_advance_width; } else { eprintf("cannot load font '%s'\n", fontstr); } if (missing) XFreeStringList(missing); dc->font.height = dc->font.ascent + dc->font.descent; return; } void setopacity(DC * dc, Window win, unsigned long opacity) { Atom _NET_WM_WINDOW_OPACITY = XInternAtom(dc->dpy, "_NET_WM_WINDOW_OPACITY", false); XChangeProperty(dc->dpy, win, _NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); } void mapdc(DC * dc, Window win, unsigned int w, unsigned int h) { XCopyArea(dc->dpy, dc->canvas, win, dc->gc, 0, 0, w, h, 0, 0); } void resizedc(DC * dc, unsigned int w, unsigned int h) { int screen = DefaultScreen(dc->dpy); if (dc->canvas) XFreePixmap(dc->dpy, dc->canvas); dc->w = w; dc->h = h; dc->canvas = XCreatePixmap(dc->dpy, DefaultRootWindow(dc->dpy), w, h, DefaultDepth(dc->dpy, screen)); if (dc->xftdraw) { XftDrawDestroy(dc->xftdraw); } if (dc->font.xft_font) { dc->xftdraw = XftDrawCreate(dc->dpy, dc->canvas, DefaultVisual(dc->dpy, screen), DefaultColormap(dc->dpy, screen)); if (!(dc->xftdraw)) eprintf("error, cannot create xft drawable\n"); } } int textnw(DC * dc, const char *text, size_t len) { if (dc->font.xft_font) { XGlyphInfo gi; XftTextExtentsUtf8(dc->dpy, dc->font.xft_font, (const FcChar8 *)text, len, &gi); return gi.width; } else if (dc->font.set) { XRectangle r; XmbTextExtents(dc->font.set, text, len, NULL, &r); return r.width; } return XTextWidth(dc->font.xfont, text, len); } int textw(DC * dc, const char *text) { return textnw(dc, text, strlen(text)) + dc->font.height; } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_prepare(GSource * source, gint * timeout) { *timeout = -1; return false; } /* * Helper function to use glib's mainloop mechanic * with Xlib */ gboolean x_mainloop_fd_check(GSource * source) { return XPending(xctx.dc->dpy) > 0; } /* * Main Dispatcher for XEvents */ gboolean x_mainloop_fd_dispatch(GSource * source, GSourceFunc callback, gpointer user_data) { XEvent ev; while (XPending(xctx.dc->dpy) > 0) { XNextEvent(xctx.dc->dpy, &ev); switch (ev.type) { case Expose: if (ev.xexpose.count == 0 && xctx.visible) { } break; case SelectionNotify: if (ev.xselection.property == xctx.utf8) break; case VisibilityNotify: if (ev.xvisibility.state != VisibilityUnobscured) XRaiseWindow(xctx.dc->dpy, xctx.win); break; case ButtonPress: if (ev.xbutton.window == xctx.win) { x_handle_click(ev); } break; case KeyPress: if (settings.close_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_ks.sym && settings.close_ks.mask == ev.xkey.state) { if (displayed) { notification_close (g_queue_peek_head_link(displayed)-> data, 2); } } if (settings.history_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.history_ks.sym && settings.history_ks.mask == ev.xkey.state) { history_pop(); } if (settings.close_all_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.close_all_ks.sym && settings.close_all_ks.mask == ev.xkey.state) { move_all_to_history(); } if (settings.context_ks.str && XLookupKeysym(&ev.xkey, 0) == settings.context_ks.sym && settings.context_ks.mask == ev.xkey.state) { context_menu(); } break; } } return true; } /* * Check whether the user is currently idle. */ bool x_is_idle(void) { XScreenSaverQueryInfo(xctx.dc->dpy, DefaultRootWindow(xctx.dc->dpy), xctx.screensaver_info); if (settings.idle_threshold == 0) { return false; } return xctx.screensaver_info->idle / 1000 > settings.idle_threshold; } /* TODO move to x_mainloop_* */ /* * Handle incoming mouse click events */ void x_handle_click(XEvent ev) { if (ev.xbutton.button == Button3) { move_all_to_history(); return; } if (ev.xbutton.button == Button1) { int y = settings.separator_height; notification *n = NULL; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { n = iter->data; int text_h = MAX(xctx.font_h, settings.line_height) * n->line_count; int padding = 2 * settings.h_padding; int height = text_h + padding; if (ev.xbutton.y > y && ev.xbutton.y < y + height) break; else y += height + settings.separator_height; } if (n) notification_close(n, 2); } } /* * Return the window that currently has * the keyboard focus. */ Window get_focused_window(void) { Window focused = 0; Atom type; int format; unsigned long nitems, bytes_after; unsigned char *prop_return = NULL; Window root = RootWindow(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); Atom netactivewindow = XInternAtom(xctx.dc->dpy, "_NET_ACTIVE_WINDOW", false); XGetWindowProperty(xctx.dc->dpy, root, netactivewindow, 0L, sizeof(Window), false, XA_WINDOW, &type, &format, &nitems, &bytes_after, &prop_return); if (prop_return) { focused = *(Window *) prop_return; XFree(prop_return); } return focused; } #ifdef XINERAMA /* * Select the screen on which the Window * should be displayed. */ int select_screen(XineramaScreenInfo * info, int info_len) { if (settings.f_mode == FOLLOW_NONE) { return settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx.dc->dpy); } else { int x, y; assert(settings.f_mode == FOLLOW_MOUSE || settings.f_mode == FOLLOW_KEYBOARD); Window root = RootWindow(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); if (settings.f_mode == FOLLOW_MOUSE) { int dummy; unsigned int dummy_ui; Window dummy_win; XQueryPointer(xctx.dc->dpy, root, &dummy_win, &dummy_win, &x, &y, &dummy, &dummy, &dummy_ui); } if (settings.f_mode == FOLLOW_KEYBOARD) { Window focused = get_focused_window(); if (focused == 0) { /* something went wrong. Fallback to default */ return settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx. dc-> dpy); } Window child_return; XTranslateCoordinates(xctx.dc->dpy, focused, root, 0, 0, &x, &y, &child_return); } for (int i = 0; i < info_len; i++) { if (INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height)) { return i; } } /* something seems to be wrong. Fallback to default */ return settings.monitor >= 0 ? settings.monitor : XDefaultScreen(xctx.dc->dpy); } } #endif /* * Update the information about the monitor * geometry. */ void x_screen_info(screen_info * scr) { #ifdef XINERAMA int n; XineramaScreenInfo *info; if ((info = XineramaQueryScreens(xctx.dc->dpy, &n))) { int screen = select_screen(info, n); if (screen >= n) { /* invalid monitor, fallback to default */ screen = 0; } scr->dim.x = info[screen].x_org; scr->dim.y = info[screen].y_org; scr->dim.h = info[screen].height; scr->dim.w = info[screen].width; XFree(info); } else #endif { scr->dim.x = 0; scr->dim.y = 0; int screen; if (settings.monitor >= 0) screen = settings.monitor; else screen = DefaultScreen(xctx.dc->dpy); scr->dim.w = DisplayWidth(xctx.dc->dpy, screen); scr->dim.h = DisplayHeight(xctx.dc->dpy, screen); } } /* * Setup X11 stuff */ void x_setup(void) { /* initialize xctx.dc, font, keyboard, colors */ xctx.dc = initdc(); initfont(xctx.dc, settings.font); x_shortcut_init(&settings.close_ks); x_shortcut_init(&settings.close_all_ks); x_shortcut_init(&settings.history_ks); x_shortcut_init(&settings.context_ks); x_shortcut_grab(&settings.close_ks); x_shortcut_ungrab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_grab(&settings.history_ks); x_shortcut_ungrab(&settings.history_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_ungrab(&settings.context_ks); xctx.color_strings[ColFG][LOW] = settings.lowfgcolor; xctx.color_strings[ColFG][NORM] = settings.normfgcolor; xctx.color_strings[ColFG][CRIT] = settings.critfgcolor; xctx.color_strings[ColBG][LOW] = settings.lowbgcolor; xctx.color_strings[ColBG][NORM] = settings.normbgcolor; xctx.color_strings[ColBG][CRIT] = settings.critbgcolor; xctx.framec = getcolor(xctx.dc, settings.frame_color); if (settings.sep_color == CUSTOM) { xctx.sep_custom_col = getcolor(xctx.dc, settings.sep_custom_color_str); } else { xctx.sep_custom_col = 0; } /* parse and set xctx.geometry and monitor position */ if (settings.geom[0] == '-') { xctx.geometry.negative_width = true; settings.geom++; } else { xctx.geometry.negative_width = false; } xctx.geometry.mask = XParseGeometry(settings.geom, &xctx.geometry.x, &xctx.geometry.y, &xctx.geometry.w, &xctx.geometry.h); xctx.screensaver_info = XScreenSaverAllocInfo(); x_win_setup(); x_shortcut_grab(&settings.history_ks); } /* TODO comments and naming */ GSList *do_word_wrap(char *text, int max_width) { GSList *result = NULL; g_strstrip(text); if (!text || strlen(text) == 0) return 0; char *begin = text; char *end = text; while (true) { if (*end == '\0') { result = g_slist_append(result, g_strdup(begin)); break; } if (*end == '\n') { *end = ' '; result = g_slist_append(result, g_strndup(begin, end - begin)); begin = ++end; } if (settings.word_wrap && max_width > 0 && textnw(xctx.dc, begin, (end - begin) + 1) > max_width) { /* find previous space */ char *space = end; while (space > begin && !isspace(*space)) space--; if (space > begin) { end = space; } result = g_slist_append(result, g_strndup(begin, end - begin)); begin = ++end; } end++; } return result; } int calculate_x_offset(int line_width, int text_width) { int leftover = line_width - text_width; struct timeval t; float pos; /* If the text is wider than the frame, bouncing is enabled and word_wrap disabled */ if (line_width < text_width && settings.bounce_freq > 0.0001 && !settings.word_wrap) { gettimeofday(&t, NULL); pos = ((t.tv_sec % 100) * 1e6 + t.tv_usec) / (1e6 / settings.bounce_freq); return (1 + sinf(2 * 3.14159 * pos)) * leftover / 2; } switch (settings.align) { case left: return settings.frame_width + settings.h_padding; case center: return settings.frame_width + settings.h_padding + (leftover / 2); case right: return settings.frame_width + settings.h_padding + leftover; default: /* this can't happen */ return 0; } } unsigned long calculate_foreground_color(unsigned long source_color) { Colormap cmap = DefaultColormap(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); XColor color; color.pixel = source_color; XQueryColor(xctx.dc->dpy, cmap, &color); int c_delta = 10000; /* do we need to darken or brighten the colors? */ int darken = (color.red + color.green + color.blue) / 3 > 65535 / 2; if (darken) { if (color.red - c_delta < 0) color.red = 0; else color.red -= c_delta; if (color.green - c_delta < 0) color.green = 0; else color.green -= c_delta; if (color.blue - c_delta < 0) color.blue = 0; else color.blue -= c_delta; } else { if (color.red + c_delta > 65535) color.red = 65535; else color.red += c_delta; if (color.green + c_delta > 65535) color.green = 65535; else color.green += c_delta; if (color.blue + c_delta > 65535) color.green = 65535; else color.green += c_delta; } color.pixel = 0; XAllocColor(xctx.dc->dpy, cmap, &color); return color.pixel; } int calculate_width(void) { screen_info scr; x_screen_info(&scr); if (xctx.geometry.mask & WidthValue && xctx.geometry.w == 0) { /* dynamic width */ return 0; } else if (xctx.geometry.mask & WidthValue) { /* fixed width */ if (xctx.geometry.negative_width) { return scr.dim.w - xctx.geometry.w; } else { return xctx.geometry.w; } } else { /* across the screen */ return scr.dim.w; } } void move_and_map(int width, int height) { int x, y; screen_info scr; x_screen_info(&scr); /* calculate window position */ if (xctx.geometry.mask & XNegative) { x = (scr.dim.x + (scr.dim.w - width)) + xctx.geometry.x; } else { x = scr.dim.x + xctx.geometry.x; } if (xctx.geometry.mask & YNegative) { y = scr.dim.y + (scr.dim.h + xctx.geometry.y) - height; } else { y = scr.dim.y + xctx.geometry.y; } /* move and map window */ if (x != xctx.window_dim.x || y != xctx.window_dim.y || width != xctx.window_dim.w || height != xctx.window_dim.h) { XResizeWindow(xctx.dc->dpy, xctx.win, width, height); XMoveWindow(xctx.dc->dpy, xctx.win, x, y); xctx.window_dim.x = x; xctx.window_dim.y = y; xctx.window_dim.h = height; xctx.window_dim.w = width; } mapdc(xctx.dc, xctx.win, width, height); } GSList *generate_render_texts(int width) { GSList *render_texts = NULL; for (GList * iter = g_queue_peek_head_link(displayed); iter; iter = iter->next) { render_text *rt = g_malloc(sizeof(render_text)); notification *n = iter->data; notification_update_text_to_render(n); rt->colors = n->colors; char *text = n->text_to_render; rt->lines = do_word_wrap(text, width); render_texts = g_slist_append(render_texts, rt); } /* add (x more) */ if (settings.indicate_hidden && queue->length > 0) { if (xctx.geometry.h != 1) { render_text *rt = g_malloc(sizeof(render_text)); rt->colors = ((render_text *) g_slist_last(render_texts)->data)-> colors; rt->lines = g_slist_append(NULL, g_strdup_printf("%d more)", queue->length)); render_texts = g_slist_append(render_texts, rt); } else { GSList *last_lines = ((render_text *) g_slist_last(render_texts)->data)-> lines; GSList *last_line = g_slist_last(last_lines); char *old = last_line->data; char *new = g_strdup_printf("%s (%d more)", old, queue->length); free(old); last_line->data = new; } } return render_texts; } void free_render_text(void *data) { g_slist_free_full(((render_text *) data)->lines, g_free); } void free_render_texts(GSList * texts) { g_slist_free_full(texts, free_render_text); } void x_win_draw(void) { int outer_width = calculate_width(); screen_info scr; x_screen_info(&scr); settings.line_height = MAX(settings.line_height, xctx.font_h); int width; if (outer_width == 0) width = 0; else width = outer_width - (2 * settings.frame_width) - (2 * settings.h_padding); GSList *texts = generate_render_texts(width); int line_count = 0; for (GSList * iter = texts; iter; iter = iter->next) { render_text *tmp = iter->data; line_count += g_slist_length(tmp->lines); } /* if we have a dynamic width, calculate the actual width */ if (width == 0) { for (GSList * iter = texts; iter; iter = iter->next) { GSList *lines = ((render_text *) iter->data)->lines; for (GSList * iiter = lines; iiter; iiter = iiter->next) width = MAX(width, textw(xctx.dc, iiter->data)); } outer_width = width + (2 * settings.frame_width) + (2 * settings.h_padding); } /* resize xctx.dc to correct width */ int height = (line_count * settings.line_height) + displayed->length * 2 * settings.padding + ((settings.indicate_hidden && queue->length > 0 && xctx.geometry.h != 1) ? 2 * settings.padding : 0) + (settings.separator_height * (displayed->length - 1)) + (2 * settings.frame_width); resizedc(xctx.dc, outer_width, height); /* draw frame * this draws a big box in the frame color which get filled with * smaller boxes of the notification colors */ xctx.dc->y = 0; xctx.dc->x = 0; if (settings.frame_width > 0) { drawrect(xctx.dc, 0, 0, outer_width, height, true, xctx.framec); } xctx.dc->y = settings.frame_width; xctx.dc->x = settings.frame_width; for (GSList * iter = texts; iter; iter = iter->next) { render_text *cur = iter->data; ColorSet *colors = cur->colors; int line_count = 0; bool first_line = true; for (GSList * iiter = cur->lines; iiter; iiter = iiter->next) { char *line = iiter->data; line_count++; int pad = 0; bool last_line = iiter->next == NULL; if (first_line && last_line) pad = 2 * settings.padding; else if (first_line || last_line) pad = settings.padding; xctx.dc->x = settings.frame_width; /* draw background */ drawrect(xctx.dc, 0, 0, width + (2 * settings.h_padding), pad + settings.line_height, true, colors->BG); /* draw text */ xctx.dc->x = calculate_x_offset(width, textw(xctx.dc, line)); xctx.dc->y += ((settings.line_height - xctx.font_h) / 2); xctx.dc->y += first_line ? settings.padding : 0; drawtextn(xctx.dc, line, strlen(line), colors); xctx.dc->y += settings.line_height - ((settings.line_height - xctx.font_h) / 2); xctx.dc->y += last_line ? settings.padding : 0; first_line = false; } /* draw separator */ if (settings.separator_height > 0 && iter->next) { xctx.dc->x = settings.frame_width; double color; if (settings.sep_color == AUTO) color = calculate_foreground_color(colors->BG); else if (settings.sep_color == FOREGROUND) color = colors->FG; else if (settings.sep_color == FRAME) color = xctx.framec; else { /* CUSTOM */ color = xctx.sep_custom_col; } drawrect(xctx.dc, 0, 0, width + (2 * settings.h_padding), settings.separator_height, true, color); xctx.dc->y += settings.separator_height; } } move_and_map(outer_width, height); free_render_texts(texts); } /* * Setup the window */ void x_win_setup(void) { Window root; XSetWindowAttributes wa; xctx.window_dim.x = 0; xctx.window_dim.y = 0; xctx.window_dim.w = 0; xctx.window_dim.h = 0; root = RootWindow(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); xctx.utf8 = XInternAtom(xctx.dc->dpy, "UTF8_STRING", false); xctx.font_h = xctx.dc->font.height + FONT_HEIGHT_BORDER; wa.override_redirect = true; wa.background_pixmap = ParentRelative; wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask; screen_info scr; x_screen_info(&scr); xctx.win = XCreateWindow(xctx.dc->dpy, root, scr.dim.x, scr.dim.y, scr.dim.w, xctx.font_h, 0, DefaultDepth(xctx.dc->dpy, DefaultScreen(xctx.dc-> dpy)), CopyFromParent, DefaultVisual(xctx.dc->dpy, DefaultScreen(xctx.dc-> dpy)), CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); settings.transparency = settings.transparency > 100 ? 100 : settings.transparency; setopacity(xctx.dc, xctx.win, (unsigned long)((100 - settings.transparency) * (0xffffffff / 100))); } /* * Show the window and grab shortcuts. */ void x_win_show(void) { /* window is already mapped or there's nothing to show */ if (xctx.visible || g_queue_is_empty(displayed)) { return; } x_shortcut_grab(&settings.close_ks); x_shortcut_grab(&settings.close_all_ks); x_shortcut_grab(&settings.context_ks); x_shortcut_setup_error_handler(); XGrabButton(xctx.dc->dpy, AnyButton, AnyModifier, xctx.win, false, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); if (x_shortcut_tear_down_error_handler()) { fprintf(stderr, "Unable to grab mouse button(s)\n"); } XMapRaised(xctx.dc->dpy, xctx.win); xctx.visible = true; } /* * Hide the window and ungrab unused keyboard_shortcuts */ void x_win_hide() { x_shortcut_ungrab(&settings.close_ks); x_shortcut_ungrab(&settings.close_all_ks); x_shortcut_ungrab(&settings.context_ks); XUngrabButton(xctx.dc->dpy, AnyButton, AnyModifier, xctx.win); XUnmapWindow(xctx.dc->dpy, xctx.win); XFlush(xctx.dc->dpy); xctx.visible = false; } /* * Parse a string into a modifier mask. */ KeySym x_shortcut_string_to_mask(const char *str) { if (!strcmp(str, "ctrl")) { return ControlMask; } else if (!strcmp(str, "mod4")) { return Mod4Mask; } else if (!strcmp(str, "mod3")) { return Mod3Mask; } else if (!strcmp(str, "mod2")) { return Mod2Mask; } else if (!strcmp(str, "mod1")) { return Mod1Mask; } else if (!strcmp(str, "shift")) { return ShiftMask; } else { fprintf(stderr, "Warning: Unknown Modifier: %s\n", str); return 0; } } /* * Error handler for grabbing mouse and keyboard errors. */ static int GrabXErrorHandler(Display * display, XErrorEvent * e) { dunst_grab_errored = true; char err_buf[BUFSIZ]; XGetErrorText(display, e->error_code, err_buf, BUFSIZ); fputs(err_buf, stderr); fputs("\n", stderr); if (e->error_code != BadAccess) { exit(EXIT_FAILURE); } return 0; } /* * Setup the Error handler. */ static void x_shortcut_setup_error_handler(void) { dunst_grab_errored = false; XFlush(xctx.dc->dpy); XSetErrorHandler(GrabXErrorHandler); } /* * Tear down the Error handler. */ static int x_shortcut_tear_down_error_handler(void) { XFlush(xctx.dc->dpy); XSync(xctx.dc->dpy, false); XSetErrorHandler(NULL); return dunst_grab_errored; } /* * Grab the given keyboard shortcut. */ int x_shortcut_grab(keyboard_shortcut * ks) { if (!ks->is_valid) return 1; Window root; root = RootWindow(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); x_shortcut_setup_error_handler(); if (ks->is_valid) XGrabKey(xctx.dc->dpy, ks->code, ks->mask, root, true, GrabModeAsync, GrabModeAsync); if (x_shortcut_tear_down_error_handler()) { fprintf(stderr, "Unable to grab key \"%s\"\n", ks->str); ks->is_valid = false; return 1; } return 0; } /* * Ungrab the given keyboard shortcut. */ void x_shortcut_ungrab(keyboard_shortcut * ks) { Window root; root = RootWindow(xctx.dc->dpy, DefaultScreen(xctx.dc->dpy)); if (ks->is_valid) XUngrabKey(xctx.dc->dpy, ks->code, ks->mask, root); } /* * Initialize the keyboard shortcut. */ void x_shortcut_init(keyboard_shortcut * ks) { if (ks == NULL || ks->str == NULL) return; if (!strcmp(ks->str, "none") || (!strcmp(ks->str, ""))) { ks->is_valid = false; return; } char *str = g_strdup(ks->str); char *str_begin = str; if (str == NULL) die("Unable to allocate memory", EXIT_FAILURE); while (strstr(str, "+")) { char *mod = str; while (*str != '+') str++; *str = '\0'; str++; g_strchomp(mod); ks->mask = ks->mask | x_shortcut_string_to_mask(mod); } g_strstrip(str); ks->sym = XStringToKeysym(str); /* find matching keycode for ks->sym */ int min_keysym, max_keysym; XDisplayKeycodes(xctx.dc->dpy, &min_keysym, &max_keysym); ks->code = NoSymbol; for (int i = min_keysym; i <= max_keysym; i++) { if (XkbKeycodeToKeysym(xctx.dc->dpy, i, 0, 0) == ks->sym || XkbKeycodeToKeysym(xctx.dc->dpy, i, 0, 1) == ks->sym) { ks->code = i; break; } } if (ks->sym == NoSymbol || ks->code == NoSymbol) { fprintf(stderr, "Warning: Unknown keyboard shortcut: %s\n", ks->str); ks->is_valid = false; } else { ks->is_valid = true; } free(str_begin); } /* vim: set ts=8 sw=8 tw=0: */