
The xctx color field is a full duplicate of the settings logic. Only logic included in xctx color fields, are the frame colors, which fall back to the global frame setting. So, this required to handle it directly in settings.c
856 lines
27 KiB
C
856 lines
27 KiB
C
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
|
|
#include "x.h"
|
|
|
|
#include <assert.h>
|
|
#include <cairo.h>
|
|
#include <cairo-xlib.h>
|
|
#include <glib-object.h>
|
|
#include <locale.h>
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <X11/extensions/shape.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/X.h>
|
|
#include <X11/XKBlib.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
|
|
#include "../dbus.h"
|
|
#include "../draw.h"
|
|
#include "../dunst.h"
|
|
#include "../log.h"
|
|
#include "../markup.h"
|
|
#include "../menu.h"
|
|
#include "../notification.h"
|
|
#include "../queues.h"
|
|
#include "../settings.h"
|
|
#include "../utils.h"
|
|
|
|
#include "screen.h"
|
|
|
|
#define WIDTH 400
|
|
#define HEIGHT 400
|
|
|
|
struct window_x11 {
|
|
Window xwin;
|
|
cairo_surface_t *root_surface;
|
|
cairo_t *c_ctx;
|
|
GSource *esrc;
|
|
int cur_screen;
|
|
bool visible;
|
|
struct dimensions dim;
|
|
};
|
|
|
|
struct x11_source {
|
|
GSource source;
|
|
struct window_x11 *win;
|
|
};
|
|
|
|
struct x_context xctx;
|
|
bool dunst_grab_errored = false;
|
|
|
|
static bool fullscreen_last = false;
|
|
|
|
static void x_shortcut_init(struct keyboard_shortcut *ks);
|
|
static int x_shortcut_grab(struct keyboard_shortcut *ks);
|
|
static void x_shortcut_ungrab(struct keyboard_shortcut *ks);
|
|
/* FIXME refactor setup teardown handlers into one setup and one teardown */
|
|
static void x_shortcut_setup_error_handler(void);
|
|
static int x_shortcut_tear_down_error_handler(void);
|
|
static void setopacity(Window win, unsigned long opacity);
|
|
static void x_handle_click(XEvent ev);
|
|
|
|
static void x_win_move(struct window_x11 *win, int x, int y, int width, int height)
|
|
{
|
|
/* move and resize */
|
|
if (x != win->dim.x || y != win->dim.y) {
|
|
XMoveWindow(xctx.dpy, win->xwin, x, y);
|
|
|
|
win->dim.x = x;
|
|
win->dim.y = y;
|
|
}
|
|
|
|
if (width != win->dim.w || height != win->dim.h) {
|
|
XResizeWindow(xctx.dpy, win->xwin, width, height);
|
|
|
|
win->dim.h = height;
|
|
win->dim.w = width;
|
|
}
|
|
}
|
|
|
|
static void x_win_round_corners(struct window_x11 *win, const int rad)
|
|
{
|
|
const int width = win->dim.w;
|
|
const int height = win->dim.h;
|
|
const int dia = 2 * rad;
|
|
const int degrees = 64; // the factor to convert degrees to XFillArc's angle param
|
|
|
|
Pixmap mask = XCreatePixmap(xctx.dpy, win->xwin, width, height, 1);
|
|
XGCValues xgcv;
|
|
|
|
GC shape_gc = XCreateGC(xctx.dpy, mask, 0, &xgcv);
|
|
|
|
XSetForeground(xctx.dpy, shape_gc, 0);
|
|
XFillRectangle(xctx.dpy,
|
|
mask,
|
|
shape_gc,
|
|
0,
|
|
0,
|
|
width,
|
|
height);
|
|
|
|
XSetForeground(xctx.dpy, shape_gc, 1);
|
|
|
|
/* To mark all pixels, which should get exposed, we
|
|
* use a circle for every corner and two overlapping rectangles */
|
|
unsigned const int centercoords[] = {
|
|
0, 0,
|
|
width - dia - 1, 0,
|
|
0, height - dia - 1,
|
|
width - dia - 1, height - dia - 1,
|
|
};
|
|
|
|
for (int i = 0; i < sizeof(centercoords)/sizeof(unsigned int); i = i+2) {
|
|
XFillArc(xctx.dpy,
|
|
mask,
|
|
shape_gc,
|
|
centercoords[i],
|
|
centercoords[i+1],
|
|
dia,
|
|
dia,
|
|
degrees * 0,
|
|
degrees * 360);
|
|
}
|
|
XFillRectangle(xctx.dpy,
|
|
mask,
|
|
shape_gc,
|
|
rad,
|
|
0,
|
|
width-dia,
|
|
height);
|
|
XFillRectangle(xctx.dpy,
|
|
mask,
|
|
shape_gc,
|
|
0,
|
|
rad,
|
|
width,
|
|
height-dia);
|
|
|
|
XShapeCombineMask(xctx.dpy, win->xwin, ShapeBounding, 0, 0, mask, ShapeSet);
|
|
|
|
XFreeGC(xctx.dpy, shape_gc);
|
|
XFreePixmap(xctx.dpy, mask);
|
|
|
|
XShapeSelectInput(xctx.dpy,
|
|
win->xwin, ShapeNotifyMask);
|
|
}
|
|
|
|
void x_display_surface(cairo_surface_t *srf, struct window_x11 *win, const struct dimensions *dim)
|
|
{
|
|
x_win_move(win, dim->x, dim->y, dim->w, dim->h);
|
|
cairo_xlib_surface_set_size(win->root_surface, dim->w, dim->h);
|
|
|
|
cairo_set_source_surface(win->c_ctx, srf, 0, 0);
|
|
cairo_paint(win->c_ctx);
|
|
cairo_show_page(win->c_ctx);
|
|
|
|
if (settings.corner_radius != 0)
|
|
x_win_round_corners(win, dim->corner_radius);
|
|
|
|
XFlush(xctx.dpy);
|
|
|
|
}
|
|
|
|
bool x_win_visible(struct window_x11 *win)
|
|
{
|
|
return win->visible;
|
|
}
|
|
|
|
cairo_t* x_win_get_context(struct window_x11 *win)
|
|
{
|
|
return win->c_ctx;
|
|
}
|
|
|
|
static void setopacity(Window win, unsigned long opacity)
|
|
{
|
|
Atom _NET_WM_WINDOW_OPACITY =
|
|
XInternAtom(xctx.dpy, "_NET_WM_WINDOW_OPACITY", false);
|
|
XChangeProperty(xctx.dpy,
|
|
win,
|
|
_NET_WM_WINDOW_OPACITY,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&opacity,
|
|
1L);
|
|
}
|
|
|
|
/*
|
|
* Returns the modifier which is NumLock.
|
|
*/
|
|
static KeySym x_numlock_mod(void)
|
|
{
|
|
static KeyCode nl = 0;
|
|
KeySym sym = 0;
|
|
XModifierKeymap *map = XGetModifierMapping(xctx.dpy);
|
|
|
|
if (!nl)
|
|
nl = XKeysymToKeycode(xctx.dpy, XStringToKeysym("Num_Lock"));
|
|
|
|
for (int mod = 0; mod < 8; mod++) {
|
|
for (int j = 0; j < map->max_keypermod; j++) {
|
|
if (map->modifiermap[mod*map->max_keypermod+j] == nl) {
|
|
/* In theory, one could use `1 << mod`, but this
|
|
* could count as 'using implementation details',
|
|
* so use this large switch. */
|
|
switch (mod) {
|
|
case ShiftMapIndex:
|
|
sym = ShiftMask;
|
|
goto end;
|
|
case LockMapIndex:
|
|
sym = LockMask;
|
|
goto end;
|
|
case ControlMapIndex:
|
|
sym = ControlMask;
|
|
goto end;
|
|
case Mod1MapIndex:
|
|
sym = Mod1Mask;
|
|
goto end;
|
|
case Mod2MapIndex:
|
|
sym = Mod2Mask;
|
|
goto end;
|
|
case Mod3MapIndex:
|
|
sym = Mod3Mask;
|
|
goto end;
|
|
case Mod4MapIndex:
|
|
sym = Mod4Mask;
|
|
goto end;
|
|
case Mod5MapIndex:
|
|
sym = Mod5Mask;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
end:
|
|
XFreeModifiermap(map);
|
|
return sym;
|
|
}
|
|
|
|
/*
|
|
* Helper function to use glib's mainloop mechanic
|
|
* with Xlib
|
|
*/
|
|
gboolean x_mainloop_fd_prepare(GSource *source, gint *timeout)
|
|
{
|
|
if (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.dpy) > 0;
|
|
}
|
|
|
|
/*
|
|
* Main Dispatcher for XEvents
|
|
*/
|
|
gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
|
|
{
|
|
struct window_x11 *win = ((struct x11_source*) source)->win;
|
|
|
|
bool fullscreen_now;
|
|
struct screen_info *scr;
|
|
XEvent ev;
|
|
unsigned int state;
|
|
while (XPending(xctx.dpy) > 0) {
|
|
XNextEvent(xctx.dpy, &ev);
|
|
|
|
switch (ev.type) {
|
|
case Expose:
|
|
LOG_D("XEvent: processing 'Expose'");
|
|
if (ev.xexpose.count == 0 && win->visible) {
|
|
draw();
|
|
}
|
|
break;
|
|
case ButtonRelease:
|
|
LOG_D("XEvent: processing 'ButtonRelease'");
|
|
if (ev.xbutton.window == win->xwin) {
|
|
x_handle_click(ev);
|
|
wake_up();
|
|
}
|
|
break;
|
|
case KeyPress:
|
|
LOG_D("XEvent: processing 'KeyPress'");
|
|
state = ev.xkey.state;
|
|
/* NumLock is also encoded in the state. Remove it. */
|
|
state &= ~x_numlock_mod();
|
|
if (settings.close_ks.str
|
|
&& XLookupKeysym(&ev.xkey,
|
|
0) == settings.close_ks.sym
|
|
&& settings.close_ks.mask == state) {
|
|
const GList *displayed = queues_get_displayed();
|
|
if (displayed && displayed->data) {
|
|
queues_notification_close(displayed->data, REASON_USER);
|
|
wake_up();
|
|
}
|
|
}
|
|
if (settings.history_ks.str
|
|
&& XLookupKeysym(&ev.xkey,
|
|
0) == settings.history_ks.sym
|
|
&& settings.history_ks.mask == state) {
|
|
queues_history_pop();
|
|
wake_up();
|
|
}
|
|
if (settings.close_all_ks.str
|
|
&& XLookupKeysym(&ev.xkey,
|
|
0) == settings.close_all_ks.sym
|
|
&& settings.close_all_ks.mask == state) {
|
|
queues_history_push_all();
|
|
wake_up();
|
|
}
|
|
if (settings.context_ks.str
|
|
&& XLookupKeysym(&ev.xkey,
|
|
0) == settings.context_ks.sym
|
|
&& settings.context_ks.mask == state) {
|
|
context_menu();
|
|
wake_up();
|
|
}
|
|
break;
|
|
case CreateNotify:
|
|
LOG_D("XEvent: processing 'CreateNotify'");
|
|
if (win->visible &&
|
|
ev.xcreatewindow.override_redirect == 0)
|
|
XRaiseWindow(xctx.dpy, win->xwin);
|
|
break;
|
|
case FocusIn:
|
|
case FocusOut:
|
|
case PropertyNotify:
|
|
LOG_D("XEvent: Checking for active screen changes");
|
|
fullscreen_now = have_fullscreen_window();
|
|
scr = get_active_screen();
|
|
|
|
if (fullscreen_now != fullscreen_last) {
|
|
fullscreen_last = fullscreen_now;
|
|
wake_up();
|
|
} else if ( settings.f_mode != FOLLOW_NONE
|
|
/* Ignore PropertyNotify, when we're still on the
|
|
* same screen. PropertyNotify is only necessary
|
|
* to detect a focus change to another screen
|
|
*/
|
|
&& win->visible
|
|
&& scr->id != win->cur_screen) {
|
|
draw();
|
|
win->cur_screen = scr->id;
|
|
}
|
|
break;
|
|
default:
|
|
screen_check_event(ev);
|
|
break;
|
|
}
|
|
}
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* Check whether the user is currently idle.
|
|
*/
|
|
bool x_is_idle(void)
|
|
{
|
|
XScreenSaverQueryInfo(xctx.dpy, DefaultRootWindow(xctx.dpy),
|
|
xctx.screensaver_info);
|
|
if (settings.idle_threshold == 0) {
|
|
return false;
|
|
}
|
|
return xctx.screensaver_info->idle > settings.idle_threshold / 1000;
|
|
}
|
|
|
|
/* TODO move to x_mainloop_* */
|
|
/*
|
|
* Handle incoming mouse click events
|
|
*/
|
|
static void x_handle_click(XEvent ev)
|
|
{
|
|
enum mouse_action act;
|
|
|
|
switch (ev.xbutton.button) {
|
|
case Button1:
|
|
act = settings.mouse_left_click;
|
|
break;
|
|
case Button2:
|
|
act = settings.mouse_middle_click;
|
|
break;
|
|
case Button3:
|
|
act = settings.mouse_right_click;
|
|
break;
|
|
default:
|
|
LOG_W("Unsupported mouse button: '%d'", ev.xbutton.button);
|
|
return;
|
|
}
|
|
|
|
if (act == MOUSE_CLOSE_ALL) {
|
|
queues_history_push_all();
|
|
|
|
return;
|
|
}
|
|
|
|
if (act == MOUSE_DO_ACTION || act == MOUSE_CLOSE_CURRENT) {
|
|
int y = settings.separator_height;
|
|
struct notification *n = NULL;
|
|
int first = true;
|
|
for (const GList *iter = queues_get_displayed(); iter;
|
|
iter = iter->next) {
|
|
n = iter->data;
|
|
if (ev.xbutton.y > y && ev.xbutton.y < y + n->displayed_height)
|
|
break;
|
|
|
|
y += n->displayed_height + settings.separator_height;
|
|
if (first)
|
|
y += settings.frame_width;
|
|
}
|
|
|
|
if (n) {
|
|
if (act == MOUSE_CLOSE_CURRENT)
|
|
queues_notification_close(n, REASON_USER);
|
|
else
|
|
notification_do_action(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
void x_free(void)
|
|
{
|
|
if (xctx.screensaver_info)
|
|
XFree(xctx.screensaver_info);
|
|
|
|
if (xctx.dpy)
|
|
XCloseDisplay(xctx.dpy);
|
|
}
|
|
|
|
/*
|
|
* Setup X11 stuff
|
|
*/
|
|
void x_setup(void)
|
|
{
|
|
|
|
/* initialize xctx.dc, font, keyboard, colors */
|
|
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
|
|
LOG_W("No locale support");
|
|
if (!(xctx.dpy = XOpenDisplay(NULL))) {
|
|
DIE("Cannot open X11 display.");
|
|
}
|
|
|
|
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.screensaver_info = XScreenSaverAllocInfo();
|
|
|
|
init_screens();
|
|
x_shortcut_grab(&settings.history_ks);
|
|
}
|
|
|
|
struct geometry x_parse_geometry(const char *geom_str)
|
|
{
|
|
assert(geom_str);
|
|
|
|
if (geom_str[0] == '-') {
|
|
settings.geometry.negative_width = true;
|
|
geom_str++;
|
|
} else {
|
|
settings.geometry.negative_width = false;
|
|
}
|
|
|
|
struct geometry geometry = { 0 };
|
|
|
|
int mask = XParseGeometry(geom_str,
|
|
&geometry.x, &geometry.y,
|
|
&geometry.w, &geometry.h);
|
|
geometry.width_set = mask & WidthValue;
|
|
geometry.negative_x = mask & XNegative;
|
|
geometry.negative_y = mask & YNegative;
|
|
|
|
return geometry;
|
|
}
|
|
|
|
static void x_set_wm(Window win)
|
|
{
|
|
|
|
Atom data[2];
|
|
|
|
/* set window title */
|
|
char *title = settings.title != NULL ? settings.title : "Dunst";
|
|
Atom _net_wm_title =
|
|
XInternAtom(xctx.dpy, "_NET_WM_NAME", false);
|
|
|
|
XStoreName(xctx.dpy, win, title);
|
|
XChangeProperty(xctx.dpy,
|
|
win,
|
|
_net_wm_title,
|
|
XInternAtom(xctx.dpy, "UTF8_STRING", false),
|
|
8,
|
|
PropModeReplace,
|
|
(unsigned char *)title,
|
|
strlen(title));
|
|
|
|
/* set window class */
|
|
char *class = settings.class != NULL ? settings.class : "Dunst";
|
|
XClassHint classhint = { class, "Dunst" };
|
|
|
|
XSetClassHint(xctx.dpy, win, &classhint);
|
|
|
|
/* set window type */
|
|
Atom net_wm_window_type =
|
|
XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE", false);
|
|
|
|
data[0] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", false);
|
|
data[1] = XInternAtom(xctx.dpy, "_NET_WM_WINDOW_TYPE_UTILITY", false);
|
|
|
|
XChangeProperty(xctx.dpy,
|
|
win,
|
|
net_wm_window_type,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)data,
|
|
2L);
|
|
|
|
/* set state above */
|
|
Atom net_wm_state =
|
|
XInternAtom(xctx.dpy, "_NET_WM_STATE", false);
|
|
|
|
data[0] = XInternAtom(xctx.dpy, "_NET_WM_STATE_ABOVE", false);
|
|
|
|
XChangeProperty(xctx.dpy, win, net_wm_state, XA_ATOM, 32,
|
|
PropModeReplace, (unsigned char *) data, 1L);
|
|
}
|
|
|
|
GSource* x_win_reg_source(struct window_x11 *win)
|
|
{
|
|
// Static is necessary here because glib keeps the pointer and we need
|
|
// to keep the reference alive.
|
|
static GSourceFuncs xsrc_fn = {
|
|
x_mainloop_fd_prepare,
|
|
x_mainloop_fd_check,
|
|
x_mainloop_fd_dispatch,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
struct x11_source *xsrc = (struct x11_source*) g_source_new(&xsrc_fn,
|
|
sizeof(struct x11_source));
|
|
|
|
xsrc->win = win;
|
|
|
|
g_source_add_unix_fd((GSource*) xsrc, xctx.dpy->fd, G_IO_IN | G_IO_HUP | G_IO_ERR);
|
|
|
|
g_source_attach((GSource*) xsrc, NULL);
|
|
|
|
return (GSource*)xsrc;
|
|
}
|
|
|
|
/*
|
|
* Setup the window
|
|
*/
|
|
struct window_x11 *x_win_create(void)
|
|
{
|
|
struct window_x11 *win = g_malloc0(sizeof(struct window_x11));
|
|
|
|
Window root;
|
|
XSetWindowAttributes wa;
|
|
|
|
root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
|
|
|
|
wa.override_redirect = true;
|
|
wa.background_pixmap = ParentRelative;
|
|
wa.event_mask =
|
|
ExposureMask | KeyPressMask | VisibilityChangeMask |
|
|
ButtonReleaseMask | FocusChangeMask| StructureNotifyMask;
|
|
|
|
struct screen_info *scr = get_active_screen();
|
|
win->xwin = XCreateWindow(xctx.dpy,
|
|
root,
|
|
scr->x,
|
|
scr->y,
|
|
scr->w,
|
|
1,
|
|
0,
|
|
DefaultDepth(xctx.dpy, DefaultScreen(xctx.dpy)),
|
|
CopyFromParent,
|
|
DefaultVisual(xctx.dpy, DefaultScreen(xctx.dpy)),
|
|
CWOverrideRedirect | CWBackPixmap | CWEventMask,
|
|
&wa);
|
|
|
|
x_set_wm(win->xwin);
|
|
settings.transparency =
|
|
settings.transparency > 100 ? 100 : settings.transparency;
|
|
setopacity(win->xwin,
|
|
(unsigned long)((100 - settings.transparency) *
|
|
(0xffffffff / 100)));
|
|
|
|
win->root_surface = cairo_xlib_surface_create(xctx.dpy, win->xwin,
|
|
DefaultVisual(xctx.dpy, 0),
|
|
WIDTH, HEIGHT);
|
|
win->c_ctx = cairo_create(win->root_surface);
|
|
|
|
win->esrc = x_win_reg_source(win);
|
|
|
|
long root_event_mask = SubstructureNotifyMask;
|
|
if (settings.f_mode != FOLLOW_NONE) {
|
|
root_event_mask |= FocusChangeMask | PropertyChangeMask;
|
|
}
|
|
XSelectInput(xctx.dpy, root, root_event_mask);
|
|
|
|
return win;
|
|
}
|
|
|
|
void x_win_destroy(struct window_x11 *win)
|
|
{
|
|
g_source_destroy(win->esrc);
|
|
g_source_unref(win->esrc);
|
|
|
|
cairo_destroy(win->c_ctx);
|
|
cairo_surface_destroy(win->root_surface);
|
|
XDestroyWindow(xctx.dpy, win->xwin);
|
|
|
|
g_free(win);
|
|
}
|
|
|
|
/*
|
|
* Show the window and grab shortcuts.
|
|
*/
|
|
void x_win_show(struct window_x11 *win)
|
|
{
|
|
/* window is already mapped or there's nothing to show */
|
|
if (win->visible)
|
|
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.dpy,
|
|
AnyButton,
|
|
AnyModifier,
|
|
win->xwin,
|
|
false,
|
|
(ButtonPressMask|ButtonReleaseMask),
|
|
GrabModeAsync,
|
|
GrabModeSync,
|
|
None,
|
|
None);
|
|
if (x_shortcut_tear_down_error_handler()) {
|
|
LOG_W("Unable to grab mouse button(s).");
|
|
}
|
|
|
|
XMapRaised(xctx.dpy, win->xwin);
|
|
win->visible = true;
|
|
}
|
|
|
|
/*
|
|
* Hide the window and ungrab unused keyboard_shortcuts
|
|
*/
|
|
void x_win_hide(struct window_x11 *win)
|
|
{
|
|
if (!win->visible)
|
|
return;
|
|
|
|
x_shortcut_ungrab(&settings.close_ks);
|
|
x_shortcut_ungrab(&settings.close_all_ks);
|
|
x_shortcut_ungrab(&settings.context_ks);
|
|
|
|
XUngrabButton(xctx.dpy, AnyButton, AnyModifier, win->xwin);
|
|
XUnmapWindow(xctx.dpy, win->xwin);
|
|
XFlush(xctx.dpy);
|
|
win->visible = false;
|
|
}
|
|
|
|
/*
|
|
* Parse a string into a modifier mask.
|
|
*/
|
|
KeySym x_shortcut_string_to_mask(const char *str)
|
|
{
|
|
if (STR_EQ(str, "ctrl")) {
|
|
return ControlMask;
|
|
} else if (STR_EQ(str, "mod4")) {
|
|
return Mod4Mask;
|
|
} else if (STR_EQ(str, "mod3")) {
|
|
return Mod3Mask;
|
|
} else if (STR_EQ(str, "mod2")) {
|
|
return Mod2Mask;
|
|
} else if (STR_EQ(str, "mod1")) {
|
|
return Mod1Mask;
|
|
} else if (STR_EQ(str, "shift")) {
|
|
return ShiftMask;
|
|
} else {
|
|
LOG_W("Unknown Modifier: '%s'", 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);
|
|
|
|
if (e->error_code != BadAccess) {
|
|
DIE("%s", err_buf);
|
|
} else {
|
|
LOG_W("%s", err_buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Setup the Error handler.
|
|
*/
|
|
static void x_shortcut_setup_error_handler(void)
|
|
{
|
|
dunst_grab_errored = false;
|
|
|
|
XFlush(xctx.dpy);
|
|
XSetErrorHandler(GrabXErrorHandler);
|
|
}
|
|
|
|
/*
|
|
* Tear down the Error handler.
|
|
*/
|
|
static int x_shortcut_tear_down_error_handler(void)
|
|
{
|
|
XFlush(xctx.dpy);
|
|
XSync(xctx.dpy, false);
|
|
XSetErrorHandler(NULL);
|
|
return dunst_grab_errored;
|
|
}
|
|
|
|
/*
|
|
* Grab the given keyboard shortcut.
|
|
*/
|
|
static int x_shortcut_grab(struct keyboard_shortcut *ks)
|
|
{
|
|
if (!ks->is_valid)
|
|
return 1;
|
|
Window root;
|
|
root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
|
|
|
|
x_shortcut_setup_error_handler();
|
|
|
|
if (ks->is_valid) {
|
|
XGrabKey(xctx.dpy,
|
|
ks->code,
|
|
ks->mask,
|
|
root,
|
|
true,
|
|
GrabModeAsync,
|
|
GrabModeAsync);
|
|
XGrabKey(xctx.dpy,
|
|
ks->code,
|
|
ks->mask | x_numlock_mod(),
|
|
root,
|
|
true,
|
|
GrabModeAsync,
|
|
GrabModeAsync);
|
|
}
|
|
|
|
if (x_shortcut_tear_down_error_handler()) {
|
|
LOG_W("Unable to grab key '%s'.", ks->str);
|
|
ks->is_valid = false;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Ungrab the given keyboard shortcut.
|
|
*/
|
|
static void x_shortcut_ungrab(struct keyboard_shortcut *ks)
|
|
{
|
|
Window root;
|
|
root = RootWindow(xctx.dpy, DefaultScreen(xctx.dpy));
|
|
if (ks->is_valid) {
|
|
XUngrabKey(xctx.dpy, ks->code, ks->mask, root);
|
|
XUngrabKey(xctx.dpy, ks->code, ks->mask | x_numlock_mod(), root);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize the keyboard shortcut.
|
|
*/
|
|
static void x_shortcut_init(struct keyboard_shortcut *ks)
|
|
{
|
|
if (!ks|| !ks->str)
|
|
return;
|
|
|
|
if (STR_EQ(ks->str, "none") || (STR_EQ(ks->str, ""))) {
|
|
ks->is_valid = false;
|
|
return;
|
|
}
|
|
|
|
char *str = g_strdup(ks->str);
|
|
char *str_begin = str;
|
|
|
|
while (strchr(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.dpy, &min_keysym, &max_keysym);
|
|
|
|
ks->code = NoSymbol;
|
|
|
|
for (int i = min_keysym; i <= max_keysym; i++) {
|
|
if (XkbKeycodeToKeysym(xctx.dpy, i, 0, 0) == ks->sym
|
|
|| XkbKeycodeToKeysym(xctx.dpy, i, 0, 1) == ks->sym) {
|
|
ks->code = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ks->sym == NoSymbol || ks->code == NoSymbol) {
|
|
LOG_W("Unknown keyboard shortcut: '%s'", ks->str);
|
|
ks->is_valid = false;
|
|
} else {
|
|
ks->is_valid = true;
|
|
}
|
|
|
|
g_free(str_begin);
|
|
}
|
|
|
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|