From 0a04bceb6f11f21bd2469a2f64bb46e1541c240a Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Thu, 21 Feb 2013 23:14:23 +0000 Subject: [PATCH] move x_* functions to x.c --- dunst.c | 1004 ------------------------------------------------------- dunst.h | 9 + x.c | 978 ++++++++++++++++++++++++++++++++++++++++++++++++++++- x.h | 28 ++ 4 files changed, 1014 insertions(+), 1005 deletions(-) diff --git a/dunst.c b/dunst.c index 4bd5256..e457832 100644 --- a/dunst.c +++ b/dunst.c @@ -41,12 +41,7 @@ // }}} // {{{ DEFINES -#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) #define LENGTH(X) (sizeof X / sizeof X[0]) -#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) -#define FONT_HEIGHT_BORDER 2 - -#define DEFFONT "Monospace-11" #ifndef VERSION #define VERSION "version info needed" @@ -75,7 +70,6 @@ bool pause_display = false; GMainLoop *mainloop = NULL; bool timer_active = false; -bool dunst_grab_errored = false; bool force_redraw = false; @@ -89,41 +83,8 @@ GSList *rules = NULL; // {{{ FUNCTION DEFINITIONS -/* window */ -void x_win_draw(void); -void x_win_hide(void); -void x_win_show(void); -void x_win_setup(void); - -/* shortcut */ -void x_shortcut_init(keyboard_shortcut *shortcut); -void x_shortcut_ungrab(keyboard_shortcut *ks); -int x_shortcut_grab(keyboard_shortcut *ks); -KeySym x_shortcut_string_to_mask(const char *str); -static void x_shortcut_setup_error_handler(void); -static int x_shortcut_tear_down_error_handler(void); - -/* X misc */ -void x_handle_click(XEvent ev); -void x_screen_info(screen_info *scr); -bool x_is_idle(void); -void x_setup(void); - -/* X mainloop */ -static gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout); -static gboolean x_mainloop_fd_check(GSource *source); -static gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data); /* misc funtions */ -void check_timeouts(void); -void history_pop(void); -void usage(int exit_status); -void move_all_to_history(void); -void print_version(void); -char *extract_urls(const char *str); -void context_menu(void); -void wake_up(void); -void pause_signal_handler(int sig); // }}} @@ -136,971 +97,6 @@ void pause_signal_handler(int sig); -// {{{ X -// {{{ X_MAINLOOP - - /* - * Helper function to use glib's mainloop mechanic - * with Xlib - */ -static gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout) -{ // {{{ - *timeout = -1; - return false; -} -// }}} - - - /* - * Helper function to use glib's mainloop mechanic - * with Xlib - */ -static gboolean x_mainloop_fd_check(GSource *source) -{ // {{{ - return XPending(xctx.dc->dpy) > 0; -} -// }}} - - - /* - * Main Dispatcher for XEvents - */ -static 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; -} -// }}} - -// }}} - -// {{{ X_MISC - - /* - * 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 */ -// {{{ X_RENDER - -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; -} -// }}} - - -char *generate_final_text(notification *n) -{ // {{{ - char *msg = g_strstrip(n->msg); - char *buf; - - /* print dup_count and msg*/ - if (n->dup_count > 0 && (n->actions || n->urls)) { - buf = g_strdup_printf("(%d%s%s) %s", - n->dup_count, - n->actions ? "A" : "", - n->urls ? "U" : "", - msg); - } else if (n->actions || n->urls) { - buf = g_strdup_printf("(%s%s) %s", - n->actions ? "A" : "", - n->urls ? "U" : "", - msg); - } else { - buf = g_strdup(msg); - } - - /* print age */ - int hours, minutes, seconds; - time_t t_delta = time(NULL) - n->timestamp; - - if (settings.show_age_threshold >= 0 && t_delta >= settings.show_age_threshold) { - hours = t_delta / 3600; - minutes = t_delta / 60 % 60; - seconds = t_delta % 60; - - char *new_buf; - if (hours > 0) { - new_buf = g_strdup_printf("%s (%dh %dm %ds old)", buf, hours, - minutes, seconds); - } else if (minutes > 0) { - new_buf = g_strdup_printf("%s (%dm %ds old)", buf, minutes, - seconds); - } else { - new_buf = g_strdup_printf("%s (%ds old)", buf, seconds); - } - - free(buf); - buf = new_buf; - } - - return buf; -} -// }}} - -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)); - - rt->colors = ((notification*)iter->data)->colors; - char *text = generate_final_text(iter->data); - rt->lines = do_word_wrap(text, width); - free(text); - 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); -} - - -// }}} - - -// {{{ X_WIN - -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; -} -// }}} - -// }}} - - -// {{{ X_SHORTCUT - - /* - * 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); -} -// }}} - -// }}} -// }}} // {{{ RUN diff --git a/dunst.h b/dunst.h index c80558d..9ea632a 100644 --- a/dunst.h +++ b/dunst.h @@ -36,4 +36,13 @@ extern DC *dc; gboolean run(void *data); void wake_up(void); +void check_timeouts(void); +void history_pop(void); +void usage(int exit_status); +void move_all_to_history(void); +void print_version(void); +char *extract_urls(const char *str); +void context_menu(void); +void wake_up(void); +void pause_signal_handler(int sig); /* vim: set ts=8 sw=8 tw=0: */ diff --git a/x.c b/x.c index 46a7668..f39ac70 100644 --- a/x.c +++ b/x.c @@ -29,7 +29,10 @@ 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 @@ -41,8 +44,16 @@ DEALINGS IN THE SOFTWARE. #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, @@ -290,4 +301,969 @@ int textw(DC * dc, const char *text) return textnw(dc, text, strlen(text)) + dc->font.height; } +// {{{ X +// {{{ X_MAINLOOP + + /* + * 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; +} +// }}} + +// }}} + +// {{{ X_MISC + + /* + * 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 */ +// {{{ X_RENDER + +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; +} +// }}} + + +char *generate_final_text(notification *n) +{ // {{{ + char *msg = g_strstrip(n->msg); + char *buf; + + /* print dup_count and msg*/ + if (n->dup_count > 0 && (n->actions || n->urls)) { + buf = g_strdup_printf("(%d%s%s) %s", + n->dup_count, + n->actions ? "A" : "", + n->urls ? "U" : "", + msg); + } else if (n->actions || n->urls) { + buf = g_strdup_printf("(%s%s) %s", + n->actions ? "A" : "", + n->urls ? "U" : "", + msg); + } else { + buf = g_strdup(msg); + } + + /* print age */ + int hours, minutes, seconds; + time_t t_delta = time(NULL) - n->timestamp; + + if (settings.show_age_threshold >= 0 && t_delta >= settings.show_age_threshold) { + hours = t_delta / 3600; + minutes = t_delta / 60 % 60; + seconds = t_delta % 60; + + char *new_buf; + if (hours > 0) { + new_buf = g_strdup_printf("%s (%dh %dm %ds old)", buf, hours, + minutes, seconds); + } else if (minutes > 0) { + new_buf = g_strdup_printf("%s (%dm %ds old)", buf, minutes, + seconds); + } else { + new_buf = g_strdup_printf("%s (%ds old)", buf, seconds); + } + + free(buf); + buf = new_buf; + } + + return buf; +} +// }}} + +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)); + + rt->colors = ((notification*)iter->data)->colors; + char *text = generate_final_text(iter->data); + rt->lines = do_word_wrap(text, width); + free(text); + 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); +} + + +// }}} + + +// {{{ X_WIN + +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; +} +// }}} + +// }}} + + +// {{{ X_SHORTCUT + + /* + * 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: */ diff --git a/x.h b/x.h index a2a6255..b253e70 100644 --- a/x.h +++ b/x.h @@ -46,6 +46,12 @@ DEALINGS IN THE SOFTWARE. #include + +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define FONT_HEIGHT_BORDER 2 +#define DEFFONT "Monospace-11" +#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) + typedef struct { int x, y, w, h; bool invert; @@ -131,5 +137,27 @@ void resizedc(DC * dc, unsigned int w, unsigned int h); int textnw(DC * dc, const char *text, size_t len); int textw(DC * dc, const char *text); +/* window */ +void x_win_draw(void); +void x_win_hide(void); +void x_win_show(void); +void x_win_setup(void); + +/* shortcut */ +void x_shortcut_init(keyboard_shortcut *shortcut); +void x_shortcut_ungrab(keyboard_shortcut *ks); +int x_shortcut_grab(keyboard_shortcut *ks); +KeySym x_shortcut_string_to_mask(const char *str); + +/* X misc */ +void x_handle_click(XEvent ev); +void x_screen_info(screen_info *scr); +bool x_is_idle(void); +void x_setup(void); + +gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data); +gboolean x_mainloop_fd_check(GSource *source); +gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout); + #endif /* vim: set ts=8 sw=8 tw=0: */