Merge remote-tracking branch 'aerusso/quick-press'

This commit is contained in:
Tasos Sahanidis
2021-03-05 13:18:24 +02:00
9 changed files with 188 additions and 33 deletions
+5
View File
@@ -224,7 +224,12 @@ int readcmd(usbdevice* kb, const char* line){
continue;
case SWITCH:
if(profile->currentmode != mode){
queued_mutex_lock(imutex(kb));
binding* bind = &profile->currentmode->bind;
for (int i = 0; i < bind->macrocount; i++)
bind->macros[i].triggered = 0;
profile->currentmode = mode;
queued_mutex_unlock(imutex(kb));
// Set mode light for non-RGB K95
int index = INDEX_OF(mode, profile->mode);
vt->setmodeindex(kb, index);
+64 -1
View File
@@ -13,8 +13,25 @@ queued_mutex_t inputmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = QUEUED_MUTEX_INITIALI
queued_mutex_t macromutex[DEV_MAX] = { [0 ... DEV_MAX-1] = QUEUED_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.
pthread_cond_t macroint[DEV_MAX]; ///< Should a macro thread's sleep be interrupted, due to repeated key press?
pthread_mutex_t interruptmutex[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_MUTEX_INITIALIZER };///< Used for interrupt transfers
pthread_cond_t interruptcond[DEV_MAX] = { [0 ... DEV_MAX-1] = PTHREAD_COND_INITIALIZER }; ///< Same as above
pthread_cond_t interruptcond[DEV_MAX]; ///< Same as above
///
/// \brief cond_nanosleep matches semantics of pthread_cond_timedwait, but with a relative wake time
/// \param cond as pthread_cond_timedwait, but must use CLOCK_MONOTONIC
/// \param mutex as pthread_cond_timedwait
/// \param ns the maximum duration of sleep, in nanoseconds
/// \return as pthread_cond_timedwait. returns ENOTSUP if clock_gettime fails
///
int cond_nanosleep(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, uint32_t ns) {
struct timespec ts = { 0 };
if(clock_gettime(CLOCK_MONOTONIC, &ts))
return ENOTSUP;
timespec_add(&ts, ns);
return pthread_cond_timedwait(cond, mutex, &ts);
}
void queued_mutex_lock(queued_mutex_t* mutex){
pthread_mutex_lock(&mutex->mutex);
@@ -49,6 +66,52 @@ void queued_mutex_unlock(queued_mutex_t* mutex){
pthread_cond_broadcast(&mutex->cond);
}
///
/// \brief queued_cond_nanosleep matches semantics of cond_nanosleep, but accepts a queued_mutex_t instead of a mutex
/// \param cond as cond_nanosleep
/// \param mutex as cond_nanosleep, but is a queued_mutex_t
/// \param ns as cond_nanosleep
/// \return as queued_cond_timedwait
///
void queued_cond_nanosleep(pthread_cond_t *restrict cond,
queued_mutex_t *restrict mutex, const uint32_t ns) {
pthread_mutex_lock(&mutex->mutex);
// release mutex
mutex->next_in++;
pthread_cond_broadcast(&mutex->cond);
// perform the sleep
cond_nanosleep(cond, &mutex->mutex, ns);
// reacquire mutex
unsigned long my_turn = mutex->next_waiting++;
while(my_turn != mutex->next_in)
pthread_cond_wait(&mutex->cond, &mutex->mutex);
pthread_mutex_unlock(&mutex->mutex);
}
/// Initialize pthread_cond's with a monotonic clock, if possible
int init_cond_monotonic(void) {
pthread_condattr_t monotonic_condattr;
if(pthread_condattr_init(&monotonic_condattr) ||
pthread_condattr_setclock(&monotonic_condattr, CLOCK_MONOTONIC))
return 1;
// pthread_cond_init
for(int i = 0 ; i < DEV_MAX ; i++) {
if(pthread_cond_init(&interruptcond[i], &monotonic_condattr) ||
pthread_cond_init(&macroint[i], &monotonic_condattr))
return 1;
}
pthread_condattr_destroy(&monotonic_condattr);
return 0;
}
/// \brief .
///
+6
View File
@@ -28,6 +28,8 @@ typedef struct queued_mutex{
void queued_mutex_lock(queued_mutex_t* mutex); // Lock a queued_mutex
int queued_mutex_trylock(queued_mutex_t* mutex); // Try to lock a queued_mutex without blocking; returns 0 on success.
void queued_mutex_unlock(queued_mutex_t* mutex); // Unlock a queued_mutex
void queued_cond_nanosleep(pthread_cond_t *restrict cond, queued_mutex_t *restrict mutex, const uint32_t ns);
int cond_nanosleep(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, uint32_t ns);
#ifdef DEBUG_MUTEX
#define MUTEX_DBG(str, kb, mutexarray) (ckb_info(str " accessed in %s:%d (%s) at ckb%d, TID 0x%lx, MID %p", __FILE__, __LINE__, __func__, INDEX_OF(kb, keyboard), pthread_self(), mutexarray + INDEX_OF(kb, keyboard)) & 0)
@@ -49,6 +51,8 @@ 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))
extern pthread_cond_t macroint[DEV_MAX];
#define mintvar(kb) (macroint + INDEX_OF(kb, keyboard))
// Mutex used for transfering URB Interrupt data between threads
extern pthread_mutex_t interruptmutex[DEV_MAX];
@@ -57,6 +61,8 @@ extern pthread_mutex_t interruptmutex[DEV_MAX];
extern pthread_cond_t interruptcond[DEV_MAX];
#define intcond(kb) (interruptcond + INDEX_OF(kb, keyboard))
int init_cond_monotonic(void);
// 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);
+91 -26
View File
@@ -78,9 +78,9 @@ static pthread_t macro_pt_first() {
// Default macro keystroke delay
const struct timespec macrodelay = { .tv_nsec = 1000000 };
// Initial repeat delay
const struct timespec init_repeat_delay = { .tv_nsec = 500000000 };
#define DELAY_REPEAT_INITIAL 500000000
// Delay for every subsequent repeat
const struct timespec catchup_repeat_delay = { .tv_nsec = 50000000 };
#define DELAY_REPEAT_CATCHUP 500000000
static inline void clock_microsleep(uint32_t s) {
const struct timespec ts = {
@@ -116,12 +116,16 @@ static void* play_macro(void* param) {
}
pthread_mutex_unlock(mmutex2(kb)); ///< Give all new threads the chance to enter the block.
int firstloop = 1;
/*
* Is this the first time the macro is being repeated due to the key being
* held down continuously?
*/
int first_keydownloop = 1;
while(1){
/// Send events for each keypress in the macro
queued_mutex_lock(mmutex(kb)); ///< Synchonization between macro output and color information
for (int a = 0; a < macro->actioncount; a++) {
macroaction* action = macro->actions + a;
for (int a = 0; a < ptr->actioncount; a++) {
macroaction* action = ptr->actions + a;
if (action->rel_x != 0 || action->rel_y != 0)
os_mousemove(kb, action->rel_x, action->rel_y);
else {
@@ -150,24 +154,49 @@ static void* play_macro(void* param) {
}
queued_mutex_unlock(mmutex(kb));
// Check if the same combination is still held down.
// If the user let go and pressed again, it's okay, because we still need to repeat.
// The other threads will still be queued up and run after this.
// Maybe in the future we can add an option to prevent new threads with the same combo.
// Additionally, wait for a bit, because otherwise short macros will be repeated even if pressed only once
if(firstloop){
clock_nanosleep(CLOCK_MONOTONIC, 0, &init_repeat_delay, NULL);
firstloop = 0;
} else {
// Give some time for userspace applications to catch up
clock_nanosleep(CLOCK_MONOTONIC, 0, &catchup_repeat_delay, NULL);
}
int delay_ns = first_keydownloop ? DELAY_REPEAT_INITIAL : DELAY_REPEAT_CATCHUP;
queued_mutex_lock(imutex(kb));
const int retrigger = macromask(kb->input.keys, macro->combo);
queued_mutex_unlock(imutex(kb));
if(!retrigger)
// detect if the macro playback has been aborted
if (ptr->abort) {
// we are responsible for free-ing actions.
free(ptr->actions);
break;
}
if (macro->triggered > 1) {
macro->triggered -= 2;
delay_ns = 0;
first_keydownloop = 1;
}
if (macro->triggered == 0)
break;
if (!delay_ns) {
queued_mutex_unlock(imutex(kb));
continue;
}
queued_cond_nanosleep(mintvar(kb), imutex(kb), delay_ns);
if (macro->triggered == 2) {
// the key was released, but not pressed again, during the sleep
macro->triggered = 0;
break;
}
// if the key released and pressed during the sleep, reset to use DELAY_REPEAT_INITIAL
if (macro->triggered == 1)
first_keydownloop = 0;
queued_mutex_unlock(imutex(kb));
}
if (!ptr->abort)
macro->running = NULL;
queued_mutex_unlock(imutex(kb));
queued_mutex_lock(mmutex(kb));
pthread_mutex_lock(mmutex2(kb)); ///< protect the linked list and the mvar
@@ -197,8 +226,24 @@ static void inputupdate_keys(usbdevice* kb){
if (kb->active) {
for (int i = 0; i < bind->macrocount; i++) {
keymacro* macro = &bind->macros[i];
// see the definition of keymacro.triggered in structures.h
if (macromask(input->keys, macro->combo)) {
if (!macro->triggered) {
if(!(macro->triggered & 1))
macro->triggered += 1;
else
continue;
} else {
if((macro->triggered & 1)) {
macro->triggered += 1;
pthread_cond_broadcast(mintvar(kb));
}
continue;
}
if (macro->triggered <= 3) {
// start up a thread if there isn't already one running
if (!macro->running) {
// assert(macro->triggered == 1)
parameter_t* params = malloc(sizeof(parameter_t));
if (params == NULL) {
perror("inputupdate_keys got no more mem:");
@@ -206,12 +251,17 @@ static void inputupdate_keys(usbdevice* kb){
pthread_t thread = 0;
params->kb = kb;
params->macro = macro;
params->actions = macro->actions;
params->actioncount = macro->actioncount;
params->abort = 0;
macro->running = (void *)params;
int retval = pthread_create(&thread, 0, play_macro, (void*)params);
if (retval) {
macro->triggered = 0;
macro->running = NULL;
perror("inputupdate_keys: Creating thread returned not null");
} else {
pthread_detach(thread);
macro->triggered = 1;
#ifndef OS_MAC
// name thread externally if it was created on non-mac systems
@@ -220,8 +270,12 @@ static void inputupdate_keys(usbdevice* kb){
#endif // OS_MAC
}
}
} else {
// if there is already a thread running, it may be waiting for DELAY_REPEAT_*
// it does not need to wait anymore
pthread_cond_broadcast(mintvar(kb));
}
} else macro->triggered = 0;
}
}
}
// Make a list of keycodes to send. Rearrange them so that modifier keydowns always come first
@@ -351,6 +405,17 @@ void updateindicators_kb(usbdevice* kb, int force){
}
}
///
/// \brief destroymacro free actions if macro is not currently running
/// \param macro
///
static inline void destroymacro(keymacro* macro) {
if (macro->running)
((parameter_t *)macro->running)->abort = 1;
else
free(macro->actions);
}
void initbind(binding* bind, usbdevice* kb){
for(int i = 0; i < N_KEYS_INPUT; i++)
bind->base[i] = kb->keymap[i].scan;
@@ -361,7 +426,7 @@ void initbind(binding* bind, usbdevice* kb){
void freebind(binding* bind){
for(int i = 0; i < bind->macrocount; i++)
free(bind->macros[i].actions);
destroymacro(&bind->macros[i]);
free(bind->macros);
memset(bind, 0, sizeof(*bind));
}
@@ -417,7 +482,7 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment,
if(!keys && !assignment){
// Null strings = "macro clear" -> erase the whole thing
for(int i = 0; i < bind->macrocount; i++)
free(bind->macros[i].actions);
destroymacro(&bind->macros[i]);
bind->macrocount = 0;
return;
}
@@ -502,7 +567,7 @@ static void _cmd_macro(usbmode* mode, const char* keys, const char* assignment,
keymacro* macros = bind->macros;
for(int i = 0; i < bind->macrocount; i++){
if(!memcmp(macros[i].combo, macro.combo, N_KEYBYTES_INPUT)){
free(macros[i].actions);
destroymacro(&macros[i]);
// If the new macro has no actions, erase the existing one
if(!macro.actioncount){
for(int j = i + 1; j < bind->macrocount; j++)
+3
View File
@@ -54,6 +54,9 @@ int os_setupindicators(usbdevice* kb);
typedef struct parameter {
usbdevice* kb;
keymacro* macro;
macroaction* actions;
int actioncount;
char abort;
} parameter_t;
/// \brief struct ptlist is one element in the single linked list to store macro_play threads waiting for their execution
+5
View File
@@ -290,6 +290,11 @@ int main(int argc, char** argv){
srand(time(NULL));
if(init_cond_monotonic()) {
ckb_fatal("Failed to initialize monotonic clock.");
exit(1);
}
// Start the USB system
int result = usbmain();
quit();
+12
View File
@@ -40,7 +40,19 @@ typedef struct {
macroaction* actions;
int actioncount;
uchar combo[N_KEYBYTES_INPUT];
/*
* The number of times the key has been pressed and released. It is therefore
* even if the key is up, and odd if the key is down. Whenever the macro execution
* completes:
* - The counter is decremented by two, unless it is at 1.
* - If state > 0, the macro is repeated, with a delay if state == 1.
*
* If the key is released during the delay, the counter is set to 0, and the running
* thread shuts itself down immediately.
*/
char triggered;
void* running; // pointer to the parameter_t of the currently running thread (null if none)
} keymacro;
// Key bindings for a mode (keyboard + mouse)
+1 -3
View File
@@ -169,9 +169,7 @@ int os_usbrecv(usbdevice* kb, uchar* in_msg, const char* file, int line){
int res;
if(kb->fwversion >= 0x120 || IS_V2_OVERRIDE(kb)){
// Wait for 2s
struct timespec condwait = {0};
condwait.tv_sec = time(NULL) + 2;
int condret = pthread_cond_timedwait(intcond(kb), intmutex(kb), &condwait);
int condret = cond_nanosleep(intcond(kb), intmutex(kb), 2000000000);
if(condret != 0){
if(pthread_mutex_unlock(intmutex(kb)))
ckb_fatal("Error unlocking interrupt mutex in os_usbrecv()");
+1 -3
View File
@@ -189,10 +189,8 @@ int os_usbrecv(usbdevice* kb, uchar* in_msg, const char* file, int line){
// Read the data from the input thread
if((kb->fwversion >= 0x120 || IS_V2_OVERRIDE(kb))) {
struct timespec condwait = {0, 0};
// Wait for 2s
condwait.tv_sec = time(NULL) + 2;
int condret = pthread_cond_timedwait(intcond(kb), intmutex(kb), &condwait);
int condret = cond_nanosleep(intcond(kb), intmutex(kb), 2000000000);
if(condret != 0){
if(pthread_mutex_unlock(intmutex(kb)))
ckb_fatal("Error unlocking interrupt mutex in os_usbrecv()");