mirror of
https://github.com/ckb-next/ckb-next.git
synced 2026-10-10 12:27:21 -04:00
Merge remote-tracking branch 'aerusso/quick-press'
This commit is contained in:
@@ -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
@@ -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(¯oint[i], &monotonic_condattr))
|
||||
return 1;
|
||||
}
|
||||
|
||||
pthread_condattr_destroy(&monotonic_condattr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// \brief .
|
||||
///
|
||||
|
||||
@@ -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
@@ -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(¯os[i]);
|
||||
// If the new macro has no actions, erase the existing one
|
||||
if(!macro.actioncount){
|
||||
for(int j = i + 1; j < bind->macrocount; j++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()");
|
||||
|
||||
@@ -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()");
|
||||
|
||||
Reference in New Issue
Block a user