ketmars X11 global hotkey

This commit is contained in:
Adam D. Ruppe 2017-03-16 00:19:40 -04:00
parent 9213c3f4ed
commit 3f56692370
1 changed files with 283 additions and 90 deletions

View File

@ -2224,6 +2224,194 @@ version(X11) {
}
}
}
/** Global hotkey handler. Simpledisplay will usually create one for you, but if you want to use subclassing
* instead of delegates, you can subclass this, and override `doHandle()` method. */
public class GlobalHotkey {
KeyEvent key;
void delegate () handler;
void doHandle () { if (handler !is null) handler(); } /// this will be called by hotkey manager
/// Create from initialzed KeyEvent object
this (KeyEvent akey, void delegate () ahandler=null) {
if (akey.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(akey.modifierState)) throw new Exception("invalid global hotkey");
key = akey;
handler = ahandler;
}
/// Create from emacs-like key name ("C-M-Y", etc.)
this (const(char)[] akey, void delegate () ahandler=null) {
key = KeyEvent.parse(akey);
if (key.key == 0 || !GlobalHotkeyManager.isGoodModifierMask(key.modifierState)) throw new Exception("invalid global hotkey");
handler = ahandler;
}
}
private extern(C) int XGrabErrorHandler (Display* dpy, XErrorEvent* evt) nothrow @nogc {
//conwriteln("failed to grab key");
GlobalHotkeyManager.ghfailed = true;
return 0;
}
/++
Global hotkey manager. It contains static methods to manage global hotkeys.
---
try {
GlobalHotkeyManager.register("M-H-A", delegate () { hideShowWindows(); });
} catch (Exception e) {
conwriteln("ERROR registering hotkey!");
}
---
The key strings are based on Emacs. In practical terms,
`M` means `alt` and `H` means the Windows logo key. `C`
is `ctrl`.
$(WARNING
This is X-specific right now. If you are on
Windows, try [registerHotKey] instead.
We will probably merge these into a single
interface later.
)
+/
public class GlobalHotkeyManager : CapableOfHandlingNativeEvent {
private static immutable uint[8] masklist = [ 0,
KeyOrButtonMask.LockMask,
KeyOrButtonMask.Mod2Mask,
KeyOrButtonMask.Mod3Mask,
KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask,
KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod3Mask,
KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask,
];
private __gshared GlobalHotkeyManager ghmanager;
private __gshared bool ghfailed = false;
private static bool isGoodModifierMask (uint modmask) pure nothrow @safe @nogc {
if (modmask == 0) return false;
if (modmask&(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask)) return false;
if (modmask&~(KeyOrButtonMask.Mod5Mask-1)) return false;
return true;
}
private static uint cleanupModifiers (uint modmask) pure nothrow @safe @nogc {
modmask &= ~(KeyOrButtonMask.LockMask|KeyOrButtonMask.Mod2Mask|KeyOrButtonMask.Mod3Mask); // remove caps, num, scroll
modmask &= (KeyOrButtonMask.Mod5Mask-1); // and other modifiers
return modmask;
}
private static uint keyEvent2KeyCode() (in auto ref KeyEvent ke) {
uint keycode = cast(uint)ke.key;
auto dpy = XDisplayConnection.get;
return XKeysymToKeycode(dpy, keycode);
}
private static ulong keyCode2Hash() (uint keycode, uint modstate) pure nothrow @safe @nogc { return ((cast(ulong)modstate)<<32)|keycode; }
private __gshared GlobalHotkey[ulong] globalHotkeyList;
NativeEventHandler getNativeEventHandler () {
return delegate int (XEvent e) {
if (e.type != EventType.KeyPress) return 1;
auto kev = cast(const(XKeyEvent)*)&e;
auto hash = keyCode2Hash(e.xkey.keycode, cleanupModifiers(e.xkey.state));
if (auto ghkp = hash in globalHotkeyList) {
try {
ghkp.doHandle();
} catch (Exception e) {
import core.stdc.stdio : stderr, fprintf;
stderr.fprintf("HOTKEY HANDLER EXCEPTION: %.*s", cast(uint)e.msg.length, e.msg.ptr);
}
}
return 1;
};
}
private this () {
auto dpy = XDisplayConnection.get;
auto root = RootWindow(dpy, DefaultScreen(dpy));
CapableOfHandlingNativeEvent.nativeHandleMapping[root] = this;
XSelectInput(dpy, root, EventMask.KeyPressMask);
}
/// Register new global hotkey with initialized `GlobalHotkey` object.
/// This function will throw if it failed to register hotkey (i.e. hotkey is invalid or already taken).
static void register (GlobalHotkey gh) {
if (gh is null) return;
if (gh.key.key == 0 || !isGoodModifierMask(gh.key.modifierState)) throw new Exception("invalid global hotkey");
auto dpy = XDisplayConnection.get;
immutable keycode = keyEvent2KeyCode(gh.key);
auto hash = keyCode2Hash(keycode, gh.key.modifierState);
if (hash in globalHotkeyList) throw new Exception("duplicate global hotkey");
if (ghmanager is null) ghmanager = new GlobalHotkeyManager();
XSync(dpy, 0/*False*/);
Window root = RootWindow(dpy, DefaultScreen(dpy));
XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
ghfailed = false;
foreach (immutable uint ormask; masklist[]) {
XGrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root, /*owner_events*/0/*False*/, GrabMode.GrabModeAsync, GrabMode.GrabModeAsync);
}
XSync(dpy, 0/*False*/);
XSetErrorHandler(savedErrorHandler);
if (ghfailed) {
savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, gh.key.modifierState|ormask, /*grab_window*/root);
XSync(dpy, 0/*False*/);
XSetErrorHandler(savedErrorHandler);
throw new Exception("cannot register global hotkey");
}
globalHotkeyList[hash] = gh;
}
/// Ditto
static void register (const(char)[] akey, void delegate () ahandler) {
register(new GlobalHotkey(akey, ahandler));
}
private static void removeByHash (ulong hash) {
if (auto ghp = hash in globalHotkeyList) {
auto dpy = XDisplayConnection.get;
immutable keycode = keyEvent2KeyCode(ghp.key);
Window root = RootWindow(dpy, DefaultScreen(dpy));
XSync(dpy, 0/*False*/);
XErrorHandler savedErrorHandler = XSetErrorHandler(&XGrabErrorHandler);
foreach (immutable uint ormask; masklist[]) XUngrabKey(dpy, keycode, ghp.key.modifierState|ormask, /*grab_window*/root);
XSync(dpy, 0/*False*/);
XSetErrorHandler(savedErrorHandler);
globalHotkeyList.remove(hash);
}
}
/// Register new global hotkey with previously used `GlobalHotkey` object.
/// It is safe to unregister unknown or invalid hotkey.
static void unregister (GlobalHotkey gh) {
//TODO: add second AA for faster search? prolly doesn't worth it.
if (gh is null) return;
foreach (const ref kv; globalHotkeyList.byKeyValue) {
if (kv.value is gh) {
removeByHash(kv.key);
return;
}
}
}
/// Ditto.
static void unregister (const(char)[] key) {
auto kev = KeyEvent.parse(key);
immutable keycode = keyEvent2KeyCode(kev);
removeByHash(keyCode2Hash(keycode, kev.modifierState));
}
}
}
version(D_Ddoc) {
@ -3320,7 +3508,7 @@ void flushGui() {
interface CapableOfHandlingNativeEvent {
NativeEventHandler getNativeEventHandler();
private static CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
/*private*//*protected*/ static CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
}
version(X11)
@ -8057,6 +8245,11 @@ struct Visual
int XBell(Display*, int);
int XSync(Display*, bool);
enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 }
int XGrabKey (Display* display, int keycode, uint modifiers, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode);
int XUngrabKey (Display* display, int keycode, uint modifiers, Window grab_window);
KeyCode XKeysymToKeycode (Display* display, KeySym keysym);
struct XPoint {
short x;
short y;