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) {
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue