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

[Feature request] Specifying pauses in macros #359 #437

Open
wants to merge 16 commits into
base: testing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
dfacdf5
change long macro delay to introduce a fixed 5ms delay on all macros.
stephenhouser Aug 11, 2016
0358f71
Reflect the changes this fork introduces in README.md
stephenhouser Aug 11, 2016
47fddda
Implement setting fixed delay with daemon's command followed by dela…
stephenhouser Aug 12, 2016
09e8ff6
Update README.md to match what the code does.
stephenhouser Aug 12, 2016
a5fd205
add delay to macro actions and handling of local delay in macro playback
stephenhouser Aug 13, 2016
e2fcb13
Implement local, per action delay, for macro playback.
stephenhouser Aug 13, 2016
5b1cf99
update readme syntax for macro delay
stephenhouser Aug 13, 2016
5ee2d40
update readme to flesh out specification of global and local delay sy…
stephenhouser Aug 13, 2016
2eed137
Move macro delay specification out of README.md into MACRO-DELAY.md
stephenhouser Aug 13, 2016
ee23e01
Remove trailing spaces in code and documentation
stephenhouser Aug 13, 2016
bfd3d49
Update specification to match invalid setting of global delay
stephenhouser Aug 13, 2016
ed08836
Remove MACRO-DELAY.md and integrate macro delay content into DAEMON.md
stephenhouser Aug 14, 2016
ca9eb63
Add periods at end of examples for macro delay in DAEMON.md
stephenhouser Aug 14, 2016
652b3ba
Make examples for macro delay a bulleted list to match document's style
stephenhouser Aug 14, 2016
99ffd3e
Make examples for macro delay a bulleted list to match document's style
stephenhouser Aug 14, 2016
d6aa207
Add uint bounds check for global delay setting.
stephenhouser Aug 14, 2016
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
31 changes: 31 additions & 0 deletions DAEMON.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,37 @@ Macros are a more advanced form of key binding, controlled with the `macro` comm

Assigning a macro to a key will cause its binding to be ignored; for instance, `macro a:+b,-b` will cause A to generate a B character regardless of its binding. However, `macro lctrl+a:+b,-b` will cause A to generate a B only when Ctrl is also held down.

### Macro playback delay

There are two types of playback delay that can be set with macros; global and local. Setting a _global delay_ value introduces a time delay between events during macro execution or playback. _Local delay_ allows setting the delay after an individual event, overriding the global delay value for that event. Thus global delay can be used to set the overall playback speed of macros and local delays can be used to tune individual events within a macro.

All delay values are specified in microseconds (us) and are positive values from `0` to `UINT_MAX - 1`. This means delays range from 0 to just over 1 hour (4,294,967,294us, 4,294 seconds, 71 minutes, or 1.19 hours). A value of zero (0) represents no delay between actions.

#### Global macro delay (default delay)

Global delay allows macro playback speed to be changed. It sets the time between (actually after) each recorded macro event. If global delay is set to 1 microsecond then a 1 ms delay will follow each individual macro event when the macro is triggered.

The _global delay_ is set with the ckb-daemon's existing (in testing branch) `delay` command followed by an unsigned integer representing the number of microseconds to wait after each macro action and before the next.

Global delay can also be set to `on` which maintains backwards compatibility with the current development of `ckb-daemon` for long macro playback. That is, setting the global delay to `on` introduces a 30us and a 100us delay based on the macro's length during playback.

**NOTE**: This setting also introduces a delay after the last macro action. This functionality exists in the current testing branch and was left as-is. It is still to be determined if this is a bug or a feature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my bug, sorry for it. You may change the logic behind it that no more delay is used with the last keycode.

Copy link
Author

@stephenhouser stephenhouser Aug 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do this as a separate pull request.

Copy link
Contributor

@frickler24 frickler24 Sep 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenhouser Hi, the last days I had some time to change the ckb client.
Please feel free to test it: fdf73e2 (Branch macrotime.0.3 at https://github.com/frickler24/ckb.git).
The wrong handling of the last delay is fixed also.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenhouser I Added some GUI repairs in f7f8ce9.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent. I'll give it a try but likely not for a few days. Code on!


**Examples:**
* `delay 1000` sets a 1,000us delay between action playback.
* `delay on` sets long macro delay; 30us for actions between 20 and 200, 100us for actions > 200.
* `delay off` sets no delay (same as 0).
* `delay 0` sets no delay (same as off).
* `delay spearmint-potato` is invalid input, sets no delay (same as off).

#### Local macro delay (keystroke delay)

Local Delay allows each macro action to have a post-action delay associated with it. This allows a macro to vary it's playback speed for each event. If no local delay is specified for a macro action, then the global `delay` (above) is used. All delay values are in microsecods (us) as with the global delay setting.

***Examples:***
* `macro g5:+d,-d,+e=5000,-e,+l,-l=10000,+a,-a,+y,-y=1000000,+enter,-enter` define a macro for `g5` with a 5,000us delay between the `e` down and `e` up actions. A 1,000us delay between `l` up and `a` down, a delay of one second (1,000,000us) after `y` up and before `enter`, and the global delay for all other actions.
* `macro g5:+d,-d=0` use default delay between `d` down and `d` up and no delay (0us) after `d` up. This removes the noted feature/bug (above) where the last action has a trailing delay associated with it.

DPI and mouse settings
----------------------

Expand Down
15 changes: 13 additions & 2 deletions src/ckb-daemon/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,20 @@ int readcmd(usbdevice* kb, const char* line){
}
continue;
}
case DELAY:
kb->delay = (!strcmp (word, "on")); // independendant from parameter to handle false commands like "delay off"
case DELAY: {
long int delay;
if(sscanf(word, "%ld", &delay) == 1 && 0 <= delay && delay < UINT_MAX) {
// Add delay of `newdelay` microseconds to macro playback
kb->delay = (unsigned int)delay;
} else if(strcmp(word, "on") == 0) {
// allow previous syntax, `delay on` means use old `long macro delay`
kb->delay = UINT_MAX;
} else {
// bad parameter to handle false commands like "delay off"
kb->delay = 0; // No delay.
}
continue;
}
default:;
}

Expand Down
33 changes: 28 additions & 5 deletions src/ckb-daemon/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ static void inputupdate_keys(usbdevice* kb){
os_mousemove(kb, action->rel_x, action->rel_y);
else {
os_keypress(kb, action->scan, action->down);
if (kb->delay) {
if (a > 200) usleep (100);
else if (a > 20) usleep(30);
if (action->delay != UINT_MAX) { // local delay set
usleep(action->delay);
} else if (kb->delay != UINT_MAX) { // use default global delay
usleep(kb->delay);
} else { // use old long macro delay code
if (a > 200) {
usleep (100);
} else if (a > 20) {
usleep(30);
}
}
}
}
Expand Down Expand Up @@ -241,7 +248,7 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment){
int empty = 1;
int left = strlen(keys), right = strlen(assignment);
int position = 0, field = 0;
char keyname[12];
char keyname[24];
while(position < left && sscanf(keys + position, "%10[^+]%n", keyname, &field) == 1){
int keycode;
if((sscanf(keyname, "#%d", &keycode) && keycode >= 0 && keycode < N_KEYS_INPUT)
Expand Down Expand Up @@ -276,9 +283,23 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment){
// Scan the actions
position = 0;
field = 0;
while(position < right && sscanf(assignment + position, "%11[^,]%n", keyname, &field) == 1){
// max action = old 11 chars plus 12 chars which is the max 32-bit int 4294967295 size
while(position < right && sscanf(assignment + position, "%23[^,]%n", keyname, &field) == 1){
if(!strcmp(keyname, "clear"))
break;

// Check for local key delay of the form '[+-]<key>=<delay>'
long int long_delay; // scanned delay value, used to keep delay in range.
unsigned int delay = UINT_MAX; // computed delay value. UINT_MAX means use global delay value.
char real_keyname[12]; // temp to hold the left side (key) of the <key>=<delay>
int scan_matches = sscanf(keyname, "%11[^=]=%ld", real_keyname, &long_delay);
if (scan_matches == 2) {
if (0 <= long_delay && long_delay < UINT_MAX) {
delay = (unsigned int)long_delay;
strcpy(keyname, real_keyname); // keyname[24], real_keyname[12]
}
}

int down = (keyname[0] == '+');
if(down || keyname[0] == '-'){
int keycode;
Expand All @@ -287,13 +308,15 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment){
// Set a key numerically
macro.actions[macro.actioncount].scan = keymap[keycode].scan;
macro.actions[macro.actioncount].down = down;
macro.actions[macro.actioncount].delay = delay;
macro.actioncount++;
} else {
// Find this key in the keymap
for(unsigned i = 0; i < N_KEYS_INPUT; i++){
if(keymap[i].name && !strcmp(keyname + 1, keymap[i].name)){
macro.actions[macro.actioncount].scan = keymap[i].scan;
macro.actions[macro.actioncount].down = down;
macro.actions[macro.actioncount].delay = delay;
macro.actioncount++;
break;
}
Expand Down
3 changes: 2 additions & 1 deletion src/ckb-daemon/structures.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef struct {
short scan; // Key scancode, OR
short rel_x, rel_y; // Mouse movement
char down; // 0 for keyup, 1 for keydown (ignored if rel_x != 0 || rel_y != 0)
uint delay; // us delay after action; UINT_MAX for use global delay
} macroaction;

// Key macro
Expand Down Expand Up @@ -247,7 +248,7 @@ typedef struct {
// Color dithering in use
char dither;
// Flag to check, if large macros should be sent delayed
char delay;
uint delay;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This var is used by the GUI element "Settings -> More Settings -> Use delay for very long macros.
Have you checked what happens with this Element if you change the type?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, on two accounts, the other changes use this as a uint, and the syntax the GUI uses works with the these changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks.

I have done some testing today. First I merged your d6aa207 and my macrotime0.1.
Then I created a new G-macro (typing very, very slowly):
+lshift,+d=26161,-d=37140,-lshift=142738,+a=89017,-a=144970,+s=92969,-s=50021,+space=1996,-space,+w=82943,-w=49985,+i=95034,-i=78998,+r=61994,-r=91999,+d=81007,-d=51698,+space,-space,+e=100986,-e=35010,+i=86196,-i=53792,+n=15992,-n=46003,+space=3206,-space=2798,+lshift=76004,+t=9013,-t=3990,-lshift=32303,+e=61019,-e=6009,+x=86265,-x=49755,+t=1956,-t=4440,+space=97006,-space=3994,+m=97974,+i=19981,-m=6008,-i=1998,+t=630,-t=7036,+space=8959,-space=65303,+lshift=7099,+d,-lshift,-d,+e,-e,+l,+a,-l=31996,+z=56249,-a=58814,-z=58948,+enter=69214,-enter

That gives a string "Das wird ein Text mit Delay", which means "That gets a text with delay". Trying this macro runs well.
But if I repeat pressing that G-key (10, 20 times) while the macro is just running, I get sometimes a disconnect of the KB. The daemon is killed and restartet from init.

The logging says something about usb disconnected (there is an unformmated file at the end of this post):

22.08.2016 20:14:59 Mainfrix kernel [20612.403540] input: ckb1: Corsair K95 RGB Gaming Keyboard as /devices/virtual/input/input117 22.08.2016 20:16:42 Mainfrix kernel [20715.383813] usb 1-3: USB disconnect, device number 46 22.08.2016 20:16:42 Mainfrix kernel [20715.657524] usb 1-3: new full-speed USB device number 47 using xhci_hcd 22.08.2016 20:16:44 Mainfrix acpid input device has been disconnected, fd 17 22.08.2016 20:16:44 Mainfrix acpid input device has been disconnected, fd 25 22.08.2016 20:16:47 Mainfrix kernel [20720.793068] usb 1-3: unable to read config index 0 descriptor/all 22.08.2016 20:16:47 Mainfrix kernel [20720.793073] usb 1-3: can't read configurations, error -110 22.08.2016 20:16:47 Mainfrix kernel [20720.905202] usb 1-3: new full-speed USB device number 48 using xhci_hcd 22.08.2016 20:16:48 Mainfrix kernel [20721.034919] usb 1-3: New USB device found, idVendor=1b1c, idProduct=1b11 22.08.2016 20:16:48 Mainfrix kernel [20721.034923] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 22.08.2016 20:16:48 Mainfrix kernel [20721.034925] usb 1-3: Product: Corsair K95 RGB Gaming Keyboard 22.08.2016 20:16:48 Mainfrix kernel [20721.034926] usb 1-3: Manufacturer: Corsair 22.08.2016 20:16:48 Mainfrix kernel [20721.034928] usb 1-3: SerialNumber: 0F022014AE3B9406537275B6F5001945 22.08.2016 20:16:48 Mainfrix kernel [20721.036220] input: Corsair Corsair K95 RGB Gaming Keyboard as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/0003:1B1C:1B11.0051/input/input118 22.08.2016 20:16:48 Mainfrix kernel [20721.089793] hid-generic 0003:1B1C:1B11.0051: input,hidraw2: USB HID v1.11 Keyboard [Corsair Corsair K95 RGB Gaming Keyboard ] on usb-0000:00:14.0-3/input0 22.08.2016 20:16:48 Mainfrix kernel [20721.090869] input: Corsair Corsair K95 RGB Gaming Keyboard as /devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.1/0003:1B1C:1B11.0052/input/input119 22.08.2016 20:16:48 Mainfrix kernel [20721.145683] hid-generic 0003:1B1C:1B11.0052: input,hidraw3: USB HID v1.11 Keyboard [Corsair Corsair K95 RGB Gaming Keyboard ] on usb-0000:00:14.0-3/input1 22.08.2016 20:16:48 Mainfrix kernel [20721.146367] hid-generic 0003:1B1C:1B11.0053: hiddev0,hidraw4: USB HID v1.11 Device [Corsair Corsair K95 RGB Gaming Keyboard ] on usb-0000:00:14.0-3/input2 22.08.2016 20:16:48 Mainfrix kernel [20721.147014] hid-generic 0003:1B1C:1B11.0054: hiddev0,hidraw5: USB HID v1.11 Device [Corsair Corsair K95 RGB Gaming Keyboard ] on usb-0000:00:14.0-3/input3 22.08.2016 20:16:48 Mainfrix mtp-probe checking bus 1, device 48: "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-3" 22.08.2016 20:16:48 Mainfrix mtp-probe bus: 1, device: 48 was not an MTP device 22.08.2016 20:16:48 Mainfrix acpid input device has been disconnected, fd 17 22.08.2016 20:16:48 Mainfrix acpid input device has been disconnected, fd 25 22.08.2016 20:16:48 Mainfrix kernel [20721.427641] input: ckb1: Corsair K95 RGB Gaming Keyboard as /devices/virtual/input/input120 22.08.2016 20:16:48 Mainfrix kernel [20721.427859] input: ckb1: Corsair K95 RGB Gaming Keyboard as /devices/virtual/input/input121

Running the ckb-daemon with gdb brings following output:

`(gdb) run
Starting program: /home/lutz/Projekte/ckb/bin/ckb-daemon
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
ckb: Corsair RGB driver beta-v0.2.6+t04
[I] Root controller ready at /dev/input/ckb0
[New Thread 0x7ffff6b5b700 (LWP 30508)]
[I] Connecting Corsair K95 RGB Gaming Keyboard at /dev/input/ckb1
[New Thread 0x7ffff635a700 (LWP 30511)]
[I] Starting input thread for /dev/input/ckb1
[New Thread 0x7ffff5b59700 (LWP 30512)]
[I] Setup finished for /dev/input/ckb1

Here is the crash:
# [E] os_usbsend (via led_keyboard.c:131): No such device
[I] Attempting reset...
[E] os_resetusb (via usb.c:169): resetusb failed: No such device
[E] usb_tryreset (usb.c:177): Reset failed. Disconnecting.
[I] Stopping input thread for /dev/input/ckb1
[I] Disconnecting /dev/input/ckb1
[Thread 0x7ffff635a700 (LWP 30511) exited]
[I] Removed device path /dev/input/ckb1
[Thread 0x7ffff5b59700 (LWP 30512) exited]
[Thread 0x7ffff6b5b700 (LWP 30508) exited]
[New Thread 0x7ffff5b59700 (LWP 2684)]
[I] Connecting Corsair K95 RGB Gaming Keyboard at /dev/input/ckb1
[New Thread 0x7ffff635a700 (LWP 2702)]
[I] Starting input thread for /dev/input/ckb1
[New Thread 0x7ffff5154700 (LWP 2703)]
[I] Setup finished for /dev/input/ckb1
`
Do you have the same effect?
You can take my 6f9331a (is called macrotime.0.2) to check this behaviour.
BTW: In my environment UINT_MAX was undefined, so I needed to include a headerfile.
Here you can find a more detailed output:
protocol_ckb.txt
cu. Lutz

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on a Mac, using Xcode's gcc to compile, UINT_MAX was 4-billion something.

I did try recording a delayed macro, on my version, not yours, and hitting the key a few times. There was a long delay but the macro played back and the keys afterwards were queued and played. The macro was not as long as yours, and I think I hit it maybe 3 to 4 times, not 10 to 20.

I suspect the key buffer is getting overflowed causing the disconnect. Perhaps there should be a limit to the delay time? I think this would only make the problem harder to reproduce rather than fixing it though.

The question is, what to do when this happens? Queue up more macro keys (make the queue larger), drop some of them (at the start or end of the queue), limit the queue size, or ignore it and pretend it doesn't happen, pretend it doesn't crash.

  1. Making the queue larger might actually make the keyboard seem broken to people. If a really long macro is playing back and they hit keys, they won't see anything happening, their keys will be delayed until earlier ones finish playing.
  2. Dropping keys seems tricky. First, which keystrokes to drop? Ones at the start or end of the queue? I could see making the case for either and for both of them being the wrong choice.
  3. Limiting the queue size would be the same as dropping the most recent keys struck.
  4. Ignoring the problem is kind of what happens now. Not sure that's the right thing to do.

I'm leaning towards limiting the queue size as being the right answer here (4). Figuring out when the queue is full and ignoring new input. Maybe there should there be command that cancels macro playback? One you could bind to another key or in a sequence.

This is getting tricky.

Copy link
Author

@stephenhouser stephenhouser Aug 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The macro-killing key should flush (empty) out the keyboard buffer and ignore any to-be sent keys as well as cancelling any macro playback.

#including limits.h worked fine in my environment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is another possible reason for the crashes: the Firmware in the KB itself.
The reason why I implemented the original delay was the overflow of the buffer in the unix User-device-driver in the direction from ckb-daemon to user-input. When I had longer macros, some of the keys were dropped - independent of the keystroke-frequency.
When I tested this, there was only 1 keystroke type ahead. With a long macro (I tested with hundreds and some thousand chars) and pressing the G-Key 3 times, the macro string appeared 2 times.
If I pressed G-key 2 times and a 3rd time when the 2nd macro was executing, a third macro string appeared and so on.
IMHO this is a behaviour of the USB user-driver in linux, but I didn't check it in the code. But if I'm right, there might be no response to the KB via USB. Maybe there is the reason for the failure. So if we can assure that the USB protocol isn't affected by our delays, we come a step further: We get your choice No 4.
On the other hand: Who needs repeatedly sending of very, very slow macros?
I'm not curious, but it's killing me, if I do not know something...

My suggestion: Let's implement the rest of the UI, bring it to the testing stage and wait for comments. Maybe this is a problem only on debian / ubuntu based systems with older kernels and with mint18 the effect is gone - who knows...

Copy link
Author

@stephenhouser stephenhouser Aug 23, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sort of agree, option 4. Perhaps add a warning somewhere in the README about really long delays and keyboard overflows.

I have not yet tested your branch on my Mac. Keyboard is on a different machine than I've been at since last week. Hope to just double check and see if there's a crash or just a really long playback.

} usbdevice;

#endif // STRUCTURES_H