[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Patch: keymasks



Hi,

since I'm a heavy emacs user (sounds like taking drugs...) I want as
many keybindings as possible. But herbstluftwm is stealing so many of
them for managing my windows, which is kind of useless, since my emacs
is always fullscreen on a single tag.

Therefore there is no reason why herbstluftwm should bind most of the
keys, when Emacs is focused. I wrote a patch, that does exactly that.
Each client has a keymask (which is matched against the keybindings as a
regular expression): All matching keybindings are enabled, all others
are disabled. Keymasks can be applied by client rules, so

    hc rule class~'Emacs' keymask="Mod4.[1-5qweras]"

applies the keymask, that only enables monitor and tag switching
bindings when Emacs is focused. When refocusing only the wrongly
disabled or enabled keybindings are regrabbed. So it should be kind of
fast. When not using keybindings no performance impact should be measurable.

chris
From 6e64d3a8e4276914de20e22de5e69fee9e4e27bf Mon Sep 17 00:00:00 2001
From: Christian Dietrich <stettberger _at_ dokucode _dot_ de>
Date: Sun, 9 Feb 2014 17:29:14 +0100
Subject: [PATCH] Adding keymask feature to disable keybindings temporarily

---
 NEWS                 |  5 +++++
 doc/herbstluftwm.txt |  9 +++++++-
 src/clientlist.c     | 10 ++++++++-
 src/clientlist.h     |  2 +-
 src/key.c            | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/key.h            |  5 +++--
 src/rules.c          | 11 ++++++++++
 src/rules.h          |  2 +-
 8 files changed, 100 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index b4a1bf1..15ac92d 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,11 @@ Current git version
     * if swap_monitors_to_get_tag is set to 0, then focus the other monitor if
       the desired tag is shown on another monitor instead of doing nothing
     * new split mode: auto
+    * For each client a keymask can be given. A keymask is a regular 
expression,
+      that is matched against the string representation of a keybinding. If it 
matches, 
+      the keybinding is enabled for this client, otherwise not. The default is 
an empty
+      keymask ("") that matches all keybindings, so no bindings are masked out 
by default.
+      A keymask is a client attribute, and can be set by a client rule.
 
 Release 0.5.3 on 2013-12-24
 ---------------------------
diff --git a/doc/herbstluftwm.txt b/doc/herbstluftwm.txt
index 4ff6b6d..5cbca5a 100644
--- a/doc/herbstluftwm.txt
+++ b/doc/herbstluftwm.txt
@@ -307,7 +307,7 @@ cycle_all [*--skip-invisible*] ['DIRECTION']::
 cycle_frame ['DIRECTION']::
     Cycles through all frames on the current tag. 'DIRECTION' = 1 means 
forward,
     'DIRECTION' = -1 means backward, 'DIRECTION' = 0 has no effect. 'DIRECTION'
-    defaults to 1. 
+    defaults to 1.
 
 cycle_layout ['DELTA' ['LAYOUTS' ...]]::
     Cycles the layout algorithm in the current frame by 'DELTA'. 'DELTA'
@@ -1131,6 +1131,13 @@ Each 'CONSEQUENCE' consists of a 'NAME'='VALUE' pair. 
Valid 'NAMES' are:
     times, which will cause a hook to be emitted for each occurrence of a hook
     consequence.
 
++keymask::
+    Sets the keymask for an client. A keymask is an regular expression that is
+    matched against the string represenation (see list_keybinds). If it matches
+    the keybinding is active when this client is focused, otherwise it is
+    disabled. The default keymask is an empty string (""), which does not
+    disable any keybinding.
+
 A rule's behaviour can be configured by some special 'FLAGS':
 
     * +not+: negates the next 'CONDITION'.
diff --git a/src/clientlist.c b/src/clientlist.c
index 23e7881..78c7f8b 100644
--- a/src/clientlist.c
+++ b/src/clientlist.c
@@ -15,6 +15,7 @@
 #include "rules.h"
 #include "ipc-protocol.h"
 #include "object.h"
+#include "key.h"
 // system
 #include "glib-backports.h"
 #include <assert.h>
@@ -238,6 +239,9 @@ HSClient* manage_client(Window win) {
         client->tag = find_tag(changes.tag_name->str);
     }
 
+    // Reuse the keymask string
+    client->keymask = changes.keymask;
+
     if (!changes.manage) {
         client_changes_free_members(&changes);
         client_destroy(client);
@@ -277,6 +281,7 @@ HSClient* manage_client(Window win) {
     HSAttribute attributes[] = {
         ATTRIBUTE_STRING(   "winid",        client->window_str,     
ATTR_READ_ONLY),
         ATTRIBUTE_STRING(   "title",        client->title,          
ATTR_READ_ONLY),
+        ATTRIBUTE_STRING(   "keymask",      client->keymask,        
ATTR_READ_ONLY),
         ATTRIBUTE_CUSTOM(   "tag",          client_attr_tag,        
ATTR_READ_ONLY),
         ATTRIBUTE_INT(      "pid",          client->pid,            
ATTR_READ_ONLY),
         ATTRIBUTE_CUSTOM(   "class",        client_attr_class,      
ATTR_READ_ONLY),
@@ -392,6 +397,9 @@ void window_unfocus_last() {
         hook_emit_list("focus_changed", "0x0", "", NULL);
         ewmh_update_active_window(None);
         tag_update_each_focus_layer();
+
+        // Enable all keys in the root window
+        key_set_keymask(get_current_monitor()->tag, 0);
     }
     lastfocus = 0;
 }
@@ -435,6 +443,7 @@ void window_focus(Window window) {
     }
     tag_update_focus_layer(get_current_monitor()->tag);
     grab_client_buttons(get_client_from_window(window), true);
+    key_set_keymask(client->tag, client);
     client_set_urgent(client, false);
 }
 
@@ -1040,4 +1049,3 @@ bool client_sendevent(HSClient *client, Atom proto) {
     }
     return exists;
 }
-
diff --git a/src/clientlist.h b/src/clientlist.h
index 9f3947d..c678688 100644
--- a/src/clientlist.h
+++ b/src/clientlist.h
@@ -27,6 +27,7 @@ typedef struct HSClient {
     HSTag*      tag;
     Rectangle   float_size;     // floating size without the window border
     GString*    title;  // or also called window title; this is never NULL
+    GString*    keymask; // keymask applied to mask out keybindins
     bool        urgent;
     bool        fullscreen;
     bool        ewmhfullscreen; // ewmh fullscreen state
@@ -107,4 +108,3 @@ void window_update_border(Window window, unsigned long 
color);
 unsigned long get_window_border_color(HSClient* client);
 
 #endif
-
diff --git a/src/key.c b/src/key.c
index d6a0c22..1c25929 100644
--- a/src/key.c
+++ b/src/key.c
@@ -11,6 +11,7 @@
 
 #include <stdio.h>
 #include <string.h>
+#include <regex.h>
 #include "glib-backports.h"
 #include <X11/keysym.h>
 #include <X11/XKBlib.h>
@@ -238,8 +239,25 @@ void grab_keybind(KeyBinding* binding, void* 
useless_pointer) {
         XGrabKey(g_display, keycode, modifiers[i]|binding->modifiers, g_root,
                  True, GrabModeAsync, GrabModeAsync);
     }
+    // mark the keybinding as enabled
+    binding->enabled = true;
 }
 
+void ungrab_keybind(KeyBinding* binding, void* useless_pointer) {
+    unsigned int modifiers[] = { 0, LockMask, numlockmask, 
numlockmask|LockMask };
+    KeyCode keycode = XKeysymToKeycode(g_display, binding->keysym);
+    if (!keycode) {
+        // ignore unknown keysyms
+        return;
+    }
+    // grab key for each modifier that is ignored (capslock, numlock)
+    for (int i = 0; i < LENGTH(modifiers); i++) {
+        XUngrabKey(g_display, keycode, modifiers[i]|binding->modifiers, 
g_root);
+    }
+    binding->enabled = false;
+}
+
+
 // update the numlockmask
 // from dwm.c
 void update_numlockmask() {
@@ -361,3 +379,47 @@ void complete_against_modifiers(char* needle, char 
seperator,
     g_string_free(buf, true);
 }
 
+static void key_set_keymask_helper(KeyBinding* b, regex_t *keymask_regex) {
+    // add keybinding
+    bool enabled = true;
+    if (keymask_regex != NULL) {
+        GString* name = keybinding_to_g_string(b);
+        regmatch_t match;
+        int status = regexec(keymask_regex, name->str, 1, &match, 0);
+        // only accept it, if it matches the entire string
+        if (status == 0
+            && match.rm_so == 0
+            && match.rm_eo == strlen(name->str)) {
+            enabled = true;
+        } else {
+            // Keybinding did not match, therefore we disable it
+            enabled = false;
+        }
+        g_string_free(name, true);
+    }
+
+    if (enabled && !b->enabled) {
+        grab_keybind(b, NULL);
+    } else if(!enabled && b->enabled) {
+        ungrab_keybind(b, NULL);
+    }
+}
+
+void key_set_keymask(HSTag *tag, HSClient *client) {
+    regex_t     keymask_regex;
+    if (client && strlen(client->keymask->str) > 0) {
+        int status = regcomp(&keymask_regex, client->keymask->str, 
REG_EXTENDED);
+        if (status == 0) {
+            g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper,
+                           &keymask_regex);
+            return;
+        } else {
+            char buf[ERROR_STRING_BUF_SIZE];
+            regerror(status, &keymask_regex, buf, ERROR_STRING_BUF_SIZE);
+            HSDebug("keymask: Can not parse regex \"%s\" from keymask: %s",
+                    client->keymask->str, buf);
+        }
+    }
+    // Enable all keys again
+    g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper, 0);
+}
diff --git a/src/key.h b/src/key.h
index e99004d..a69d52f 100644
--- a/src/key.h
+++ b/src/key.h
@@ -9,6 +9,7 @@
 #include <stdbool.h>
 #include <X11/Xlib.h>
 #include "glib-backports.h"
+#include "clientlist.h"
 
 #define KEY_COMBI_SEPARATORS "+-"
 
@@ -17,6 +18,7 @@ typedef struct KeyBinding {
     unsigned int modifiers;
     int     cmd_argc; // number of arguments for command
     char**  cmd_argv; // arguments for command to call
+    bool    enabled;  // Is the keybinding already grabbed
 } KeyBinding;
 
 unsigned int modifiername2mask(const char* name);
@@ -41,11 +43,10 @@ void regrab_keys();
 void grab_keybind(KeyBinding* binding, void* useless_pointer);
 void update_numlockmask();
 unsigned int* get_numlockmask_ptr();
-
+void key_set_keymask(HSTag * tag, HSClient *client);
 void handle_key_press(XEvent* ev);
 
 void key_init();
 void key_destroy();
 
 #endif
-
diff --git a/src/rules.c b/src/rules.c
index f4da9fb..46defad 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -63,6 +63,7 @@ DECLARE_CONSEQUENCE(consequence_switchtag);
 DECLARE_CONSEQUENCE(consequence_ewmhrequests);
 DECLARE_CONSEQUENCE(consequence_ewmhnotify);
 DECLARE_CONSEQUENCE(consequence_hook);
+DECLARE_CONSEQUENCE(consequence_keymask);
 
 /// GLOBALS ///
 
@@ -91,6 +92,7 @@ static HSConsequenceType g_consequence_types[] = {
     { "ewmhrequests",   consequence_ewmhrequests    },
     { "ewmhnotify",     consequence_ewmhnotify      },
     { "hook",           consequence_hook            },
+    { "keymask",        consequence_keymask         },
 };
 
 static GQueue g_rules = G_QUEUE_INIT; // a list of HSRule* elements
@@ -559,6 +561,7 @@ void client_changes_init(HSClientChanges* changes, 
HSClient* client) {
     changes->switchtag = false;
     changes->manage = true;
     changes->fullscreen = ewmh_is_fullscreen_set(client->window);
+    changes->keymask = g_string_new("");
 }
 
 void client_changes_free_members(HSClientChanges* changes) {
@@ -821,3 +824,11 @@ void consequence_hook(HSConsequence* cons, HSClient* 
client,
     g_string_free(winid, true);
 }
 
+void consequence_keymask(HSConsequence* cons,
+                         HSClient* client, HSClientChanges* changes) {
+    if (changes->keymask) {
+        g_string_assign(changes->keymask, cons->value.str);
+    } else {
+        changes->keymask = g_string_new(cons->value.str);
+    }
+}
diff --git a/src/rules.h b/src/rules.h
index 5cdf739..07a6584 100644
--- a/src/rules.h
+++ b/src/rules.h
@@ -63,6 +63,7 @@ typedef struct {
     bool            manage; // whether we should manage it
     bool            fullscreen;
     bool            ewmhnotify; // whether to send ewmh-notifications
+    GString*        keymask; // Which keymask rule should be applied for this 
client
 } HSClientChanges;
 
 void rules_init();
@@ -83,4 +84,3 @@ int rule_print_all_command(int argc, char** argv, GString* 
output);
 void complete_against_rule_names(int argc, char** argv, int pos, GString* 
output);
 
 #endif
-
-- 
1.9.rc1

Attachment: signature.asc
Description: OpenPGP digital signature