You are not logged in.
Hello,
do you know a tool that supports double click keybinding?
The goal I have is to execute the program slock when I double press escape.
Last edited by castor (2022-04-02 21:47:51)
Offline
sxhkd
Offline
Stefan, can you elaborate? I don't believe sxhkd has this ability. What would you put in the config file for this binding?
I doubt any key binding tool could do this. While the double click, or any key sequence, is possible, I'm not aware of key binding tools that support this. More importantly, though, even just binding "Esc" or any single key without modifiers would be problematic. In order to have a global bind the key binding program grabs a specific key combination and all events with this key press are sent to this client only. So if you bind 'Esc' globally, no other client program could receive escape key presses.
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
The approach would be the same as for a double click - you press a second Esc within the doubleclick interval (eg. 150ms) or the initial press gets replayed.
The downside is that your escape key now lags - and I'm not aware of any exisiting shortcut daemon that would have this feature. Though emacs might … I've never understood the shortcut system there ![]()
Offline
Having this shortcut within a client would not be challenging (e.g., within emacs). Vim handles key chaining. But doing this globally would be very difficult at best (I'm almost curious enough to try to figure out how / if it'd be possible). The real hurdle would be knowing which client to resend the event to if a second enter key wasn't pressed. This is why I gave the note above that it could be feasible if one was ok with no Esc key ever getting through to any other client.
To put it another way, if the question was is there a program that could bind a double-tap Ctrl-A, I'd say I don't think one exists, but it'd not be hard to create one. But if you ask if a program could globally bind even a single-tap of Esc without creating problems ... I'm not so sure that's really possible (at the X11 level at least ... certainly anything that interacted with the kernel or lower level events system in the way a key logger would could do this).
Last edited by Trilby (2022-04-01 22:07:17)
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
Having this in the .sxhkdrc starts slock when pressing Escape twice
Escape + Escape
slockOffline
@Trilby - sorry, I mentioned emacs as some sort of joke (as it's in/famous for its shortcut system as well as for the random feature aggregation).
I'd be interesting enouhg to test sxhkd's behavior, but the general concept is a passive grab on Esc, leading to a function that possibly performs an active grab and if there's a keystrike within the next 150ms either performs the shortcut action (if the next key is esc as well) or XAllowEvents/Replay both grabbed events (and releases the active grab)
The downside of this approach is, as mentioned, that the escape key now lags by 150ms and if you press it and quick enough change the keyboard focus, it'll get to the "wrong" client.
There may also be bad interaction w/ some popup windows that may desire to grab the escape key to close themselves.
An alternative approach would be Xinput2, where none of this is a problem: https://bbs.archlinux.org/viewtopic.php … 3#p1999253
Offline
Without any effort to solve it, here are more thoughts on this:
I really like the keylogger idea. It's just going more low level. In certain computer games, the keybindings are also independent from the set layout. Or they track your layout. Like in minecraft.
I'd love to put a [Solved] on this thread. But I'm not experienced / genius enough to quickly dive into low level / kernel input capture.
I mean this would be the generic code I would write
#include <low-level-library.h>
// keysym
#define ESCAPE 0xff1b
int main() {
int key = 0;
int last_key = 0;
while (key = listen_to_keyboard()) {
if (key == ESCAPE) {
if (last_key == ESCAPE) {
// execute slock
}
}
last_key = key;
}
}edit: I think this code wouldn't work.
This code doesn't define a interval though, like 250ms. I don't have the brain power right now. But I have found this:
stackoverflow time.h
I tried sxhkd.
Escape + Escape
slockThis makes slock appear after only pressing Escape once.
More ideas:
Could you even use a fifo and put all your keyboard input to a fifo and use it as a kind of keyboard multiplex.
Sorry for this draft-y post. Would rather have posted the solution. But I'm best in not putting my ideas into practice ![]()
Last edited by castor (2022-04-02 11:30:20)
Offline
The previously linked https://bbs.archlinux.org/viewtopic.php … 3#p1999253 has some (actual) code for xinput2 that you may use as a base, the ominus "9" in there is actually the escape key (hardcoded it for a POC/very specific hack)
The downside of this approach will be that you're still firing the escape key and whatever client has the focus will receive it (what's probably harmless enough for escape, but could be a *really* stupid idea for eg. Enter)
Offline
I want to make this work.
Okay, I tried your code.
I used xterm as a 'victim' to start when pressing escape.
It works, but the behavior is weird for me.
xterm only starts once. When I press escape again, it doesn't open a second window.
But when I close xterm it just reappears a couple of times until it's finally closed without reappearing.
Haven't included double tap, but I guess I can use the clock() function from time.h for that.
Along with the constant CLOCKS_PER_SEC.
I found a behavior: When I press escape a couple of times with a terminal window focused,
it echos: Display all 2958 possibilities? (y or n).
The same happens when I press tab twice.
With escape I have to press more than twice. So I can live with that.
It's the same behavior with zsh as well as with sh and different terminal emulators.
Anyway. I'm glad for the code from the other thread and try to reuse it.
Last edited by castor (2022-04-02 15:17:26)
Offline
Okay, I tried your code.
I used xterm as a 'victim' to start when pressing escape.
It works, but the behavior is weird for me.
You'll have to post your actual code here, but notably "system("xterm");" runs xterm synchronously and the code waits there untill the xterm process terminated (then unspool all your other keypresses…)
https://man.archlinux.org/man/core/man- … ystem.3.en
Offline
This makes slock appear after only pressing Escape once.
I figured that'd be the result. sxhkd uses the + to create key combinations. So that config line is the same as just one "Escape".
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
Okay, here is my first attempt:
(code taken from the other thread)
(full code further down)
int lastkey = 0;
clock_t lasttime = clock(); // can i set this initially to 0?
// Ask X what it calls that key; skip if it doesn't know
if (ev->detail == 9) {
double delay = (double)clock() - lasttime / CLOCKS_PER_SEC;
if (lastkey != 9) {
printf("pressed escape once\n");
}
else if (lastkey == 9 && delay >= 1.0) {
printf("pressed escape twice but, took longer than 1s, delay: %.2f\n", delay);
}
if (lastkey == 9 && delay < 1.0) {
printf("pressed twice within 1s, delay: %.2f\n", delay);
system("xterm&");
}
lastkey = ev->detail;
lasttime = clock();
}
}
}Unfortunately it doesn't work.
I think I have a logic/think error here with time.h/clock() function. Cause here is some sample output:
^[pressed escape twice but, took longer than 1s, delay: 2474.00
^[pressed escape twice but, took longer than 1s, delay: 2584.00
^[pressed escape twice but, took longer than 1s, delay: 2681.00
^[pressed escape twice but, took longer than 1s, delay: 2747.00
^[pressed escape twice but, took longer than 1s, delay: 2801.00
^[pressed escape twice but, took longer than 1s, delay: 2859.00
^[pressed escape twice but, took longer than 1s, delay: 2914.00
^[pressed escape twice but, took longer than 1s, delay: 3007.00The delay looks like it's about the same even though I wait very long sometimes.
Also it always says "took longer than 1s" even, when I press it twice really fast. Or multiple times.
Also I don't understand the whole code.
Is the while loop running the whole time? so the 'clock is ticking' until there is
a new keypress. Or does it 'hibernate'?
if (ev->detail == 9) is for checking if escape was pressed.
Here is the full code from the other thread with my modification:
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char * argv[]) {
// Connect to X display
Display *dpy = XOpenDisplay(NULL);
if (!dpy) {
printf("Cannot open X display\n");
exit(1);
}
int xiOpcode;
{ // Test for XInput 2 extension
int queryEvent, queryError;
if (!XQueryExtension(dpy, "XInputExtension", &xiOpcode, &queryEvent, &queryError)) {
printf("X Input extension not available\n");
exit(2);
}
}
{ // Request XInput 2.0, to guard against changes in future versions
int major = 2, minor = 0;
int queryResult = XIQueryVersion(dpy, &major, &minor);
if (queryResult == BadRequest) {
printf("Need XI 2.0 support (got %d.%d)\n", major, minor);
exit(3);
} else if (queryResult != Success) {
printf("XIQueryVersion failed!\n");
exit(4);
}
}
{ // Register to receive XInput events
Window root = DefaultRootWindow(dpy);
XIEventMask m;
m.deviceid = XIAllMasterDevices;
m.mask_len = XIMaskLen(XI_LASTEVENT);
m.mask = calloc(m.mask_len, sizeof(char));
XISetMask(m.mask, XI_RawKeyPress);
XISelectEvents(dpy, root, &m, 1 /*number of masks*/);
XSync(dpy, False);
free(m.mask);
}
int lastkey = 0;
clock_t lasttime = clock(); // can i set this initially to 0?
while (True) {
XEvent event;
XGenericEventCookie *cookie = (XGenericEventCookie*)&event.xcookie;
XNextEvent(dpy, &event);
if (XGetEventData(dpy, cookie) && cookie->type == GenericEvent &&
cookie->extension == xiOpcode && cookie->evtype == XI_RawKeyPress) {
XIRawEvent *ev = cookie->data;
// Ask X what it calls that key; skip if it doesn't know
if (ev->detail == 9) {
double delay = (double)clock() - lasttime / CLOCKS_PER_SEC;
if (lastkey != 9) {
printf("pressed escape once\n");
}
else if (lastkey == 9 && delay >= 1.0) {
printf("pressed escape twice but, took longer than 1s, delay: %.2f\n", delay);
}
if (lastkey == 9 && delay < 1.0) {
printf("pressed twice within 1s, delay: %.2f\n", delay);
system("xterm&");
}
lastkey = ev->detail;
lasttime = clock();
}
}
}
}With system("xterm&") it spawns as much terminal windows as I press escape.
EDIT: I have an error in the code that I do double delay = (double)clock() - lasttime / CLOCKS_PER_SEC;
instead of double delay = (double)(clock() - lasttime) / CLOCKS_PER_SEC;
Last edited by castor (2022-04-02 17:05:19)
Offline
Don't use clock(), XIRawEvent has a Time "time" field which counts the server time in miliseconds.
Offline
Sorry, I'm complete nobo ![]()
After going through XInput2.h github, I understand, that the XIRawEvent (struct?) has the (field?) time, which is another struct Time.
But I haven't found where Time is defined/declared, and how to use it.
I haven't been using structs so far.
I really lack the programming skills to hack my desktop environment, but still want to do it.
EDIT:
Ahh, thanks to this site: code.woboq.org I have found it:
include/X11/X.h
typedef unsigned long Time;So Time is just an unsigned long? But what does the number represent? seconds? I'll try it out.
EDIT2: Rather milliseconds, since unsigned long are only natural numbers.
EDIT3:
I made it work!
That's the code:
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
// Connect to X display
Display *dpy = XOpenDisplay(NULL);
if (!dpy) {
printf("Cannot open X display\n");
exit(1);
}
int xiOpcode;
{ // Test for XInput 2 extension
int queryEvent, queryError;
if (!XQueryExtension(dpy, "XInputExtension", &xiOpcode, &queryEvent, &queryError)) {
printf("X Input extension not available\n");
exit(2);
}
}
{ // Request XInput 2.0, to guard against changes in future versions
int major = 2, minor = 0;
int queryResult = XIQueryVersion(dpy, &major, &minor);
if (queryResult == BadRequest) {
printf("Need XI 2.0 support (got %d.%d)\n", major, minor);
exit(3);
} else if (queryResult != Success) {
printf("XIQueryVersion failed!\n");
exit(4);
}
}
{ // Register to receive XInput events
Window root = DefaultRootWindow(dpy);
XIEventMask m;
m.deviceid = XIAllMasterDevices;
m.mask_len = XIMaskLen(XI_LASTEVENT);
m.mask = calloc(m.mask_len, sizeof(char));
XISetMask(m.mask, XI_RawKeyPress);
XISelectEvents(dpy, root, &m, 1 /*number of masks*/);
XSync(dpy, False);
free(m.mask);
}
int lastkey = 0;
unsigned long delay = -1; // -1 so it's end of range
unsigned long lastpress = 0;
while (True) {
XEvent event;
XGenericEventCookie *cookie = (XGenericEventCookie*)&event.xcookie;
XNextEvent(dpy, &event);
if (XGetEventData(dpy, cookie) && cookie->type == GenericEvent &&
cookie->extension == xiOpcode && cookie->evtype == XI_RawKeyPress) {
XIRawEvent *ev = cookie->data;
// Ask X what it calls that key; skip if it doesn't know
if (ev->detail == 9) {
unsigned long now = ev->time;
delay = now - lastpress;
if (lastkey != 9) {
printf("pressed escape once\n");
}
else if (lastkey == 9 && delay >= 200) {
printf("pressed escape twice but, took longer than 10ms, delay: %ul\n", delay);
}
if (lastkey == 9 && delay < 200) {
printf("pressed twice within 10ms, delay: %ul\n", delay);
system("slock&");
}
lastpress = now;
}
lastkey = ev->detail;
}
}
}I found 200ms a good delay.
I don't really know, if this code is safe enough to use 'in production'.
Haha, I 'll put it into my .xinitrc. (The executable).
Thanks for your help!!!
I also used structs for the first time, and xlib.
thanks, seth, Trilby and Stefan Husmann
Last edited by castor (2022-04-02 18:35:00)
Offline
I also used structs for the first time, and xlib.
To continue your strike of first time actions, please always remember to mark resolved threads by editing your initial posts subject - so others will know that there's no task left, but maybe a solution to find.
Thanks. ![]()
I briefly looked at the code and w/ o the pure debug statements about the relevant "if (lastkey == 9 && delay < 200) {" it looks fine.
You might want to resovle the keycode, https://tronche.com/gui/x/xlib/utilitie … ycode.html (XK_Escape, see /usr/include/X11/keysymdef.h)
Offline