mirror of https://github.com/adamdruppe/arsd.git
ketmars X11 global hotkey
This commit is contained in:
parent
9213c3f4ed
commit
3f56692370
195
simpledisplay.d
195
simpledisplay.d
|
@ -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) {
|
version(D_Ddoc) {
|
||||||
|
@ -3320,7 +3508,7 @@ void flushGui() {
|
||||||
interface CapableOfHandlingNativeEvent {
|
interface CapableOfHandlingNativeEvent {
|
||||||
NativeEventHandler getNativeEventHandler();
|
NativeEventHandler getNativeEventHandler();
|
||||||
|
|
||||||
private static CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
|
/*private*//*protected*/ static CapableOfHandlingNativeEvent[NativeWindowHandle] nativeHandleMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
version(X11)
|
version(X11)
|
||||||
|
@ -8057,6 +8245,11 @@ struct Visual
|
||||||
int XBell(Display*, int);
|
int XBell(Display*, int);
|
||||||
int XSync(Display*, bool);
|
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 {
|
struct XPoint {
|
||||||
short x;
|
short x;
|
||||||
short y;
|
short y;
|
||||||
|
|
Loading…
Reference in New Issue