diff --git a/DAEMON.md b/DAEMON.md
index 0c72b315c8..c125f9b86f 100644
--- a/DAEMON.md
+++ b/DAEMON.md
@@ -9,11 +9,13 @@ The daemon provides devices at `/dev/input/ckb*`, where * is the device number,
Other `ckb*` devices contain the following:
- `cmd`: Keyboard controller.
-- `notify0`: Keyboard notifications.
+- `notify0`: Keyboard- or mouse notifications.
+- `notify1`: Keyboard- or mouse notifications, used for macro recording.
- `features`: Device features.
- `fwversion`: Device firmware version (not present on all devices).
- `model`: Device description/model.
- `pollrate`: Poll rate in milliseconds (not present on all devices).
+- `productid`: Contains the USB productID of the hardware
- `serial`: Device serial number. `model` and `serial` will match the info found in `ckb0/connected`
Commands
@@ -128,6 +130,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.
+
+**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
----------------------
@@ -207,7 +240,7 @@ Restart
-------
Because sometimes the communication between the daemon and the keyboard is corrupted after resuming from standby or suspend, a restart function is implemented.
-It first calls the quit() funtion, then it calls main() again with the original parameter list.
+It first calls the quit() function, then it calls main() again with the original parameter list.
There are two ways to restart the daemon:
- send the string "restart some-description-as-one-word" to the cmd-pipe (normally /dev/input/ckb1/cmd or /dev/input/ckb2/cmd, depending on what device gets which ID.
diff --git a/README.md b/README.md
index 2d2024e84a..85fe2e0969 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
ckb-next: RGB Driver for Linux and OS X
-==================================
@@ -162,6 +161,8 @@ The user interface is still a work in progress.
- Reactive lighting
- Multiple profiles/modes with hardware save function
- Adjustable mouse DPI with ability to change DPI on button press
+- Key macros (G-Keys also); Have a look at https://youtu.be/qhrKP03_NrM for a short video tutorial
+- Key macro delays: Handle delays between keystrokes when playing a macro
Closing ckb will actually minimize it to the system tray. Use the Quit option from the tray icon or the settings screen to exit the application.
@@ -169,7 +170,6 @@ Closing ckb will actually minimize it to the system tray. Use the Quit option fr
- **v0.3 release:**
- Ability to store profiles separately from devices, import/export them
- More functions for the Win Lock key
-- Key macros
- **v0.4 release:**
- Ability to import CUE profiles
- Ability to tie profiles to which application has focus
diff --git a/src/ckb-daemon/command.c b/src/ckb-daemon/command.c
index a4abc25458..b1b97356b5 100644
--- a/src/ckb-daemon/command.c
+++ b/src/ckb-daemon/command.c
@@ -1,3 +1,4 @@
+#include
#include "command.h"
#include "device.h"
#include "devnode.h"
@@ -200,9 +201,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;
+ }
case RESTART: {
char mybuffer[] = "no reason specified";
if (sscanf(line, " %[^\n]", word) == -1) { ///< Because length of word is length of line + 1, there should be no problem with buffer overflow.
@@ -211,7 +223,6 @@ int readcmd(usbdevice* kb, const char* line){
vt->do_cmd[command](kb, mode, notifynumber, 0, word);
continue;
}
-
default:;
}
diff --git a/src/ckb-daemon/device.c b/src/ckb-daemon/device.c
index 79d142b17e..39be88eae6 100644
--- a/src/ckb-daemon/device.c
+++ b/src/ckb-daemon/device.c
@@ -9,8 +9,11 @@ int hwload_mode = 1; ///< hwload_mode = 1 means read hardware once. shoul
// Device list
usbdevice keyboard[DEV_MAX]; ///< remember all usb devices. Needed for closeusb().
pthread_mutex_t devlistmutex = PTHREAD_MUTEX_INITIALIZER;
-pthread_mutex_t devmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER };
-pthread_mutex_t inputmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER };
+pthread_mutex_t devmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER }; ///< Mutex for handling the usbdevice structure
+pthread_mutex_t inputmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER }; ///< Mutex for dealing with usb input frames
+pthread_mutex_t macromutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER }; ///< Protecting macros against lightning: Both use usb_send
+pthread_mutex_t macromutex2[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER }; ///< Protecting the single link list of threads and the macrovar
+pthread_cond_t macrovar[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_COND_INITIALIZER }; ///< This variable is used to stop and wakeup all macro threads which have to wait.
/// \brief .
///
diff --git a/src/ckb-daemon/device.h b/src/ckb-daemon/device.h
index a60a5ab2cb..163aa6683f 100644
--- a/src/ckb-daemon/device.h
+++ b/src/ckb-daemon/device.h
@@ -21,6 +21,14 @@ extern pthread_mutex_t devmutex[DEV_MAX];
extern pthread_mutex_t inputmutex[DEV_MAX];
#define imutex(kb) (inputmutex + INDEX_OF(kb, keyboard))
+// Needed to synchronize sending macro-keys to the os and sending color info to the device
+extern pthread_mutex_t macromutex[DEV_MAX];
+#define mmutex(kb) (macromutex + INDEX_OF(kb, keyboard))
+extern pthread_mutex_t macromutex2[DEV_MAX];
+#define mmutex2(kb) (macromutex2 + INDEX_OF(kb, keyboard))
+extern pthread_cond_t macrovar[DEV_MAX];
+#define mvar(kb) (macrovar + INDEX_OF(kb, keyboard))
+
// Sets up device hardware, after software initialization is finished. Also used during resets
// Should be called only from setupusb/resetusb
int start_dev(usbdevice* kb, int makeactive);
diff --git a/src/ckb-daemon/input.c b/src/ckb-daemon/input.c
index 78a5c3943c..d4050d6225 100644
--- a/src/ckb-daemon/input.c
+++ b/src/ckb-daemon/input.c
@@ -1,3 +1,4 @@
+#include
#include "device.h"
#include "input.h"
#include "notify.h"
@@ -12,39 +13,154 @@ int macromask(const uchar* key1, const uchar* key2){
return 1;
}
+///
+/// \brief pt_head is the head pointer for the single linked thread list managed by macro_pt_en/dequeue().
+static ptlist_t* pt_head = 0;
+/// \brief pt_tail is the tail pointer for the single linked thread list managed by macro_pt_en/dequeue().
+static ptlist_t* pt_tail = 0;
+
+///
+/// \brief macro_pt_enqueue Save the new thread in the single linked list (FIFO).
+/// \attention Becuase multiple threads may use this function in parallel,
+/// save the critical section with a mutex.
+///
+static void macro_pt_enqueue() {
+ ptlist_t* new_elem = malloc(sizeof(ptlist_t));
+ if (!new_elem) {
+ perror("macro_pt_enqueue: ");
+ exit (-1); ///< exit on critical situation; \todo find a better exit strategy if no more mem available.
+ }
+ new_elem->next = 0;
+ new_elem->thread_id = pthread_self(); ///< The element knows its ID byself
+ if (pt_head == 0) {
+ pt_head = pt_tail = new_elem; ///< new list, first element
+ } else {
+ pt_tail->next = new_elem; ///< existing list, append on last element (FIFO)
+ pt_tail = new_elem;
+ }
+}
+
+///
+/// \brief macro_pt_dequeue gets the first thread id of the list and returns the thread_id stored in it.
+/// \return the ptread_id of the first element. If list is empty, return 0.
+/// \attention Becuase multiple threads may use this function in parallel,
+/// save the critical section with a mutex.
+///
+static pthread_t macro_pt_dequeue() {
+ pthread_t retval = 0;
+ ptlist_t* elem = 0;
+ if (pt_head == 0 && pt_tail == 0) {
+ ckb_err("macro_pt_dequeue: called on empty list.\n");
+ return 0; ///< why are we called?
+ }
+ elem = pt_head;
+ pt_head = pt_head->next;
+ if (pt_head == 0) pt_tail = 0; ///< Was last element in the list, so clear tail.
+ retval = elem->thread_id; ///< save the return value before deleting element
+ free(elem);
+ return retval;
+}
+
+///
+/// \brief macro_pt_first returns the first pthread_id but does not remove the first entry.
+/// \return the pthread_id of the first element in the list or 0 if list is empty.
+/// \attention Becuase multiple threads may use this function in parallel,
+/// save the critical section with a mutex (avoid NIL-ptr)
+///
+static pthread_t macro_pt_first() {
+ return pt_head? pt_head->thread_id : 0;
+}
+
+///
+/// \brief play_macro is the code for all threads started to play a macro.
+/// \param param \a parameter_t to store Kb-ptr and macro-ptr (thread may get only one user-parameter)
+/// \return 0 on success, -1 else (no one is interested in it except the kernel...)
+///
+static void* play_macro(void* param) {
+ parameter_t* ptr = (parameter_t*) param;
+ usbdevice* kb = ptr->kb;
+ keymacro* macro = ptr->macro;
+
+ /// First have a look if we are the first and only macro-thread to run. If not, wait.
+ /// So enqueue our thread first, so it is remembered for us and can be seen by all others.
+ pthread_mutex_lock(mmutex2(kb));
+ macro_pt_enqueue();
+ // ckb_info("Entering critical section with 0x%lx. Queue head is 0x%lx\n", (unsigned long int)pthread_self(), (unsigned long int)macro_pt_first());
+ while (macro_pt_first() != pthread_self()) { ///< If the first thread in the list is not our, another one is running
+ // ckb_info("Now waiting with 0x%lx because of 0x%lx\n", (unsigned long int)pthread_self(), (unsigned long int)macro_pt_first());
+ pthread_cond_wait(mvar(kb), mmutex2(kb));
+ // ckb_info("Waking up with 0x%lx\n", (unsigned long int)pthread_self());
+ }
+ pthread_mutex_unlock(mmutex2(kb)); ///< Give all new threads the chance to enter the block.
+
+ /// Send events for each keypress in the macro
+ pthread_mutex_lock(mmutex(kb)); ///< Synchonization between macro output and color information
+ for (int a = 0; a < macro->actioncount; a++) {
+ macroaction* action = macro->actions + a;
+ if (action->rel_x != 0 || action->rel_y != 0)
+ os_mousemove(kb, action->rel_x, action->rel_y);
+ else {
+ os_keypress(kb, action->scan, action->down);
+ pthread_mutex_unlock(mmutex(kb)); ///< use this unlock / relock for enablling the parallel running colorization
+ if (action->delay != UINT_MAX && action->delay) { ///< local delay set
+ usleep(action->delay);
+ } else if (kb->delay != UINT_MAX && kb->delay) { ///< use default global delay
+ usleep(kb->delay);
+ } else if (a < (macro->actioncount - 1)) { ///< use delays depending on macro length
+ if (a > 200) {
+ usleep (100);
+ } else if (a > 20) {
+ usleep(30);
+ }
+ }
+ pthread_mutex_lock(mmutex(kb));
+ }
+ }
+
+ pthread_mutex_lock(mmutex2(kb)); ///< protect the linked list and the mvar
+ // ckb_info("Now leaving 0x%lx and waking up all others\n", (unsigned long int)pthread_self());
+ macro_pt_dequeue();
+ pthread_cond_broadcast(mvar(kb)); ///< Wake up all waiting threads
+ pthread_mutex_unlock(mmutex2(kb)); ///< for the linked list and the mvar
+
+ pthread_mutex_unlock(mmutex(kb)); ///< Sync keyboard input/output and colorization
+ return 0;
+}
+
+///
+/// \brief inputupdate_keys Handle input from Keyboard or mouse; start Macrof if detected.
+/// \param kb
+///
static void inputupdate_keys(usbdevice* kb){
usbmode* mode = kb->profile->currentmode;
binding* bind = &mode->bind;
usbinput* input = &kb->input;
+
// Don't do anything if the state hasn't changed
if(!memcmp(input->prevkeys, input->keys, N_KEYBYTES_INPUT))
return;
// Look for macros matching the current state
- int macrotrigger = 0;
- if(kb->active){
- for(int i = 0; i < bind->macrocount; i++){
+ if (kb->active) {
+ for (int i = 0; i < bind->macrocount; i++) {
keymacro* macro = &bind->macros[i];
- if(macromask(input->keys, macro->combo)){
- if(!macro->triggered){
- macrotrigger = 1;
- macro->triggered = 1;
- // Send events for each keypress in the macro
- for(int a = 0; a < macro->actioncount; a++){
- macroaction* action = macro->actions + a;
- if(action->rel_x != 0 || action->rel_y != 0)
- 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 (macromask(input->keys, macro->combo)) {
+ if (!macro->triggered) {
+ parameter_t* params = malloc(sizeof(parameter_t));
+ if (params == 0) {
+ perror("inputupdate_keys got no more mem:");
+ } else {
+ pthread_t thread = 0;
+ params->kb = kb;
+ params->macro = macro;
+ int retval = pthread_create(&thread, 0, play_macro, (void*)params);
+ if (retval) {
+ perror("inputupdate_keys: Creating thread returned not null");
+ } else {
+ macro->triggered = 1;
}
}
}
- } else {
- macro->triggered = 0;
- }
+ } else macro->triggered = 0;
}
}
// Make a list of keycodes to send. Rearrange them so that modifier keydowns always come first
@@ -68,8 +184,8 @@ static void inputupdate_keys(usbdevice* kb){
char old = oldb & mask, new = newb & mask;
// If the key state changed, send it to the input device
if(old != new){
- // Don't echo a key press if a macro was triggered or if there's no scancode associated
- if(!macrotrigger && !(scancode & SCAN_SILENT)){
+ // Don't echo a key press if there's no scancode associated
+ if(!(scancode & SCAN_SILENT)){
if(IS_MOD(scancode)){
if(new){
// Modifier down: Add to the end of modifier keys
@@ -111,11 +227,14 @@ static void inputupdate_keys(usbdevice* kb){
}
}
}
- // Process all queued keypresses
- int totalkeys = modcount + keycount + rmodcount;
- for(int i = 0; i < totalkeys; i++){
- int scancode = events[i];
- os_keypress(kb, (scancode < 0 ? -scancode : scancode) - 1, scancode > 0);
+ /// Process all queued keypresses if no macro is running yet.
+ /// \todo If we want to get all keys typed while a macro is played, add the code for it here.
+ if (!macro_pt_first()) {
+ int totalkeys = modcount + keycount + rmodcount;
+ for(int i = 0; i < totalkeys; i++){
+ int scancode = events[i];
+ os_keypress(kb, (scancode < 0 ? -scancode : scancode) - 1, scancode > 0);
+ }
}
}
@@ -241,7 +360,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)
@@ -276,9 +395,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 '[+-]='
+ 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 =
+ 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;
@@ -287,6 +420,7 @@ 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
@@ -294,6 +428,7 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment){
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;
}
diff --git a/src/ckb-daemon/input.h b/src/ckb-daemon/input.h
index 7625df5b30..55ef137d07 100644
--- a/src/ckb-daemon/input.h
+++ b/src/ckb-daemon/input.h
@@ -47,5 +47,21 @@ void os_mousemove(usbdevice* kb, int x, int y);
// Perform OS-specific setup for indicator lights. Called when the device is created. Return 0 on success.
int os_setupindicators(usbdevice* kb);
+///
+/// \brief struct parameter contains the values for a fresh started macro_play thread.
+/// \a parameter_t is the typedef for it.
+///
+typedef struct parameter {
+ usbdevice* kb;
+ keymacro* macro;
+} parameter_t;
+
+/// \brief struct ptlist is one element in the single linked list to store macro_play threads waiting for their execution
+/// \a ptlist_t is the typedef for it.
+///
+typedef struct ptlist {
+ struct ptlist* next;
+ pthread_t thread_id;
+} ptlist_t;
#endif // INPUT_H
diff --git a/src/ckb-daemon/led_keyboard.c b/src/ckb-daemon/led_keyboard.c
index 4fea1331af..f327a03b4c 100644
--- a/src/ckb-daemon/led_keyboard.c
+++ b/src/ckb-daemon/led_keyboard.c
@@ -220,7 +220,7 @@ int loadrgb_kb(usbdevice* kb, lighting* light, int mode){
if(!usbrecv(kb, data_pkt[i + clr * 4], in_pkt[i]))
return -1;
- uchar* comparePacket = data_pkt[i + clr * 4]; ///> That is the old comparison method: you get back what you sent.
+ uchar* comparePacket = data_pkt[i + clr * 4]; ///< That is the old comparison method: you get back what you sent.
/// Normally a firmware version >= 2.05 runs with the new compare array.
/// Up to now there is a 2.04 running in K70 RGB Lux with the same behavior.
/// It seems that K70RGB has the same problem
diff --git a/src/ckb-daemon/structures.h b/src/ckb-daemon/structures.h
index 9fdd3cf643..4a38920440 100644
--- a/src/ckb-daemon/structures.h
+++ b/src/ckb-daemon/structures.h
@@ -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
@@ -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;
} usbdevice;
#endif // STRUCTURES_H
diff --git a/src/ckb-daemon/usb.c b/src/ckb-daemon/usb.c
index 07e3085f5e..45051fe865 100644
--- a/src/ckb-daemon/usb.c
+++ b/src/ckb-daemon/usb.c
@@ -534,8 +534,10 @@ int _usbsend(usbdevice* kb, const uchar* messages, int count, const char* file,
for(int i = 0; i < count; i++){
// Send each message via the OS function
while(1){
+ pthread_mutex_lock(mmutex(kb)); ///< Synchonization between macro and color information
DELAY_SHORT(kb);
int res = os_usbsend(kb, messages + i * MSG_SIZE, 0, file, line);
+ pthread_mutex_unlock(mmutex(kb));
if(res == 0)
return 0;
else if(res != -1){
@@ -598,15 +600,17 @@ int _usbsend(usbdevice* kb, const uchar* messages, int count, const char* file,
///
int _usbrecv(usbdevice* kb, const uchar* out_msg, uchar* in_msg, const char* file, int line){
// Try a maximum of 5 times
- for(int try = 0; try < 5; try++){
+ for (int try = 0; try < 5; try++) {
// Send the output message
+ pthread_mutex_lock(mmutex(kb)); ///< Synchonization between macro and color information
DELAY_SHORT(kb);
int res = os_usbsend(kb, out_msg, 1, file, line);
- if(res == 0)
+ pthread_mutex_unlock(mmutex(kb));
+ if (res == 0)
return 0;
- else if(res == -1){
+ else if (res == -1) {
// Retry on temporary failure
- if(reset_stop)
+ if (reset_stop)
return 0;
DELAY_LONG(kb);
continue;
@@ -632,7 +636,7 @@ int _usbrecv(usbdevice* kb, const uchar* out_msg, uchar* in_msg, const char* fil
/// An imutex lock ensures first of all, that no communication is currently running from the viewpoint of the driver to the user input device
/// (ie the virtual driver with which characters or mouse movements are sent from the daemon to the operating system as inputs).
///
-/// If the \b kb has an acceptable value = 0,
+/// If the \b kb has an acceptable value != 0,
/// the index of the device is looked for
/// and with this index os_inputclose() is called.
/// After this no more characters can be sent to the operating system.
diff --git a/src/ckb-daemon/usb_linux.c b/src/ckb-daemon/usb_linux.c
index 721ab1e3d4..5f5853d225 100644
--- a/src/ckb-daemon/usb_linux.c
+++ b/src/ckb-daemon/usb_linux.c
@@ -65,9 +65,9 @@ static char kbsyspath[DEV_MAX][FILENAME_MAX];
/// If DEBUG_USB is set during compilation,
/// the number of bytes sent and their representation are logged to the error channel.
///
-int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* file, int line){
+int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* file, int line) {
int res;
- if(kb->fwversion >= 0x120 && !is_recv){
+ if (kb->fwversion >= 0x120 && !is_recv) {
struct usbdevfs_bulktransfer transfer;
memset(&transfer, 0, sizeof(transfer));
transfer.ep = (kb->fwversion >= 0x130 && kb->fwversion < 0x200) ? 4 : 3;
@@ -79,13 +79,14 @@ int os_usbsend(usbdevice* kb, const uchar* out_msg, int is_recv, const char* fil
struct usbdevfs_ctrltransfer transfer = { 0x21, 0x09, 0x0200, kb->epcount - 1, MSG_SIZE, 5000, (void*)out_msg };
res = ioctl(kb->handle - 1, USBDEVFS_CONTROL, &transfer);
}
- if(res <= 0){
- ckb_err_fn("%s\n", file, line, res ? strerror(errno) : "No data written");
+
+ if (res <= 0){
+ ckb_err_fn(" %s, res = 0x%x\n", file, line, res ? strerror(errno) : "No data written", res);
if(res == -1 && errno == ETIMEDOUT)
return -1;
else
return 0;
- } else if(res != MSG_SIZE)
+ } else if (res != MSG_SIZE)
ckb_warn_fn("Wrote %d bytes (expected %d)\n", file, line, res, MSG_SIZE);
#ifdef DEBUG_USB
char converted[MSG_SIZE*3 + 1];
@@ -254,7 +255,7 @@ void* os_inputmain(void* context){
}
/// Get an usbdevfs_urb data structure and clear it via memset()
- struct usbdevfs_urb urbs[urbcount];
+ struct usbdevfs_urb urbs[urbcount + 1];
memset(urbs, 0, sizeof(urbs));
/// Hopefully the buffer lengths are equal for all devices with congruent types.
@@ -299,7 +300,7 @@ void* os_inputmain(void* context){
/// if the ioctl returns something != 0, let's have a deeper look what happened.
/// Broken devices or shutting down the entire system leads to closing the device and finishing this thread.
- if (ioctl(fd, USBDEVFS_REAPURB, &urb)){
+ if (ioctl(fd, USBDEVFS_REAPURB, &urb)) {
if (errno == ENODEV || errno == ENOENT || errno == ESHUTDOWN)
// Stop the thread if the handle closes
break;
@@ -311,12 +312,14 @@ void* os_inputmain(void* context){
ioctl(fd, USBDEVFS_SUBMITURB, urb);
urb = 0;
}
+ continue;
}
/// A correct REAPURB returns a Pointer to the URB which we now have a closer look into.
/// Lock all following actions with imutex.
///
if (urb) {
+
/// Process the input depending on type of device. Interprete the actual size of the URB buffer
///
/// device | detect with macro combination | seems to be endpoint # | actual buffer-length | function called
@@ -367,6 +370,7 @@ void* os_inputmain(void* context){
/// The input data is transformed and copied to the kb structure. Now give it to the OS and unlock the imutex afterwards.
inputupdate(kb);
pthread_mutex_unlock(imutex(kb));
+
/// Re-submit the URB for the next run.
ioctl(fd, USBDEVFS_SUBMITURB, urb);
urb = 0;
@@ -550,8 +554,8 @@ int os_setupusb(usbdevice* kb) {
else
kb->fwversion = 0;
int index = INDEX_OF(kb, keyboard);
- ///
- /// - Do some output abaout connecting interfaces
+
+ /// - Do some output about connecting interfaces
ckb_info("Connecting %s at %s%d\n", kb->name, devpath, index);
///
@@ -561,7 +565,6 @@ int os_setupusb(usbdevice* kb) {
///
const char* ep_str = udev_device_get_sysattr_value(dev, "bNumInterfaces");
#ifdef DEBUG
- ckb_info("Connecting %s at %s%d\n", kb->name, devpath, index);
ckb_info("claiming interfaces. name=%s, serial=%s, firmware=%s; Got >>%s<< as ep_str\n", name, serial, firmware, ep_str);
#endif //DEBUG
kb->epcount = 0;
diff --git a/src/ckb/kbbind.cpp b/src/ckb/kbbind.cpp
index 2519113891..e3e3d36a6f 100644
--- a/src/ckb/kbbind.cpp
+++ b/src/ckb/kbbind.cpp
@@ -244,7 +244,7 @@ void KbBind::update(QFile& cmd, bool force){
if(_winLock)
cmd.write(" unbind lwin rwin");
- // At last, send Macro definitions if avalilable.
+ // At last, send Macro definitions if available.
// If no definitions are made, clear macro will be sent only to reset all macros,
cmd.write(macros.toLatin1());
lastCmd = &cmd;
diff --git a/src/ckb/keyaction.cpp b/src/ckb/keyaction.cpp
index 0e3ce38bc2..4c872f0237 100644
--- a/src/ckb/keyaction.cpp
+++ b/src/ckb/keyaction.cpp
@@ -498,8 +498,8 @@ void KeyAction::adjustDisplay(){
/// macroAction ist called while being in the macro pane
/// and clicking Apply with something in the Macro Text Box.
/// It tags that input with "$macro:" for further recognition.
-/// \param macroDef holds the String containing parts 2-4 of a complete macro definition.
-/// \return QString holding the complete G-Key macro definition (parts 1-4)
+/// \param macroDef holds the String containing parts 2-5 of a complete macro definition.
+/// \return QString holding the complete G-Key macro definition (parts 1-5)
///
QString KeyAction::macroAction(QString macroDef) {
return QString ("$macro:%1").arg(macroDef);
diff --git a/src/ckb/keyaction.h b/src/ckb/keyaction.h
index b12d2d8e50..06d000bb59 100644
--- a/src/ckb/keyaction.h
+++ b/src/ckb/keyaction.h
@@ -37,7 +37,7 @@ class KeyAction : public QObject
/// returns the complete string except the leading "$"
/// (the $ may confuse some caller).
/// \return QString
- /// All 4 parts are returned in one QString.
+ /// All 5 parts are returned in one QString.
/// If no definition exists, return ""
///
inline QString macroFullLine() const {
@@ -47,15 +47,16 @@ class KeyAction : public QObject
//////////
/// \brief isValidMacro checks whether a keyAction contains a valid macro.
/// This is done easily: If the macro action starts with $macro:
- /// and has four elements, delimited by ":", we may assume,
+ /// and has five elements, delimited by ":", we may assume,
/// that is a structural correct macro action.
+ /// If it has 4 entries only, it is an older definition and ok also.
/// \return bool as true iff the macro definition contains all four elements.
///
inline bool isValidMacro() const {
if (isMacro()) {
QStringList ret;
ret =_value.split(":");
- return (ret.count() == 4);
+ return ((ret.count() >= 4) && (ret.count() <= 5));
} else {
return false;
}
@@ -64,8 +65,10 @@ class KeyAction : public QObject
//////////
/// \brief macroLine returns all interresting content for a macro definition.
/// \return QStringList returns the Macro Key Definition,
- /// Readble Macro String and
- /// Readable Macro Comment as QStringList.
+ /// Readble Macro String,
+ /// Readable Macro Comment and
+ /// the original timing information (if it exists as a 5th part)
+ /// as QStringList.
///
inline QStringList macroLine() const {
if (isValidMacro()) {
@@ -81,9 +84,25 @@ class KeyAction : public QObject
/// \return QString macroContent
///
inline QString macroContent() const {
+ // return isValidMacro() ? _value.split(":")[1].replace(QRegExp("=\\d+"), "") : ""; ///< Is used if we have ckb without delay handling
return isValidMacro() ? _value.split(":")[1] : "";
}
+ //////////
+ /// \brief macroTiming returns the macro key definition with original timing infos
+ /// (the fifth and up to now last part of the macro action).
+ /// If the implementation does not know anything about delays and has no 5th part,
+ /// return first part.
+ /// \return QString macroTiming
+ ///
+ inline QString macroTiming() const {
+ if (isValidMacro()) {
+ QStringList rval = _value.split(":");
+ return (rval.length() == 4)? rval[1] : rval[4];
+ }
+ return QString("");
+ }
+
//////////
/// \brief Debug output for invalid macro Definitions
///
@@ -94,6 +113,7 @@ class KeyAction : public QObject
/// This sequence will program the keyboard and is hardly readable
/// 3. Readable Macro String: This is displayed in pteMacroText
/// 4. Readable Macro Comment:This is displayed in pteMacroComment
+ /// 5. completely unreadable original macro information with timing values
///
void macroDisplay();
diff --git a/src/ckb/macroreader.cpp b/src/ckb/macroreader.cpp
index 3a1d411148..d226cf0c23 100644
--- a/src/ckb/macroreader.cpp
+++ b/src/ckb/macroreader.cpp
@@ -1,5 +1,6 @@
#include
#include "macroreader.h"
+#include
//////////
/// \class MacroReader
@@ -63,9 +64,26 @@ void MacroReaderThread::run() {
}
}
// Read data from notification node macroPath
+ // Count time between lines read from the interface
QByteArray line;
+ timeval t;
+ gettimeofday(&t, NULL);
+ double tstart = t.tv_sec+(t.tv_usec/1000000.0);
+ bool firstline = true;
+
while(macroFile.isOpen() && (line = macroFile.readLine()).length() > 0){
QString text = QString::fromUtf8(line);
+ gettimeofday(&t, NULL);
+ double tnow = t.tv_sec+(t.tv_usec/1000000.0);
+
+ // in the first line, there is only a delay "before start". Don't use it.
+ if (!firstline) {
+ text.prepend ("\n");
+ text.prepend (QString::number ((tnow - tstart) * 1000000.0, 'f', 0));
+ text.prepend ("=");
+ } else firstline = false;
+ tstart = tnow;
+
metaObject()->invokeMethod(this, "readMacro", Qt::QueuedConnection, Q_ARG(QString, text));
}
qDebug() << "MacroReader::run() ends.";
diff --git a/src/ckb/rebindwidget.cpp b/src/ckb/rebindwidget.cpp
index ee4e5f1206..7b4ed70230 100644
--- a/src/ckb/rebindwidget.cpp
+++ b/src/ckb/rebindwidget.cpp
@@ -192,6 +192,7 @@ void RebindWidget::setSelection(const QStringList& newSelection, bool applyPrevi
ui->pteMacroBox->setPlainText("");
ui->pteMacroText->setPlainText("");
ui->pteMacroComment->setPlainText("");
+ ui->txtBuffer->setText("");
// Fill in field and select tab according to action type
bool mouse = act.isMouse();
if(mouse){
@@ -294,6 +295,12 @@ void RebindWidget::setSelection(const QStringList& newSelection, bool applyPrevi
ui->pteMacroBox->setPlainText(act.macroContent());
ui->pteMacroText->setPlainText(act.macroLine()[1].replace("&das_IST_31N_col0n;", ":"));
ui->pteMacroComment->setPlainText(act.macroLine()[2].replace("&das_IST_31N_col0n;", ":"));
+ // Set the invisible Buffer to the original timing information.
+ // For convenience / Migration from older versions:
+ // If the timing information is only "x", then ignore it by setting it to an empty QString.
+ ui->txtBuffer->setText("");
+ if (act.macroTiming() != "x") ui->txtBuffer->setText(act.macroTiming());
+ setCorrectRadioButton(act.macroContent());
} else {
qDebug("RebindWidget::setSelection found invalid macro definition.");
act.macroDisplay();
@@ -370,12 +377,25 @@ void RebindWidget::applyChanges(const QStringList& keys, bool doUnbind){
}
bind->setAction(keys, KeyAction::programAction(ui->programKpBox->text(), ui->programKrBox->text(), kpStop | krStop));
} else if (ui->pteMacroBox->toPlainText().length() > 0) {
- // G-key macro handling:
- // Set the macro definiton for all keys selected (indeed, it may be multiple keys).
- // First, concat the Macro Key Definion and the Macro plain text
- // after escaping possible colos in the parts for Macro Text and Macro Comment.
+ /// G-key macro handling:
+ /// Set the macro definiton for all keys selected (indeed, it may be multiple keys).
+ /// First, concat the Macro Key Definion and the Macro plain text
+ /// after escaping possible colos in the parts for Macro Text and Macro Comment.
+
+ /// But first, there is a special condition to handle:
+ /// You have recorded a macro with timing infos.
+ /// Afterwards you changed manually the timing infos in the pteMacroBox and press Apply.
+ /// In that case we must overwrite the txtBuffer to remember your changes.
+ if (ui->rb_delay_asTyped->isChecked()) ui->txtBuffer->setText(ui->pteMacroBox->toPlainText());
+
+ /// \todo There is still a bug in the state machine:
+ /// If you record a macro in asTyped-mode, switch to another mode
+ /// and change the vontent of the pteMacroBox manually,
+ /// then the changes are not saved in the timing buffer.
+ /// But anyhow, let's do more relevant things...
QString mac;
- mac = ui->pteMacroComment->toPlainText().replace(":", "&das_IST_31N_col0n;");
+ mac = ui->txtBuffer->text();
+ mac = ui->pteMacroComment->toPlainText().replace(":", "&das_IST_31N_col0n;") + ":" + mac;
mac = ui->pteMacroText->toPlainText().replace(":", "&das_IST_31N_col0n;") + ":" + mac;
mac = ui->pteMacroBox->toPlainText() + ":" + mac;
bind->setAction(keys, KeyAction::macroAction(mac));
@@ -433,6 +453,7 @@ void RebindWidget::setBox(QWidget* box){
// Clear macro panel
if (box != ui->pteMacroBox) {
ui->pteMacroBox->setPlainText("");
+ ui->txtBuffer->setText("");
helpStatus(1);
}
}
@@ -711,13 +732,16 @@ void RebindWidget::on_btnStartMacro_clicked() {
ui->unbindButton->setEnabled(false);
ui->btnStartMacro->setEnabled(false);
ui->btnStopMacro->setEnabled(true);
+ ui->rb_delay_asTyped->setEnabled(false);
+ ui->rb_delay_no->setEnabled(false);
+ ui->rb_delay_default->setEnabled(false);
helpStatus(2);
}
}
//////////
/// \brief RebindWidget::on_btnStopMacro_clicked ends the macro recording.
-/// Notify channel ist closed, the ReaderThread is deleted if the notification is really down.
+/// Notify channel ist closed, the ReaderThread is deleted when the notification is really down.
///
/// Afterwards, the characters in the MacroBox are changed from KB-out format to cmd-in format.
/// At last the UI changes to the new state.
@@ -733,6 +757,9 @@ void RebindWidget::on_btnStopMacro_clicked() {
ui->unbindButton->setEnabled(true);
ui->btnStartMacro->setEnabled(true);
ui->btnStopMacro->setEnabled(false);
+ ui->rb_delay_asTyped->setEnabled(true);
+ ui->rb_delay_no->setEnabled(true);
+ ui->rb_delay_default->setEnabled(true);
helpStatus(3);
}
}
@@ -741,6 +768,9 @@ void RebindWidget::on_btnStopMacro_clicked() {
/// \brief RebindWidget::on_btnClearMacro_clicked changes the help info an the panel.
/// The job of clearing the input panels is triggerd with signal/slot via the RebindWidget.ui file.
///
+/// \todo I do not know what is the better solution with the delay-buttons in case of clicking clear:
+/// Reset the button to the default value or do not touch it? Not clear is ignored.
+///
void RebindWidget::on_btnClearMacro_clicked() {
helpStatus(1);
}
@@ -768,16 +798,100 @@ void RebindWidget::helpStatus(int status) {
//////////
/// \brief RebindWidget::convertMacroBox converts the macroBox content.
/// The KB sends each keypress as "key [+|-]"
+/// This is followed by timing information (delays between keystrokes).
///
-/// the ckb-daemon needs a shorter format, only " [+|-]"
+/// The ckb-daemon needs a shorter format, only " [+|-]=",
+/// multiple entries are separated by comma.
///
/// That function does the conversion.
///
void RebindWidget::convertMacroBox() {
QString in;
- in = ui->pteMacroBox->toPlainText();
- in.replace (QRegExp("\n"), ",");
- in.replace (QRegExp("key "), "");
+ // Remember the original input stream before it is converted.
+ // In case of new choice of delay mode we have to restore it.
+ if (ui->txtBuffer->text() == "") {
+ ui->txtBuffer->setText(ui->pteMacroBox->toPlainText());
+ in = ui->pteMacroBox->toPlainText();
+ } else in = ui->txtBuffer->text();
+
+ in.replace (QRegExp("\n"), ","); // first join all in one line
+ in.replace (QRegExp("key "), ""); // then delete keyword "key" followed by space
+ in.replace (QRegExp(",="), "="); // at last join each keystroke with its delay parameter
+
+ // How to deal with the delay params?
+ // Because the three radio buttons are mututally exclusive,
+ // we can run through the if-chain w/o conflicts.
+ // If rb_delay_asTyped is checked, do nothing, because that's the standard.
+
+ if (ui->rb_delay_default->isChecked()) {
+ in.replace(QRegExp("=\\d+,"), ","); // Delete the timing infos, use default value
+ in.replace(QRegExp("=\\d+$"), ""); // The last entry is without comma
+ }
+ if (ui->rb_delay_no->isChecked()) {
+ in.replace(QRegExp("=\\d+,"), "=0,"); // Set timing infos to zero for no delay
+ in.replace(QRegExp("=\\d+$"), "=0"); // Again the last entry w/o comma
+ in.replace(QRegExp("([\\+\\-]\\w+),"), "\\1=0,"); // If no delay is given, force it to zero
+ in.replace(QRegExp("([\\+\\-]\\w+)$"), "\\1=0");
+ }
+
+ // Show the new format by replacing the older one.
ui->pteMacroBox->setPlainText(in);
}
+
+//////////
+/// \brief RebindWidget::on_rb_delay_no_toggled
+/// \param checked
+/// The following slots are triggerd by changing the mutual exclusive radio buttons
+/// when choosing the delay.
+/// They are called, if the button ist enabled.
+/// This first one should disable all delay.
+///
+void RebindWidget::on_rb_delay_no_toggled(bool checked)
+{
+ convertMacroBox();
+}
+
+//////////
+/// \brief RebindWidget::on_rb_delay_asTyped_toggled
+/// \param checked
+/// This button ist clicked to use the delay times, as they are recorded.
+/// Returs a warning message, if we are not in the recording phase,
+/// because then we don't have the delay times any more.
+///
+void RebindWidget::on_rb_delay_asTyped_toggled(bool checked)
+{
+ convertMacroBox();
+}
+
+//////////
+/// \brief RebindWidget::on_rb_delay_default_toggled
+/// \param checked
+/// This is as easy as the no-delay-button, because this means
+/// take the default values.
+///
+void RebindWidget::on_rb_delay_default_toggled(bool checked)
+{
+ convertMacroBox();
+}
+
+//////////
+/// \brief RebindWidget::setCorrectRadioButton
+/// \param macdef
+/// Set the radiobutton for timing paramters according to
+/// the context.
+/// If no "=" followed by a number and comma can be found, it is the default button.
+/// If "=" can be found and numbers with more than one digit (means: > 9), it is the "asTyped" button
+/// Otherwise it is the "no" button.
+///
+void RebindWidget::setCorrectRadioButton (QString macdef) {
+ if (!macdef.contains(QRegExp("=\\d+,"))) {
+ ui->rb_delay_default->setChecked(true);
+ return;
+ }
+ if (macdef.contains(QRegExp("=\\d\\d+,"))) {
+ ui->rb_delay_asTyped->setChecked(true);
+ return;
+ }
+ ui->rb_delay_no->setChecked(true);
+}
diff --git a/src/ckb/rebindwidget.h b/src/ckb/rebindwidget.h
index 4fe8495356..ebb0fec411 100644
--- a/src/ckb/rebindwidget.h
+++ b/src/ckb/rebindwidget.h
@@ -1,5 +1,5 @@
-#ifndef BINDDIALOG_H
-#define BINDDIALOG_H
+#ifndef REBINDWIDGET_H
+#define REBINDWIDGET_H
#include
#include "kbbind.h"
@@ -70,6 +70,12 @@ private slots:
void on_btnStopMacro_clicked();
void on_btnClearMacro_clicked();
+ void on_rb_delay_no_toggled(bool checked);
+
+ void on_rb_delay_asTyped_toggled(bool checked);
+
+ void on_rb_delay_default_toggled(bool checked);
+
private:
Ui::RebindWidget *ui;
@@ -83,6 +89,8 @@ private slots:
// Show some help info
void helpStatus(int status);
+ void setCorrectRadioButton (QString macdef);
+
KbBind* bind;
KbProfile* profile;
QStringList selection;
@@ -98,4 +106,4 @@ private slots:
MacroReader* macReader; ///< \brief macReader holds the MacroReader when macro recording starts.
};
-#endif // BINDDIALOG_H
+#endif // REBINDWIDGET_H
diff --git a/src/ckb/rebindwidget.ui b/src/ckb/rebindwidget.ui
index f88c9663c1..4c6d42862e 100644
--- a/src/ckb/rebindwidget.ui
+++ b/src/ckb/rebindwidget.ui
@@ -16,12 +16,24 @@
0
+
+ font-size: 12px ;
+
-
+
+
+
0
+
+ false
+
+
+ false
+
@@ -1239,25 +1251,34 @@
40
12
241
- 171
+ 161
+
+ QLayout::SetNoConstraint
+
-
Macro Key Actions
-
-
- -
-
-
- Qt::WheelFocus
-
-
- true
-
+
+
+
+ 0
+ 20
+ 241
+ 131
+
+
+
+ Qt::WheelFocus
+
+
+ true
+
+
@@ -1268,8 +1289,8 @@
- 420
- 20
+ 410
+ 10
311
31
@@ -1317,10 +1338,10 @@
- 420
- 60
+ 410
+ 50
311
- 61
+ 66
@@ -1355,12 +1376,15 @@
300
- 20
- 119
- 81
+ 10
+ 111
+ 71
+
+ QLayout::SetNoConstraint
+
-
@@ -1382,25 +1406,25 @@
300
160
- 436
- 30
+ 407
+ 36
-
-
-
-
- Macro recording
-
-
-
-
false
- Stop
+ Stop
+
+
+
+ -
+
+
+ Clear
@@ -1411,10 +1435,14 @@
- -
-
+
-
+
+
+ font: 75 10pt ;
+
+
- Clear
+ Record macro
@@ -1429,16 +1457,144 @@
27
+
+ font: 75 11pt ; color: darkblue;
+
Comment label for help
- pteMacroComment
+
+
+
+ 40
+ 170
+ 238
+ 28
+
+
+
+
+ 0
+
+
+ QLayout::SetMinimumSize
+
+ -
+
+
+ configure delays between keystrokes: Disable delay
+
+
+ font: 10pt ; color: green;
+
+
+ No delay
+
+
+
+ -
+
+
+ configure delays between keystrokes: Set delay to default values: 20us up to 15 chars, 200us above
+
+
+ font: 10pt ; color: green;
+
+
+ default
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ configure delays between keystrokes: Remember delays as they had been typed while the macro definition process
+
+
+ font: 10pt ; color: green;
+
+
+ as typed
+
+
+ true
+
+
+
+
+
+
+
+ false
+
+
+
+ 300
+ 100
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::NoContextMenu
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+ QFrame::Plain
+
+
+ 0
+
+
+
+
+
+ Qt::PlainText
+
+
+ 0
+
+
+ Qt::NoTextInteraction
+
+
+ layoutWidget
layoutWidget1
layoutWidget
pteMacroText
layoutWidget_2
lbl_macro
+ txtBuffer
@@ -1548,82 +1704,130 @@
- cancelButton
+ btnClearMacro
clicked()
- pteMacroBox
+ pteMacroText
clear()
- 735
- 331
+ 711
+ 238
- 252
- 201
+ 705
+ 131
btnClearMacro
clicked()
- pteMacroBox
+ pteMacroComment
clear()
- 702
- 206
+ 711
+ 238
- 270
- 179
+ 731
+ 78
btnClearMacro
clicked()
- pteMacroText
+ btnStopMacro
+ animateClick()
+
+
+ 711
+ 238
+
+
+ 618
+ 238
+
+
+
+
+ cancelButton
+ clicked()
+ txtBuffer
clear()
- 718
- 206
+ 564
+ 320
- 705
- 131
+ 319
+ 143
btnClearMacro
clicked()
- pteMacroComment
+ txtBuffer
clear()
- 723
- 201
+ 655
+ 219
- 722
- 85
+ 339
+ 154
- btnClearMacro
+ btnStartMacro
clicked()
- btnStopMacro
- animateClick()
+ txtBuffer
+ clear()
+
+
+ 485
+ 228
+
+
+ 352
+ 156
+
+
+
+
+ unbindButton
+ clicked()
+ txtBuffer
+ clear()
+
+
+ 361
+ 313
+
+
+ 332
+ 142
+
+
+
+
+ resetButton
+ clicked()
+ txtBuffer
+ clear()
- 671
- 208
+ 452
+ 310
- 612
- 206
+ 348
+ 142