Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XLib event loop canceling #16

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 89 additions & 43 deletions hotkey_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@
//
// Written by Changkun Ou <changkun.de>

//go:build linux
// go:build linux
changkun marked this conversation as resolved.
Show resolved Hide resolved

#include <stdint.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdint.h>
// Needed for memset()
#include <string.h>
changkun marked this conversation as resolved.
Show resolved Hide resolved

extern void hotkeyDown(uintptr_t hkhandle);
extern void hotkeyUp(uintptr_t hkhandle);

int displayTest() {
Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
if (d == NULL) continue;
break;
}
if (d == NULL) {
return -1;
}
return 0;
Display *d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
if (d == NULL)
continue;
break;
}
if (d == NULL) {
return -1;
}
return 0;
}

// FIXME: handle bad access properly.
Expand All @@ -38,40 +40,84 @@ int displayTest() {
// pErr->minor_code );
// if( pErr->request_code == 33 ){ // 33 (X_GrabKey)
// if( pErr->error_code == BadAccess ){
// printf("ERROR: key combination already grabbed by another client.\n");
// return 0;
// printf("ERROR: key combination already grabbed by another
// client.\n"); return 0;
// }
// }
// return 0;
// }

Display *openDisplay() {
Display *d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
if (d == NULL)
continue;
break;
}
return d;
}

// Creates an invisible window, which can receive ClientMessage events. On
// hotkey cancel a ClientMessageEvent is generated on the window. The event is
// catched and the event loop terminates. x: 0 y: 0 w: 1 h: 1 border_width: 1
// depth: 0
// class: InputOnly (window will not be drawn)
// visual: default visual of display
// no attributes will be set (0, &attr)
Window createInvisWindow(Display *d) {
XSetWindowAttributes attr;
return XCreateWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, InputOnly,
DefaultVisual(d, 0), 0, &attr);
}

// Sends a custom ClientMessage of type (Atom) "go_hotkey_cancel_hotkey"
// Passed value 'True' of XInternAtom creates the Atom, if it does not exist yet
void sendCancel(Display *d, Window window) {
Atom atom = XInternAtom(d, "go_hotkey_cancel_hotkey", True);
data-niklas marked this conversation as resolved.
Show resolved Hide resolved
XClientMessageEvent clientEvent;
memset(&clientEvent, 0, sizeof(clientEvent));
clientEvent.type = ClientMessage;
clientEvent.send_event = True;
clientEvent.display = d;
clientEvent.window = window;
clientEvent.message_type = atom;
clientEvent.format = 8;

XEvent event;
event.type = ClientMessage;
event.xclient = clientEvent;
XSendEvent(d, window, False, 0, &event);
XFlush(d);
}

// Closes the connection and destroys the invisible 'cancel' window
void cleanupConnection(Display *d, Window w) {
XDestroyWindow(d, w);
XCloseDisplay(d);
}

// waitHotkey blocks until the hotkey is triggered.
// this function crashes the program if the hotkey already grabbed by others.
int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key) {
Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = XOpenDisplay(0);
if (d == NULL) continue;
break;
}
if (d == NULL) {
return -1;
}
int keycode = XKeysymToKeycode(d, key);
XGrabKey(d, keycode, mod, DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
XSelectInput(d, DefaultRootWindow(d), KeyPressMask);
XEvent ev;
while(1) {
XNextEvent(d, &ev);
switch(ev.type) {
case KeyPress:
hotkeyDown(hkhandle);
continue;
case KeyRelease:
hotkeyUp(hkhandle);
XUngrabKey(d, keycode, mod, DefaultRootWindow(d));
XCloseDisplay(d);
return 0;
}
}
}
int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key, Display *d,
Window w) {
int keycode = XKeysymToKeycode(d, key);
XGrabKey(d, keycode, mod, DefaultRootWindow(d), False, GrabModeAsync,
GrabModeAsync);
XSelectInput(d, DefaultRootWindow(d), KeyPressMask);
XEvent ev;
while (1) {
XNextEvent(d, &ev);
switch (ev.type) {
case KeyPress:
hotkeyDown(hkhandle);
continue;
case KeyRelease:
hotkeyUp(hkhandle);
XUngrabKey(d, keycode, mod, DefaultRootWindow(d));
return 0;
case ClientMessage:
return 0;
}
}
}
17 changes: 14 additions & 3 deletions hotkey_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ package hotkey
#cgo LDFLAGS: -lX11

#include <stdint.h>
#include <X11/Xlib.h>

int displayTest();
int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key);
Display *openDisplay();
Window createInvisWindow(Display *d);
void sendCancel(Display *d, Window window);
void cleanupConnection(Display *d, Window window);
int waitHotkey(uintptr_t hkhandle, unsigned int mod, int key, Display *d, Window w);
*/
import "C"
import (
Expand Down Expand Up @@ -50,6 +55,8 @@ type platformHotkey struct {
ctx context.Context
cancel context.CancelFunc
canceled chan struct{}
display *C.Display
window C.Window
}

// Nothing needs to do for register
Expand All @@ -75,6 +82,7 @@ func (hk *Hotkey) unregister() error {
if !hk.registered {
return errors.New("hotkey is not registered.")
}
C.sendCancel(hk.display, hk.window)
hk.cancel()
hk.registered = false
<-hk.canceled
Expand All @@ -88,20 +96,23 @@ func (hk *Hotkey) handle() {
defer runtime.UnlockOSThread()
// KNOWN ISSUE: if a hotkey is grabbed by others, C side will crash the program

hk.display = C.openDisplay()
hk.window = C.createInvisWindow(hk.display)

var mod Modifier
for _, m := range hk.mods {
mod = mod | m
}
h := cgo.NewHandle(hk)
defer h.Delete()

defer C.cleanupConnection(hk.display, hk.window)
for {
select {
case <-hk.ctx.Done():
close(hk.canceled)
return
default:
_ = C.waitHotkey(C.uintptr_t(h), C.uint(mod), C.int(hk.key))
_ = C.waitHotkey(C.uintptr_t(h), C.uint(mod), C.int(hk.key), hk.display, hk.window)
}
}
}
Expand Down