From c1e8ee9ec104631f1b50363ebcc38b982f26d9f2 Mon Sep 17 00:00:00 2001 From: koproductions Date: Tue, 20 Jan 2026 02:28:02 +0100 Subject: [PATCH 01/10] feat: add multitouch gesture support as hotkeys using trackpad --- makefile | 5 +- src/hotkey.c | 2 +- src/hotkey.h | 20 ++ src/multitouch.h | 145 +++++++++++++ src/parse.c | 20 +- src/skhd.c | 4 + src/tokenize.h | 13 +- src/touch.c | 525 +++++++++++++++++++++++++++++++++++++++++++++++ src/touch.h | 13 ++ 9 files changed, 739 insertions(+), 8 deletions(-) create mode 100644 src/multitouch.h create mode 100644 src/touch.c create mode 100644 src/touch.h diff --git a/makefile b/makefile index 19c17e7..bdc772d 100644 --- a/makefile +++ b/makefile @@ -1,4 +1,5 @@ -FRAMEWORKS = -framework Cocoa -framework Carbon -framework CoreServices +FRAMEWORK_PATH = -F/System/Library/PrivateFrameworks +FRAMEWORKS = -framework Cocoa -framework Carbon -framework CoreServices -framework MultitouchSupport -framework CoreFoundation BUILD_PATH = ./bin BUILD_FLAGS = -std=c99 -Wall -g -O0 SKHD_SRC = ./src/skhd.c @@ -16,4 +17,4 @@ clean: $(BUILD_PATH)/skhd: $(SKHD_SRC) mkdir -p $(BUILD_PATH) - clang $^ $(BUILD_FLAGS) $(FRAMEWORKS) -o $@ + clang $^ $(BUILD_FLAGS) $(FRAMEWORK_PATH) $(FRAMEWORKS) -o $@ diff --git a/src/hotkey.c b/src/hotkey.c index 6ed79f5..9e1aec3 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -252,7 +252,7 @@ cgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags eventflags, uint32_t *flags } } -static uint32_t +uint32_t cgevent_flags_to_hotkey_flags(uint32_t eventflags) { uint32_t flags = 0; diff --git a/src/hotkey.h b/src/hotkey.h index 78aba3f..4e3200d 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -11,6 +11,25 @@ #define Modifier_Keycode_Ctrl 0x3B #define Modifier_Keycode_Fn 0x3F +#define Gesture_Keycode_Base 0x10000000 +#define Gesture_ThreeFingerSwipeLeft (Gesture_Keycode_Base + 1) +#define Gesture_ThreeFingerSwipeRight (Gesture_Keycode_Base + 2) +#define Gesture_ThreeFingerSwipeUp (Gesture_Keycode_Base + 3) +#define Gesture_ThreeFingerSwipeDown (Gesture_Keycode_Base + 4) +#define Gesture_ThreeFingerTap (Gesture_Keycode_Base + 5) +#define Gesture_FourFingerSwipeLeft (Gesture_Keycode_Base + 6) +#define Gesture_FourFingerSwipeRight (Gesture_Keycode_Base + 7) +#define Gesture_FourFingerSwipeUp (Gesture_Keycode_Base + 8) +#define Gesture_FourFingerSwipeDown (Gesture_Keycode_Base + 9) +#define Gesture_FiveFingerSwipeLeft (Gesture_Keycode_Base + 10) +#define Gesture_FiveFingerSwipeRight (Gesture_Keycode_Base + 11) +#define Gesture_FiveFingerSwipeUp (Gesture_Keycode_Base + 12) +#define Gesture_FiveFingerSwipeDown (Gesture_Keycode_Base + 13) +#define Gesture_FourFingerTap (Gesture_Keycode_Base + 14) +#define Gesture_FiveFingerTap (Gesture_Keycode_Base + 15) +#define Gesture_TipTapLeft (Gesture_Keycode_Base + 16) +#define Gesture_TipTapRight (Gesture_Keycode_Base + 17) + enum osx_event_mask { Event_Mask_Alt = 0x00080000, @@ -105,6 +124,7 @@ unsigned long hash_hotkey(struct hotkey *a); struct hotkey create_eventkey(CGEventRef event); bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey); +uint32_t cgevent_flags_to_hotkey_flags(uint32_t eventflags); bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon); void free_mode_map(struct table *mode_map); diff --git a/src/multitouch.h b/src/multitouch.h new file mode 100644 index 0000000..fd795ec --- /dev/null +++ b/src/multitouch.h @@ -0,0 +1,145 @@ +// Copyright (C) 2009 Fajran Iman Rusadi + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef MULTITOUCH_H +#define MULTITOUCH_H + +#ifdef __OBJC__ +#include +#endif +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct { + float x; + float y; + } MTPoint; + + typedef struct { + MTPoint position; + MTPoint velocity; + } MTVector; + + enum { + MTTouchStateNotTracking = 0, + MTTouchStateStartInRange = 1, + MTTouchStateHoverInRange = 2, + MTTouchStateMakeTouch = 3, + MTTouchStateTouching = 4, + MTTouchStateBreakTouch = 5, + MTTouchStateLingerInRange = 6, + MTTouchStateOutOfRange = 7 + }; + typedef uint32_t MTTouchState; + + typedef struct { + int32_t frame; + double timestamp; + int32_t pathIndex; // "P" (~transducerIndex) + MTTouchState state; + int32_t fingerID; // "F" (~identity) + int32_t handID; // "H" (always 1) + MTVector normalizedVector; + float zTotal; // "ZTot" (~quality, multiple of 1/8 between 0 and 1) + int32_t field9; // always 0 + float angle; + float majorAxis; + float minorAxis; + MTVector absoluteVector; // "mm" + int32_t field14; // always 0 + int32_t field15; // always 0 + float zDensity; // "ZDen" (~density) + } MTTouch; + + typedef void* MTDeviceRef; + + double MTAbsoluteTimeGetCurrent(); + bool MTDeviceIsAvailable(); // true if can create default device + + CFArrayRef MTDeviceCreateList(); // creates for driver types 0, 1, 4, 2, 3 + MTDeviceRef MTDeviceCreateDefault(); + MTDeviceRef MTDeviceCreateFromDeviceID(int64_t); + MTDeviceRef MTDeviceCreateFromService(io_service_t); + MTDeviceRef MTDeviceCreateFromGUID(uuid_t); // GUID's compared by pointer, not value! + void MTDeviceRelease(MTDeviceRef); + + CFRunLoopSourceRef MTDeviceCreateMultitouchRunLoopSource(MTDeviceRef); + OSStatus MTDeviceScheduleOnRunLoop(MTDeviceRef, CFRunLoopRef, CFStringRef); + + OSStatus MTDeviceStart(MTDeviceRef, int); + OSStatus MTDeviceStop(MTDeviceRef); + bool MTDeviceIsRunning(MTDeviceRef); + + bool MTDeviceIsValid(MTDeviceRef); + bool MTDeviceIsBuiltIn(MTDeviceRef) __attribute__ ((weak_import)); // no 10.5 + bool MTDeviceIsOpaqueSurface(MTDeviceRef); + io_service_t MTDeviceGetService(MTDeviceRef); + OSStatus MTDeviceGetSensorSurfaceDimensions(MTDeviceRef, int*, int*); + OSStatus MTDeviceGetFamilyID(MTDeviceRef, int*); + OSStatus MTDeviceGetDeviceID(MTDeviceRef, uint64_t*) __attribute__ ((weak_import)); // no 10.5 + OSStatus MTDeviceGetDriverType(MTDeviceRef, int*); + OSStatus MTDeviceGetActualType(MTDeviceRef, int*); + OSStatus MTDeviceGetGUID(MTDeviceRef, uuid_t*); + void MTDeviceGetTransportMethod(MTDeviceRef, int *); + + typedef void (*MTFrameCallbackFunction)(MTDeviceRef device, + MTTouch touches[], size_t numTouches, + double timestamp, size_t frame); + void MTRegisterContactFrameCallback(MTDeviceRef, MTFrameCallbackFunction); + void MTUnregisterContactFrameCallback(MTDeviceRef, MTFrameCallbackFunction); + + typedef void (*MTPathCallbackFunction)(MTDeviceRef device, long pathID, long state, MTTouch* touch); + //MTPathCallbackFunction MTPathPrintCallback; + void MTRegisterPathCallback(MTDeviceRef, MTPathCallbackFunction); + void MTUnregisterPathCallback(MTDeviceRef, MTPathCallbackFunction); + + /* + // callbacks never called (need different flags?) + typedef void (*MTImageCallbackFunction)(MTDeviceRef, void*, void*); + MTImageCallbackFunction MTImagePrintCallback; + void MTRegisterMultitouchImageCallback(MTDeviceRef, MTImageCallbackFunction); + */ + + /* + // these log error + void MTVibratorRunForDuration(MTDeviceRef,long); + void MTVibratorStop(MTDeviceRef); + */ + + inline const char* + MTTouchStateName(MTTouchState ps) { + switch (ps) { + case MTTouchStateNotTracking: return "NotTracking" ; + case MTTouchStateStartInRange: return "StartInRange" ; + case MTTouchStateHoverInRange: return "HoverInRange" ; + case MTTouchStateMakeTouch: return "MakeTouch" ; + case MTTouchStateTouching: return "Touching" ; + case MTTouchStateBreakTouch: return "BreakTouch" ; + case MTTouchStateLingerInRange: return "LingerInRange" ; + case MTTouchStateOutOfRange: return "OutOfRange" ; + default: return "Unknown" ; + } + } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/parse.c b/src/parse.c index d9c453a..dcbda45 100644 --- a/src/parse.c +++ b/src/parse.c @@ -152,7 +152,8 @@ parse_key(struct parser *parser) } #define KEY_HAS_IMPLICIT_FN_MOD 4 -#define KEY_HAS_IMPLICIT_NX_MOD 35 +#define KEY_NX_START 35 +#define KEY_NX_END 47 static uint32_t literal_keycode_value[] = { kVK_Return, kVK_Tab, kVK_Space, @@ -171,16 +172,27 @@ static uint32_t literal_keycode_value[] = NX_KEYTYPE_SOUND_UP, NX_KEYTYPE_SOUND_DOWN, NX_KEYTYPE_MUTE, NX_KEYTYPE_PLAY, NX_KEYTYPE_PREVIOUS, NX_KEYTYPE_NEXT, NX_KEYTYPE_REWIND, NX_KEYTYPE_FAST, NX_KEYTYPE_BRIGHTNESS_UP, - NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN + NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN, + + Gesture_ThreeFingerSwipeLeft, Gesture_ThreeFingerSwipeRight, + Gesture_ThreeFingerSwipeUp, Gesture_ThreeFingerSwipeDown, + Gesture_ThreeFingerTap, + Gesture_FourFingerSwipeLeft, Gesture_FourFingerSwipeRight, + Gesture_FourFingerSwipeUp, Gesture_FourFingerSwipeDown, + Gesture_FourFingerTap, + Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, + Gesture_FiveFingerSwipeUp, Gesture_FiveFingerSwipeDown, + Gesture_FiveFingerTap, + Gesture_TipTapLeft, Gesture_TipTapRight }; static inline void handle_implicit_literal_flags(struct hotkey *hotkey, int literal_index) { if ((literal_index > KEY_HAS_IMPLICIT_FN_MOD) && - (literal_index < KEY_HAS_IMPLICIT_NX_MOD)) { + (literal_index < KEY_NX_START)) { hotkey->flags |= Hotkey_Flag_Fn; - } else if (literal_index >= KEY_HAS_IMPLICIT_NX_MOD) { + } else if (literal_index >= KEY_NX_START && literal_index < KEY_NX_END) { hotkey->flags |= Hotkey_Flag_NX; } } diff --git a/src/skhd.c b/src/skhd.c index 0cea25b..5cb903a 100644 --- a/src/skhd.c +++ b/src/skhd.c @@ -28,6 +28,7 @@ #include "hotkey.h" #include "synthesize.h" #include "service.h" +#include "touch.h" #include "hotload.c" #include "event_tap.c" @@ -38,6 +39,7 @@ #include "hotkey.c" #include "synthesize.c" #include "notify.c" +#include "touch.c" extern void NSApplicationLoad(void); extern CFDictionaryRef CGSCopyCurrentSessionDictionary(void); @@ -514,6 +516,8 @@ int main(int argc, char **argv) event_tap.mask = (1 << kCGEventKeyDown) | (1 << NX_SYSDEFINED); event_tap_begin(&event_tap, key_handler); END_SCOPED_TIMED_BLOCK(); + + touch_begin(&mode_map, &blacklst, ¤t_mode, &carbon); END_SCOPED_TIMED_BLOCK(); NSApplicationLoad(); diff --git a/src/tokenize.h b/src/tokenize.h index 13d7001..4087cf1 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -28,7 +28,18 @@ static const char *literal_keycode_str[] = "sound_up", "sound_down", "mute", "play", "previous", "next", "rewind", "fast", "brightness_up", - "brightness_down", "illumination_up", "illumination_down" + "brightness_down", "illumination_up", "illumination_down", + + "three_finger_swipe_left", "three_finger_swipe_right", + "three_finger_swipe_up", "three_finger_swipe_down", + "three_finger_tap", + "four_finger_swipe_left", "four_finger_swipe_right", + "four_finger_swipe_up", "four_finger_swipe_down", + "four_finger_tap", + "five_finger_swipe_left", "five_finger_swipe_right", + "five_finger_swipe_up", "five_finger_swipe_down", + "five_finger_tap", + "tip_tap_left", "tip_tap_right" }; enum token_type diff --git a/src/touch.c b/src/touch.c new file mode 100644 index 0000000..0a6b778 --- /dev/null +++ b/src/touch.c @@ -0,0 +1,525 @@ +#include +#include +#include + +#include "multitouch.h" +#include "touch.h" +#include "log.h" +#include "hotkey.h" +#include "carbon.h" +#include "hashtable.h" + +#define THREE_FINGER_SWIPE_MIN_DISTANCE 0.05f +#define THREE_FINGER_SWIPE_MIN_VELOCITY 0.05f +#define THREE_FINGER_SWIPE_AXIS_RATIO 1.45f +#define THREE_FINGER_TAP_MAX_DURATION 0.18 +#define THREE_FINGER_TAP_MAX_MOVEMENT 0.04f +#define TIP_TAP_MAX_DURATION 0.10 +#define TIP_TAP_HOLD_MAX_MOVEMENT 0.08f +#define TIP_TAP_TAP_MAX_MOVEMENT 0.03f +#define TIP_TAP_MIN_OFFSET 0.04f +#define TIP_TAP_AXIS_RATIO 0.30f + +struct touch_context +{ + struct table *mode_map; + struct table *blacklst; + struct mode **current_mode; + struct carbon_event *carbon; +}; + +static struct touch_context touch_ctx; +static MTDeviceRef touch_device; +static bool touch_tracking; +static bool touch_fired; +static MTPoint touch_start_pos; +static double touch_start_time; +static float touch_max_delta; +static bool touch_tracking_four; +static bool touch_fired_four; +static MTPoint touch_start_pos_four; +static double touch_start_time_four; +static float touch_max_delta_four; +static bool touch_tracking_five; +static bool touch_fired_five; +static MTPoint touch_start_pos_five; +static double touch_start_time_five; +static float touch_max_delta_five; +static enum { + TipTapState_Idle = 0, + TipTapState_TwoFingersDown, + TipTapState_ThreeFingersDown +} tip_tap_state; +static MTPoint tip_tap_two_finger_pos; +static MTPoint tip_tap_three_finger_pos_1; +static MTPoint tip_tap_three_finger_pos_2; +static MTPoint tip_tap_three_finger_pos_3; +static double tip_tap_two_finger_start_time; +static double tip_tap_three_fingers_start_time; +static float tip_tap_two_finger_max_delta; +static float tip_tap_tap_max_delta; +static bool tip_tap_gesture_fired; + +static inline uint32_t +current_modifier_flags(void) +{ + CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState); + return cgevent_flags_to_hotkey_flags(flags); +} + +static inline void +dispatch_gesture(uint32_t key) +{ + if (!touch_ctx.current_mode || !(*touch_ctx.current_mode)) return; + if (touch_ctx.blacklst && touch_ctx.carbon && + table_find(touch_ctx.blacklst, touch_ctx.carbon->process_name)) { + return; + } + + struct hotkey eventkey = { + .key = key, + .flags = current_modifier_flags() + }; + + find_and_exec_hotkey(&eventkey, touch_ctx.mode_map, touch_ctx.current_mode, touch_ctx.carbon); +} + +static inline bool +touch_state_is_active(MTTouchState state) +{ + return state == MTTouchStateMakeTouch || + state == MTTouchStateTouching || + state == MTTouchStateLingerInRange; +} + +static inline bool +touch_state_is_present(MTTouchState state) +{ + return state != MTTouchStateNotTracking && + state != MTTouchStateOutOfRange; +} + +static inline void +get_average_position(MTTouch *data, size_t nFingers, MTPoint *out_pos) +{ + float sum_x = 0.0f; + float sum_y = 0.0f; + int count = 0; + + for (size_t i = 0; i < nFingers; ++i) { + if (touch_state_is_active(data[i].state)) { + sum_x += data[i].normalizedVector.position.x; + sum_y += data[i].normalizedVector.position.y; + ++count; + } + } + + if (count > 0) { + out_pos->x = sum_x / (float)count; + out_pos->y = sum_y / (float)count; + } else { + out_pos->x = 0.0f; + out_pos->y = 0.0f; + } +} + +static inline void +get_two_finger_positions(MTTouch *data, size_t nFingers, MTPoint *pos1, MTPoint *pos2) +{ + int found = 0; + for (size_t i = 0; i < nFingers && found < 2; ++i) { + if (touch_state_is_present(data[i].state)) { + if (found == 0) { + *pos1 = data[i].normalizedVector.position; + } else { + *pos2 = data[i].normalizedVector.position; + } + ++found; + } + } +} + +static inline void +get_three_finger_positions(MTTouch *data, size_t nFingers, MTPoint *pos1, MTPoint *pos2, MTPoint *pos3) +{ + int found = 0; + for (size_t i = 0; i < nFingers && found < 3; ++i) { + if (touch_state_is_present(data[i].state)) { + if (found == 0) { + *pos1 = data[i].normalizedVector.position; + } else if (found == 1) { + *pos2 = data[i].normalizedVector.position; + } else { + *pos3 = data[i].normalizedVector.position; + } + ++found; + } + } +} + +static inline void +reset_tip_tap_state(void) +{ + tip_tap_state = TipTapState_Idle; + tip_tap_two_finger_pos = (MTPoint){0}; + tip_tap_three_finger_pos_1 = (MTPoint){0}; + tip_tap_three_finger_pos_2 = (MTPoint){0}; + tip_tap_three_finger_pos_3 = (MTPoint){0}; + tip_tap_two_finger_start_time = 0.0; + tip_tap_three_fingers_start_time = 0.0; + tip_tap_two_finger_max_delta = 0.0f; + tip_tap_tap_max_delta = 0.0f; + tip_tap_gesture_fired = false; +} + +static void +touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timestamp, size_t frame) +{ + (void) device; + (void) timestamp; + (void) frame; + + if (nFingers != 3 && nFingers != 4 && nFingers != 5) { + if (touch_tracking && !touch_fired) { + double duration = timestamp - touch_start_time; + if (duration <= THREE_FINGER_TAP_MAX_DURATION && + touch_max_delta <= THREE_FINGER_TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_ThreeFingerTap); + } + } + if (touch_tracking_four && !touch_fired_four) { + double duration = timestamp - touch_start_time_four; + if (duration <= THREE_FINGER_TAP_MAX_DURATION && + touch_max_delta_four <= THREE_FINGER_TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_FourFingerTap); + } + } + if (touch_tracking_five && !touch_fired_five) { + double duration = timestamp - touch_start_time_five; + if (duration <= THREE_FINGER_TAP_MAX_DURATION && + touch_max_delta_five <= THREE_FINGER_TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_FiveFingerTap); + } + } + touch_tracking = false; + touch_fired = false; + touch_tracking_four = false; + touch_fired_four = false; + touch_tracking_five = false; + touch_fired_five = false; + } + + if (nFingers <= 3) { + if (nFingers == 0 && tip_tap_gesture_fired) { + reset_tip_tap_state(); + return; + } + if (tip_tap_gesture_fired && nFingers > 0) { + return; + } + switch (tip_tap_state) { + case TipTapState_Idle: { + if (nFingers == 2) { + get_average_position(data, nFingers, &tip_tap_two_finger_pos); + tip_tap_two_finger_start_time = timestamp; + tip_tap_two_finger_max_delta = 0.0f; + tip_tap_state = TipTapState_TwoFingersDown; + } + break; + } + + case TipTapState_TwoFingersDown: { + if (nFingers == 0) { + reset_tip_tap_state(); + } else if (nFingers == 2) { + MTPoint current_pos; + get_average_position(data, nFingers, ¤t_pos); + float dx = current_pos.x - tip_tap_two_finger_pos.x; + float dy = current_pos.y - tip_tap_two_finger_pos.y; + float abs_dx = fabsf(dx); + float abs_dy = fabsf(dy); + if (abs_dx > tip_tap_two_finger_max_delta) tip_tap_two_finger_max_delta = abs_dx; + if (abs_dy > tip_tap_two_finger_max_delta) tip_tap_two_finger_max_delta = abs_dy; + if (tip_tap_two_finger_max_delta > TIP_TAP_HOLD_MAX_MOVEMENT) { + reset_tip_tap_state(); + } + } else if (nFingers == 3) { + get_three_finger_positions(data, nFingers, &tip_tap_three_finger_pos_1, &tip_tap_three_finger_pos_2, &tip_tap_three_finger_pos_3); + tip_tap_three_fingers_start_time = timestamp; + tip_tap_tap_max_delta = 0.0f; + tip_tap_state = TipTapState_ThreeFingersDown; + } + break; + } + + case TipTapState_ThreeFingersDown: { + if (nFingers == 0) { + reset_tip_tap_state(); + } else if (nFingers == 2) { + reset_tip_tap_state(); + } else if (nFingers == 3) { + MTPoint pos1, pos2, pos3; + get_three_finger_positions(data, nFingers, &pos1, &pos2, &pos3); + float dx1 = pos1.x - tip_tap_three_finger_pos_1.x; + float dy1 = pos1.y - tip_tap_three_finger_pos_1.y; + float dx2 = pos2.x - tip_tap_three_finger_pos_2.x; + float dy2 = pos2.y - tip_tap_three_finger_pos_2.y; + float dx3 = pos3.x - tip_tap_three_finger_pos_3.x; + float dy3 = pos3.y - tip_tap_three_finger_pos_3.y; + float max_dx = fmaxf(fmaxf(fabsf(dx1), fabsf(dx2)), fabsf(dx3)); + float max_dy = fmaxf(fmaxf(fabsf(dy1), fabsf(dy2)), fabsf(dy3)); + float max_delta = fmaxf(max_dx, max_dy); + if (max_delta > tip_tap_tap_max_delta) tip_tap_tap_max_delta = max_delta; + if (tip_tap_tap_max_delta > TIP_TAP_TAP_MAX_MOVEMENT || + timestamp - tip_tap_three_fingers_start_time > TIP_TAP_MAX_DURATION) { + float d1 = fabsf(tip_tap_three_finger_pos_1.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_1.y - tip_tap_two_finger_pos.y); + float d2 = fabsf(tip_tap_three_finger_pos_2.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_2.y - tip_tap_two_finger_pos.y); + float d3 = fabsf(tip_tap_three_finger_pos_3.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_3.y - tip_tap_two_finger_pos.y); + MTPoint tap_pos; + if (d1 > d2 && d1 > d3) { + tap_pos = tip_tap_three_finger_pos_1; + } else if (d2 > d3) { + tap_pos = tip_tap_three_finger_pos_2; + } else { + tap_pos = tip_tap_three_finger_pos_3; + } + float delta_x = tap_pos.x - tip_tap_two_finger_pos.x; + float delta_y = tap_pos.y - tip_tap_two_finger_pos.y; + float abs_dx = fabsf(delta_x); + float abs_dy = fabsf(delta_y); + if (abs_dx >= TIP_TAP_MIN_OFFSET && + abs_dx > abs_dy * TIP_TAP_AXIS_RATIO) { + dispatch_gesture(delta_x < 0.0f ? Gesture_TipTapLeft : Gesture_TipTapRight); + tip_tap_gesture_fired = true; + } + reset_tip_tap_state(); + } + } + break; + } + } + return; + } + + if (tip_tap_state != TipTapState_Idle) { + reset_tip_tap_state(); + } + + float sum_x = 0.0f; + float sum_y = 0.0f; + float sum_vx = 0.0f; + float sum_vy = 0.0f; + int active = 0; + int present = 0; + + for (size_t i = 0; i < nFingers; ++i) { + if (touch_state_is_present(data[i].state)) { + ++present; + } + if (!touch_state_is_active(data[i].state)) continue; + sum_x += data[i].normalizedVector.position.x; + sum_y += data[i].normalizedVector.position.y; + sum_vx += data[i].normalizedVector.velocity.x; + sum_vy += data[i].normalizedVector.velocity.y; + ++active; + } + + if (nFingers >= 3 && active != (int)nFingers) return; + + float inv = 1.0f / (float) nFingers; + MTPoint avg_pos = { + .x = sum_x * inv, + .y = sum_y * inv + }; + + MTPoint avg_vel = { + .x = sum_vx * inv, + .y = sum_vy * inv + }; + + if (nFingers == 3) { + if (!touch_tracking) { + touch_tracking = true; + touch_fired = false; + touch_start_pos = avg_pos; + touch_start_time = timestamp; + touch_max_delta = 0.0f; + return; + } + + if (touch_fired) return; + + float delta_x = avg_pos.x - touch_start_pos.x; + float delta_y = avg_pos.y - touch_start_pos.y; + float abs_dx = fabsf(delta_x); + float abs_dy = fabsf(delta_y); + if (abs_dx > touch_max_delta) touch_max_delta = abs_dx; + if (abs_dy > touch_max_delta) touch_max_delta = abs_dy; + + if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired = true; + dispatch_gesture(Gesture_ThreeFingerSwipeLeft); + } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired = true; + dispatch_gesture(Gesture_ThreeFingerSwipeRight); + } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired = true; + dispatch_gesture(Gesture_ThreeFingerSwipeDown); + } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired = true; + dispatch_gesture(Gesture_ThreeFingerSwipeUp); + } + } else if (nFingers == 4) { + if (!touch_tracking_four) { + touch_tracking_four = true; + touch_fired_four = false; + touch_start_pos_four = avg_pos; + touch_start_time_four = timestamp; + touch_max_delta_four = 0.0f; + return; + } + + if (touch_fired_four) return; + + float delta_x = avg_pos.x - touch_start_pos_four.x; + float delta_y = avg_pos.y - touch_start_pos_four.y; + float abs_dx = fabsf(delta_x); + float abs_dy = fabsf(delta_y); + if (abs_dx > touch_max_delta_four) touch_max_delta_four = abs_dx; + if (abs_dy > touch_max_delta_four) touch_max_delta_four = abs_dy; + + if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_four = true; + dispatch_gesture(Gesture_FourFingerSwipeLeft); + } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_four = true; + dispatch_gesture(Gesture_FourFingerSwipeRight); + } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_four = true; + dispatch_gesture(Gesture_FourFingerSwipeDown); + } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_four = true; + dispatch_gesture(Gesture_FourFingerSwipeUp); + } + } else if (nFingers == 5) { + if (!touch_tracking_five) { + touch_tracking_five = true; + touch_fired_five = false; + touch_start_pos_five = avg_pos; + touch_start_time_five = timestamp; + touch_max_delta_five = 0.0f; + return; + } + + if (touch_fired_five) return; + + float delta_x = avg_pos.x - touch_start_pos_five.x; + float delta_y = avg_pos.y - touch_start_pos_five.y; + float abs_dx = fabsf(delta_x); + float abs_dy = fabsf(delta_y); + if (abs_dx > touch_max_delta_five) touch_max_delta_five = abs_dx; + if (abs_dy > touch_max_delta_five) touch_max_delta_five = abs_dy; + + if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_five = true; + dispatch_gesture(Gesture_FiveFingerSwipeLeft); + } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_five = true; + dispatch_gesture(Gesture_FiveFingerSwipeRight); + } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_five = true; + dispatch_gesture(Gesture_FiveFingerSwipeDown); + } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_five = true; + dispatch_gesture(Gesture_FiveFingerSwipeUp); + } + } +} + +bool touch_begin(struct table *mode_map, struct table *blacklst, struct mode **current_mode, struct carbon_event *carbon) +{ + touch_ctx.mode_map = mode_map; + touch_ctx.blacklst = blacklst; + touch_ctx.current_mode = current_mode; + touch_ctx.carbon = carbon; + + if (!MTDeviceIsAvailable()) { + warn("no multitouch device found.. touch gestures disabled\n"); + return false; + } + + CFArrayRef device_list = MTDeviceCreateList(); + if (device_list) { + CFIndex count = CFArrayGetCount(device_list); + for (CFIndex i = 0; i < count; ++i) { + MTDeviceRef candidate = (MTDeviceRef) CFArrayGetValueAtIndex(device_list, i); + int width = 0; + int height = 0; + if (MTDeviceGetSensorSurfaceDimensions(candidate, &width, &height) != noErr) { + continue; + } + if ((width == 23212 && height == 810) || + (width == 5152 && height == 9056)) { + debug("skipping non-touch multitouch device (touchbar, magic mouse) with dimensions %d x %d\n", width, height); + continue; + } + touch_device = candidate; + break; + } + } + + if (!touch_device) { + warn("could not initialize multitouch device.. touch gestures disabled\n"); + return false; + } + + MTRegisterContactFrameCallback(touch_device, touch_callback); + MTDeviceStart(touch_device, 0); + + return true; +} + +void touch_end(void) +{ + if (!touch_device) return; + MTUnregisterContactFrameCallback(touch_device, touch_callback); + MTDeviceStop(touch_device); + MTDeviceRelease(touch_device); + touch_device = NULL; +} diff --git a/src/touch.h b/src/touch.h new file mode 100644 index 0000000..34bd59c --- /dev/null +++ b/src/touch.h @@ -0,0 +1,13 @@ +#ifndef SKHD_TOUCH_H +#define SKHD_TOUCH_H + +#include + +struct table; +struct mode; +struct carbon_event; + +bool touch_begin(struct table *mode_map, struct table *blacklst, struct mode **current_mode, struct carbon_event *carbon); +void touch_end(void); + +#endif From 2e64a0a45da268f6cc7f7bb92c41d9725b5095e1 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 12:14:45 +0100 Subject: [PATCH 02/10] feat: Added two finger swipe gestures. --- src/hotkey.h | 36 +++++---- src/parse.c | 5 +- src/tokenize.h | 5 +- src/touch.c | 215 ++++++++++++------------------------------------- 4 files changed, 76 insertions(+), 185 deletions(-) diff --git a/src/hotkey.h b/src/hotkey.h index 4e3200d..ebd81dd 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -12,23 +12,25 @@ #define Modifier_Keycode_Fn 0x3F #define Gesture_Keycode_Base 0x10000000 -#define Gesture_ThreeFingerSwipeLeft (Gesture_Keycode_Base + 1) -#define Gesture_ThreeFingerSwipeRight (Gesture_Keycode_Base + 2) -#define Gesture_ThreeFingerSwipeUp (Gesture_Keycode_Base + 3) -#define Gesture_ThreeFingerSwipeDown (Gesture_Keycode_Base + 4) -#define Gesture_ThreeFingerTap (Gesture_Keycode_Base + 5) -#define Gesture_FourFingerSwipeLeft (Gesture_Keycode_Base + 6) -#define Gesture_FourFingerSwipeRight (Gesture_Keycode_Base + 7) -#define Gesture_FourFingerSwipeUp (Gesture_Keycode_Base + 8) -#define Gesture_FourFingerSwipeDown (Gesture_Keycode_Base + 9) -#define Gesture_FiveFingerSwipeLeft (Gesture_Keycode_Base + 10) -#define Gesture_FiveFingerSwipeRight (Gesture_Keycode_Base + 11) -#define Gesture_FiveFingerSwipeUp (Gesture_Keycode_Base + 12) -#define Gesture_FiveFingerSwipeDown (Gesture_Keycode_Base + 13) -#define Gesture_FourFingerTap (Gesture_Keycode_Base + 14) -#define Gesture_FiveFingerTap (Gesture_Keycode_Base + 15) -#define Gesture_TipTapLeft (Gesture_Keycode_Base + 16) -#define Gesture_TipTapRight (Gesture_Keycode_Base + 17) +#define Gesture_TwoFingerSwipeLeft (Gesture_Keycode_Base + 1) +#define Gesture_TwoFingerSwipeRight (Gesture_Keycode_Base + 2) +#define Gesture_TwoFingerSwipeUp (Gesture_Keycode_Base + 3) +#define Gesture_TwoFingerSwipeDown (Gesture_Keycode_Base + 4) +#define Gesture_ThreeFingerSwipeLeft (Gesture_Keycode_Base + 5) +#define Gesture_ThreeFingerSwipeRight (Gesture_Keycode_Base + 6) +#define Gesture_ThreeFingerSwipeUp (Gesture_Keycode_Base + 7) +#define Gesture_ThreeFingerSwipeDown (Gesture_Keycode_Base + 8) +#define Gesture_ThreeFingerTap (Gesture_Keycode_Base + 9) +#define Gesture_FourFingerSwipeLeft (Gesture_Keycode_Base + 10) +#define Gesture_FourFingerSwipeRight (Gesture_Keycode_Base + 11) +#define Gesture_FourFingerSwipeUp (Gesture_Keycode_Base + 12) +#define Gesture_FourFingerSwipeDown (Gesture_Keycode_Base + 13) +#define Gesture_FiveFingerSwipeLeft (Gesture_Keycode_Base + 14) +#define Gesture_FiveFingerSwipeRight (Gesture_Keycode_Base + 15) +#define Gesture_FiveFingerSwipeUp (Gesture_Keycode_Base + 16) +#define Gesture_FiveFingerSwipeDown (Gesture_Keycode_Base + 17) +#define Gesture_FourFingerTap (Gesture_Keycode_Base + 18) +#define Gesture_FiveFingerTap (Gesture_Keycode_Base + 19) enum osx_event_mask { diff --git a/src/parse.c b/src/parse.c index dcbda45..55a7d37 100644 --- a/src/parse.c +++ b/src/parse.c @@ -174,6 +174,8 @@ static uint32_t literal_keycode_value[] = NX_KEYTYPE_REWIND, NX_KEYTYPE_FAST, NX_KEYTYPE_BRIGHTNESS_UP, NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN, + Gesture_TwoFingerSwipeLeft, Gesture_TwoFingerSwipeRight, + Gesture_TwoFingerSwipeUp, Gesture_TwoFingerSwipeDown, Gesture_ThreeFingerSwipeLeft, Gesture_ThreeFingerSwipeRight, Gesture_ThreeFingerSwipeUp, Gesture_ThreeFingerSwipeDown, Gesture_ThreeFingerTap, @@ -182,8 +184,7 @@ static uint32_t literal_keycode_value[] = Gesture_FourFingerTap, Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, Gesture_FiveFingerSwipeUp, Gesture_FiveFingerSwipeDown, - Gesture_FiveFingerTap, - Gesture_TipTapLeft, Gesture_TipTapRight + Gesture_FiveFingerTap }; static inline void diff --git a/src/tokenize.h b/src/tokenize.h index 4087cf1..5abc76a 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -30,6 +30,8 @@ static const char *literal_keycode_str[] = "rewind", "fast", "brightness_up", "brightness_down", "illumination_up", "illumination_down", + "two_finger_swipe_left", "two_finger_swipe_right", + "two_finger_swipe_up", "two_finger_swipe_down", "three_finger_swipe_left", "three_finger_swipe_right", "three_finger_swipe_up", "three_finger_swipe_down", "three_finger_tap", @@ -38,8 +40,7 @@ static const char *literal_keycode_str[] = "four_finger_tap", "five_finger_swipe_left", "five_finger_swipe_right", "five_finger_swipe_up", "five_finger_swipe_down", - "five_finger_tap", - "tip_tap_left", "tip_tap_right" + "five_finger_tap" }; enum token_type diff --git a/src/touch.c b/src/touch.c index 0a6b778..e6eaa33 100644 --- a/src/touch.c +++ b/src/touch.c @@ -14,11 +14,6 @@ #define THREE_FINGER_SWIPE_AXIS_RATIO 1.45f #define THREE_FINGER_TAP_MAX_DURATION 0.18 #define THREE_FINGER_TAP_MAX_MOVEMENT 0.04f -#define TIP_TAP_MAX_DURATION 0.10 -#define TIP_TAP_HOLD_MAX_MOVEMENT 0.08f -#define TIP_TAP_TAP_MAX_MOVEMENT 0.03f -#define TIP_TAP_MIN_OFFSET 0.04f -#define TIP_TAP_AXIS_RATIO 0.30f struct touch_context { @@ -35,6 +30,9 @@ static bool touch_fired; static MTPoint touch_start_pos; static double touch_start_time; static float touch_max_delta; +static bool touch_tracking_two; +static bool touch_fired_two; +static MTPoint touch_start_pos_two; static bool touch_tracking_four; static bool touch_fired_four; static MTPoint touch_start_pos_four; @@ -45,20 +43,6 @@ static bool touch_fired_five; static MTPoint touch_start_pos_five; static double touch_start_time_five; static float touch_max_delta_five; -static enum { - TipTapState_Idle = 0, - TipTapState_TwoFingersDown, - TipTapState_ThreeFingersDown -} tip_tap_state; -static MTPoint tip_tap_two_finger_pos; -static MTPoint tip_tap_three_finger_pos_1; -static MTPoint tip_tap_three_finger_pos_2; -static MTPoint tip_tap_three_finger_pos_3; -static double tip_tap_two_finger_start_time; -static double tip_tap_three_fingers_start_time; -static float tip_tap_two_finger_max_delta; -static float tip_tap_tap_max_delta; -static bool tip_tap_gesture_fired; static inline uint32_t current_modifier_flags(void) @@ -123,54 +107,7 @@ get_average_position(MTTouch *data, size_t nFingers, MTPoint *out_pos) } } -static inline void -get_two_finger_positions(MTTouch *data, size_t nFingers, MTPoint *pos1, MTPoint *pos2) -{ - int found = 0; - for (size_t i = 0; i < nFingers && found < 2; ++i) { - if (touch_state_is_present(data[i].state)) { - if (found == 0) { - *pos1 = data[i].normalizedVector.position; - } else { - *pos2 = data[i].normalizedVector.position; - } - ++found; - } - } -} - -static inline void -get_three_finger_positions(MTTouch *data, size_t nFingers, MTPoint *pos1, MTPoint *pos2, MTPoint *pos3) -{ - int found = 0; - for (size_t i = 0; i < nFingers && found < 3; ++i) { - if (touch_state_is_present(data[i].state)) { - if (found == 0) { - *pos1 = data[i].normalizedVector.position; - } else if (found == 1) { - *pos2 = data[i].normalizedVector.position; - } else { - *pos3 = data[i].normalizedVector.position; - } - ++found; - } - } -} -static inline void -reset_tip_tap_state(void) -{ - tip_tap_state = TipTapState_Idle; - tip_tap_two_finger_pos = (MTPoint){0}; - tip_tap_three_finger_pos_1 = (MTPoint){0}; - tip_tap_three_finger_pos_2 = (MTPoint){0}; - tip_tap_three_finger_pos_3 = (MTPoint){0}; - tip_tap_two_finger_start_time = 0.0; - tip_tap_three_fingers_start_time = 0.0; - tip_tap_two_finger_max_delta = 0.0f; - tip_tap_tap_max_delta = 0.0f; - tip_tap_gesture_fired = false; -} static void touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timestamp, size_t frame) @@ -179,7 +116,7 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest (void) timestamp; (void) frame; - if (nFingers != 3 && nFingers != 4 && nFingers != 5) { + if (nFingers != 2 && nFingers != 3 && nFingers != 4 && nFingers != 5) { if (touch_tracking && !touch_fired) { double duration = timestamp - touch_start_time; if (duration <= THREE_FINGER_TAP_MAX_DURATION && @@ -203,108 +140,15 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest } touch_tracking = false; touch_fired = false; + touch_tracking_two = false; + touch_fired_two = false; touch_tracking_four = false; touch_fired_four = false; touch_tracking_five = false; touch_fired_five = false; - } - - if (nFingers <= 3) { - if (nFingers == 0 && tip_tap_gesture_fired) { - reset_tip_tap_state(); - return; - } - if (tip_tap_gesture_fired && nFingers > 0) { - return; - } - switch (tip_tap_state) { - case TipTapState_Idle: { - if (nFingers == 2) { - get_average_position(data, nFingers, &tip_tap_two_finger_pos); - tip_tap_two_finger_start_time = timestamp; - tip_tap_two_finger_max_delta = 0.0f; - tip_tap_state = TipTapState_TwoFingersDown; - } - break; - } - - case TipTapState_TwoFingersDown: { - if (nFingers == 0) { - reset_tip_tap_state(); - } else if (nFingers == 2) { - MTPoint current_pos; - get_average_position(data, nFingers, ¤t_pos); - float dx = current_pos.x - tip_tap_two_finger_pos.x; - float dy = current_pos.y - tip_tap_two_finger_pos.y; - float abs_dx = fabsf(dx); - float abs_dy = fabsf(dy); - if (abs_dx > tip_tap_two_finger_max_delta) tip_tap_two_finger_max_delta = abs_dx; - if (abs_dy > tip_tap_two_finger_max_delta) tip_tap_two_finger_max_delta = abs_dy; - if (tip_tap_two_finger_max_delta > TIP_TAP_HOLD_MAX_MOVEMENT) { - reset_tip_tap_state(); - } - } else if (nFingers == 3) { - get_three_finger_positions(data, nFingers, &tip_tap_three_finger_pos_1, &tip_tap_three_finger_pos_2, &tip_tap_three_finger_pos_3); - tip_tap_three_fingers_start_time = timestamp; - tip_tap_tap_max_delta = 0.0f; - tip_tap_state = TipTapState_ThreeFingersDown; - } - break; - } - - case TipTapState_ThreeFingersDown: { - if (nFingers == 0) { - reset_tip_tap_state(); - } else if (nFingers == 2) { - reset_tip_tap_state(); - } else if (nFingers == 3) { - MTPoint pos1, pos2, pos3; - get_three_finger_positions(data, nFingers, &pos1, &pos2, &pos3); - float dx1 = pos1.x - tip_tap_three_finger_pos_1.x; - float dy1 = pos1.y - tip_tap_three_finger_pos_1.y; - float dx2 = pos2.x - tip_tap_three_finger_pos_2.x; - float dy2 = pos2.y - tip_tap_three_finger_pos_2.y; - float dx3 = pos3.x - tip_tap_three_finger_pos_3.x; - float dy3 = pos3.y - tip_tap_three_finger_pos_3.y; - float max_dx = fmaxf(fmaxf(fabsf(dx1), fabsf(dx2)), fabsf(dx3)); - float max_dy = fmaxf(fmaxf(fabsf(dy1), fabsf(dy2)), fabsf(dy3)); - float max_delta = fmaxf(max_dx, max_dy); - if (max_delta > tip_tap_tap_max_delta) tip_tap_tap_max_delta = max_delta; - if (tip_tap_tap_max_delta > TIP_TAP_TAP_MAX_MOVEMENT || - timestamp - tip_tap_three_fingers_start_time > TIP_TAP_MAX_DURATION) { - float d1 = fabsf(tip_tap_three_finger_pos_1.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_1.y - tip_tap_two_finger_pos.y); - float d2 = fabsf(tip_tap_three_finger_pos_2.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_2.y - tip_tap_two_finger_pos.y); - float d3 = fabsf(tip_tap_three_finger_pos_3.x - tip_tap_two_finger_pos.x) + fabsf(tip_tap_three_finger_pos_3.y - tip_tap_two_finger_pos.y); - MTPoint tap_pos; - if (d1 > d2 && d1 > d3) { - tap_pos = tip_tap_three_finger_pos_1; - } else if (d2 > d3) { - tap_pos = tip_tap_three_finger_pos_2; - } else { - tap_pos = tip_tap_three_finger_pos_3; - } - float delta_x = tap_pos.x - tip_tap_two_finger_pos.x; - float delta_y = tap_pos.y - tip_tap_two_finger_pos.y; - float abs_dx = fabsf(delta_x); - float abs_dy = fabsf(delta_y); - if (abs_dx >= TIP_TAP_MIN_OFFSET && - abs_dx > abs_dy * TIP_TAP_AXIS_RATIO) { - dispatch_gesture(delta_x < 0.0f ? Gesture_TipTapLeft : Gesture_TipTapRight); - tip_tap_gesture_fired = true; - } - reset_tip_tap_state(); - } - } - break; - } - } return; } - if (tip_tap_state != TipTapState_Idle) { - reset_tip_tap_state(); - } - float sum_x = 0.0f; float sum_y = 0.0f; float sum_vx = 0.0f; @@ -324,7 +168,7 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest ++active; } - if (nFingers >= 3 && active != (int)nFingers) return; + if (nFingers >= 2 && active != (int)nFingers) return; float inv = 1.0f / (float) nFingers; MTPoint avg_pos = { @@ -337,7 +181,50 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest .y = sum_vy * inv }; - if (nFingers == 3) { + if (nFingers != 2) { + touch_tracking_two = false; + touch_fired_two = false; + } + + if (nFingers == 2) { + if (!touch_tracking_two) { + touch_tracking_two = true; + touch_fired_two = false; + touch_start_pos_two = avg_pos; + return; + } + + if (touch_fired_two) return; + + float delta_x = avg_pos.x - touch_start_pos_two.x; + float delta_y = avg_pos.y - touch_start_pos_two.y; + + if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_two = true; + dispatch_gesture(Gesture_TwoFingerSwipeLeft); + } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_two = true; + dispatch_gesture(Gesture_TwoFingerSwipeRight); + } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_two = true; + dispatch_gesture(Gesture_TwoFingerSwipeDown); + } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && + avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + touch_fired_two = true; + dispatch_gesture(Gesture_TwoFingerSwipeUp); + } + } else if (nFingers == 3) { if (!touch_tracking) { touch_tracking = true; touch_fired = false; From 1d944c359da7c8dc8ebde0cabe6855966d20de19 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 13:50:44 +0100 Subject: [PATCH 03/10] chore: Removed unused symbols and variables. --- src/touch.c | 187 +++++++++++++++++++++------------------------------- 1 file changed, 75 insertions(+), 112 deletions(-) diff --git a/src/touch.c b/src/touch.c index e6eaa33..9bb59f8 100644 --- a/src/touch.c +++ b/src/touch.c @@ -9,11 +9,11 @@ #include "carbon.h" #include "hashtable.h" -#define THREE_FINGER_SWIPE_MIN_DISTANCE 0.05f -#define THREE_FINGER_SWIPE_MIN_VELOCITY 0.05f -#define THREE_FINGER_SWIPE_AXIS_RATIO 1.45f -#define THREE_FINGER_TAP_MAX_DURATION 0.18 -#define THREE_FINGER_TAP_MAX_MOVEMENT 0.04f +#define SWIPE_MIN_DISTANCE 0.05f +#define SWIPE_MIN_VELOCITY 0.05f +#define SWIPE_AXIS_RATIO 1.45f +#define TAP_MAX_DURATION 0.18 +#define TAP_MAX_MOVEMENT 0.04f struct touch_context { @@ -76,39 +76,6 @@ touch_state_is_active(MTTouchState state) state == MTTouchStateLingerInRange; } -static inline bool -touch_state_is_present(MTTouchState state) -{ - return state != MTTouchStateNotTracking && - state != MTTouchStateOutOfRange; -} - -static inline void -get_average_position(MTTouch *data, size_t nFingers, MTPoint *out_pos) -{ - float sum_x = 0.0f; - float sum_y = 0.0f; - int count = 0; - - for (size_t i = 0; i < nFingers; ++i) { - if (touch_state_is_active(data[i].state)) { - sum_x += data[i].normalizedVector.position.x; - sum_y += data[i].normalizedVector.position.y; - ++count; - } - } - - if (count > 0) { - out_pos->x = sum_x / (float)count; - out_pos->y = sum_y / (float)count; - } else { - out_pos->x = 0.0f; - out_pos->y = 0.0f; - } -} - - - static void touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timestamp, size_t frame) { @@ -119,22 +86,22 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest if (nFingers != 2 && nFingers != 3 && nFingers != 4 && nFingers != 5) { if (touch_tracking && !touch_fired) { double duration = timestamp - touch_start_time; - if (duration <= THREE_FINGER_TAP_MAX_DURATION && - touch_max_delta <= THREE_FINGER_TAP_MAX_MOVEMENT) { + if (duration <= TAP_MAX_DURATION && + touch_max_delta <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_ThreeFingerTap); } } if (touch_tracking_four && !touch_fired_four) { double duration = timestamp - touch_start_time_four; - if (duration <= THREE_FINGER_TAP_MAX_DURATION && - touch_max_delta_four <= THREE_FINGER_TAP_MAX_MOVEMENT) { + if (duration <= TAP_MAX_DURATION && + touch_max_delta_four <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_FourFingerTap); } } if (touch_tracking_five && !touch_fired_five) { double duration = timestamp - touch_start_time_five; - if (duration <= THREE_FINGER_TAP_MAX_DURATION && - touch_max_delta_five <= THREE_FINGER_TAP_MAX_MOVEMENT) { + if (duration <= TAP_MAX_DURATION && + touch_max_delta_five <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_FiveFingerTap); } } @@ -154,12 +121,8 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest float sum_vx = 0.0f; float sum_vy = 0.0f; int active = 0; - int present = 0; for (size_t i = 0; i < nFingers; ++i) { - if (touch_state_is_present(data[i].state)) { - ++present; - } if (!touch_state_is_active(data[i].state)) continue; sum_x += data[i].normalizedVector.position.x; sum_y += data[i].normalizedVector.position.y; @@ -199,28 +162,28 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest float delta_x = avg_pos.x - touch_start_pos_two.x; float delta_y = avg_pos.y - touch_start_pos_two.y; - if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + if (delta_x < -SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_two = true; dispatch_gesture(Gesture_TwoFingerSwipeLeft); - } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_x > SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_two = true; dispatch_gesture(Gesture_TwoFingerSwipeRight); - } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y < -SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_two = true; dispatch_gesture(Gesture_TwoFingerSwipeDown); - } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y > SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_two = true; dispatch_gesture(Gesture_TwoFingerSwipeUp); } @@ -243,28 +206,28 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest if (abs_dx > touch_max_delta) touch_max_delta = abs_dx; if (abs_dy > touch_max_delta) touch_max_delta = abs_dy; - if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + if (delta_x < -SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired = true; dispatch_gesture(Gesture_ThreeFingerSwipeLeft); - } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_x > SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired = true; dispatch_gesture(Gesture_ThreeFingerSwipeRight); - } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y < -SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired = true; dispatch_gesture(Gesture_ThreeFingerSwipeDown); - } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y > SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired = true; dispatch_gesture(Gesture_ThreeFingerSwipeUp); } @@ -287,28 +250,28 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest if (abs_dx > touch_max_delta_four) touch_max_delta_four = abs_dx; if (abs_dy > touch_max_delta_four) touch_max_delta_four = abs_dy; - if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + if (delta_x < -SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_four = true; dispatch_gesture(Gesture_FourFingerSwipeLeft); - } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_x > SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_four = true; dispatch_gesture(Gesture_FourFingerSwipeRight); - } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y < -SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_four = true; dispatch_gesture(Gesture_FourFingerSwipeDown); - } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y > SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_four = true; dispatch_gesture(Gesture_FourFingerSwipeUp); } @@ -331,28 +294,28 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest if (abs_dx > touch_max_delta_five) touch_max_delta_five = abs_dx; if (abs_dy > touch_max_delta_five) touch_max_delta_five = abs_dy; - if (delta_x < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + if (delta_x < -SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_five = true; dispatch_gesture(Gesture_FiveFingerSwipeLeft); - } else if (delta_x > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.x > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_x > SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { touch_fired_five = true; dispatch_gesture(Gesture_FiveFingerSwipeRight); - } else if (delta_y < -THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y < -THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y < -SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_five = true; dispatch_gesture(Gesture_FiveFingerSwipeDown); - } else if (delta_y > THREE_FINGER_SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * THREE_FINGER_SWIPE_AXIS_RATIO && - avg_vel.y > THREE_FINGER_SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * THREE_FINGER_SWIPE_AXIS_RATIO) { + } else if (delta_y > SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { touch_fired_five = true; dispatch_gesture(Gesture_FiveFingerSwipeUp); } From 6f7e23aa633ed6873e68e48637b154d322d14d4a Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 17:55:28 +0100 Subject: [PATCH 04/10] fix: Added touch reset and error handling for device starting. --- src/touch.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/touch.c b/src/touch.c index 9bb59f8..bd00edd 100644 --- a/src/touch.c +++ b/src/touch.c @@ -44,6 +44,32 @@ static MTPoint touch_start_pos_five; static double touch_start_time_five; static float touch_max_delta_five; +static void +reset_touch_state(void) +{ + touch_tracking = false; + touch_fired = false; + touch_start_pos = (MTPoint) { 0 }; + touch_start_time = 0.0; + touch_max_delta = 0.0f; + + touch_tracking_two = false; + touch_fired_two = false; + touch_start_pos_two = (MTPoint) { 0 }; + + touch_tracking_four = false; + touch_fired_four = false; + touch_start_pos_four = (MTPoint) { 0 }; + touch_start_time_four = 0.0; + touch_max_delta_four = 0.0f; + + touch_tracking_five = false; + touch_fired_five = false; + touch_start_pos_five = (MTPoint) { 0 }; + touch_start_time_five = 0.0; + touch_max_delta_five = 0.0f; +} + static inline uint32_t current_modifier_flags(void) { @@ -360,13 +386,31 @@ bool touch_begin(struct table *mode_map, struct table *blacklst, struct mode **c } MTRegisterContactFrameCallback(touch_device, touch_callback); - MTDeviceStart(touch_device, 0); + if (!MTDeviceIsValid(touch_device)) { + warn("failed to register multitouch callback.. touch gestures disabled\n"); + MTDeviceRelease(touch_device); + touch_device = NULL; + reset_touch_state(); + return false; + } + + OSStatus start_status = MTDeviceStart(touch_device, 0); + if (start_status != noErr) { + warn("could not start multitouch device (%d).. touch gestures disabled\n", (int) start_status); + MTUnregisterContactFrameCallback(touch_device, touch_callback); + MTDeviceRelease(touch_device); + touch_device = NULL; + reset_touch_state(); + return false; + } + if (device_list) CFRelease(device_list); return true; } void touch_end(void) { + reset_touch_state(); if (!touch_device) return; MTUnregisterContactFrameCallback(touch_device, touch_callback); MTDeviceStop(touch_device); From 5f3cbb33a1a311136d6bf0675994b5b4ce36f52e Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:20:38 +0100 Subject: [PATCH 05/10] feat: Add two finger tap gesture support and refactor swipe state management --- src/hotkey.h | 1 + src/parse.c | 3 +- src/tokenize.h | 3 +- src/touch.c | 331 +++++++++++++++---------------------------------- 4 files changed, 104 insertions(+), 234 deletions(-) diff --git a/src/hotkey.h b/src/hotkey.h index ebd81dd..f3f1cb1 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -31,6 +31,7 @@ #define Gesture_FiveFingerSwipeDown (Gesture_Keycode_Base + 17) #define Gesture_FourFingerTap (Gesture_Keycode_Base + 18) #define Gesture_FiveFingerTap (Gesture_Keycode_Base + 19) +#define Gesture_TwoFingerTap (Gesture_Keycode_Base + 20) enum osx_event_mask { diff --git a/src/parse.c b/src/parse.c index 55a7d37..0563a82 100644 --- a/src/parse.c +++ b/src/parse.c @@ -184,7 +184,8 @@ static uint32_t literal_keycode_value[] = Gesture_FourFingerTap, Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, Gesture_FiveFingerSwipeUp, Gesture_FiveFingerSwipeDown, - Gesture_FiveFingerTap + Gesture_FiveFingerTap, + Gesture_TwoFingerTap }; static inline void diff --git a/src/tokenize.h b/src/tokenize.h index 5abc76a..995b01d 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -40,7 +40,8 @@ static const char *literal_keycode_str[] = "four_finger_tap", "five_finger_swipe_left", "five_finger_swipe_right", "five_finger_swipe_up", "five_finger_swipe_down", - "five_finger_tap" + "five_finger_tap", + "two_finger_tap" }; enum token_type diff --git a/src/touch.c b/src/touch.c index bd00edd..67e6dbe 100644 --- a/src/touch.c +++ b/src/touch.c @@ -23,51 +23,29 @@ struct touch_context struct carbon_event *carbon; }; +struct swipe_state +{ + bool tracking; + bool fired; + MTPoint start_pos; + double start_time; + float max_delta; +}; + static struct touch_context touch_ctx; static MTDeviceRef touch_device; -static bool touch_tracking; -static bool touch_fired; -static MTPoint touch_start_pos; -static double touch_start_time; -static float touch_max_delta; -static bool touch_tracking_two; -static bool touch_fired_two; -static MTPoint touch_start_pos_two; -static bool touch_tracking_four; -static bool touch_fired_four; -static MTPoint touch_start_pos_four; -static double touch_start_time_four; -static float touch_max_delta_four; -static bool touch_tracking_five; -static bool touch_fired_five; -static MTPoint touch_start_pos_five; -static double touch_start_time_five; -static float touch_max_delta_five; +static struct swipe_state swipe_two; +static struct swipe_state swipe_three; +static struct swipe_state swipe_four; +static struct swipe_state swipe_five; static void reset_touch_state(void) { - touch_tracking = false; - touch_fired = false; - touch_start_pos = (MTPoint) { 0 }; - touch_start_time = 0.0; - touch_max_delta = 0.0f; - - touch_tracking_two = false; - touch_fired_two = false; - touch_start_pos_two = (MTPoint) { 0 }; - - touch_tracking_four = false; - touch_fired_four = false; - touch_start_pos_four = (MTPoint) { 0 }; - touch_start_time_four = 0.0; - touch_max_delta_four = 0.0f; - - touch_tracking_five = false; - touch_fired_five = false; - touch_start_pos_five = (MTPoint) { 0 }; - touch_start_time_five = 0.0; - touch_max_delta_five = 0.0f; + swipe_two = (struct swipe_state) { 0 }; + swipe_three = (struct swipe_state) { 0 }; + swipe_four = (struct swipe_state) { 0 }; + swipe_five = (struct swipe_state) { 0 }; } static inline uint32_t @@ -102,6 +80,56 @@ touch_state_is_active(MTTouchState state) state == MTTouchStateLingerInRange; } +static inline void +handle_swipe_multi(struct swipe_state *state, double timestamp, MTPoint avg_pos, MTPoint avg_vel, + uint32_t gesture_left, uint32_t gesture_right, + uint32_t gesture_down, uint32_t gesture_up) +{ + if (!state->tracking) { + state->tracking = true; + state->fired = false; + state->start_pos = avg_pos; + state->start_time = timestamp; + state->max_delta = 0.0f; + return; + } + + if (state->fired) return; + + float delta_x = avg_pos.x - state->start_pos.x; + float delta_y = avg_pos.y - state->start_pos.y; + float abs_dx = fabsf(delta_x); + float abs_dy = fabsf(delta_y); + if (abs_dx > state->max_delta) state->max_delta = abs_dx; + if (abs_dy > state->max_delta) state->max_delta = abs_dy; + + if (delta_x < -SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { + state->fired = true; + dispatch_gesture(gesture_left); + } else if (delta_x > SWIPE_MIN_DISTANCE && + fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && + avg_vel.x > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { + state->fired = true; + dispatch_gesture(gesture_right); + } else if (delta_y < -SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y < -SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { + state->fired = true; + dispatch_gesture(gesture_down); + } else if (delta_y > SWIPE_MIN_DISTANCE && + fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && + avg_vel.y > SWIPE_MIN_VELOCITY && + fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { + state->fired = true; + dispatch_gesture(gesture_up); + } +} + static void touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timestamp, size_t frame) { @@ -109,36 +137,36 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest (void) timestamp; (void) frame; - if (nFingers != 2 && nFingers != 3 && nFingers != 4 && nFingers != 5) { - if (touch_tracking && !touch_fired) { - double duration = timestamp - touch_start_time; + if (nFingers != 2 && nFingers != 3 && nFingers != 4 && nFingers != 5) { // Fingers lifted + if (swipe_two.tracking && !swipe_two.fired) { + double duration = timestamp - swipe_two.start_time; + if (duration <= TAP_MAX_DURATION && + swipe_two.max_delta <= TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_TwoFingerTap); + } + } + if (swipe_three.tracking && !swipe_three.fired) { + double duration = timestamp - swipe_three.start_time; if (duration <= TAP_MAX_DURATION && - touch_max_delta <= TAP_MAX_MOVEMENT) { + swipe_three.max_delta <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_ThreeFingerTap); } } - if (touch_tracking_four && !touch_fired_four) { - double duration = timestamp - touch_start_time_four; + if (swipe_four.tracking && !swipe_four.fired) { + double duration = timestamp - swipe_four.start_time; if (duration <= TAP_MAX_DURATION && - touch_max_delta_four <= TAP_MAX_MOVEMENT) { + swipe_four.max_delta <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_FourFingerTap); } } - if (touch_tracking_five && !touch_fired_five) { - double duration = timestamp - touch_start_time_five; + if (swipe_five.tracking && !swipe_five.fired) { + double duration = timestamp - swipe_five.start_time; if (duration <= TAP_MAX_DURATION && - touch_max_delta_five <= TAP_MAX_MOVEMENT) { + swipe_five.max_delta <= TAP_MAX_MOVEMENT) { dispatch_gesture(Gesture_FiveFingerTap); } } - touch_tracking = false; - touch_fired = false; - touch_tracking_two = false; - touch_fired_two = false; - touch_tracking_four = false; - touch_fired_four = false; - touch_tracking_five = false; - touch_fired_five = false; + reset_touch_state(); return; } @@ -171,180 +199,26 @@ touch_callback(MTDeviceRef device, MTTouch *data, size_t nFingers, double timest }; if (nFingers != 2) { - touch_tracking_two = false; - touch_fired_two = false; + swipe_two.tracking = false; + swipe_two.fired = false; } if (nFingers == 2) { - if (!touch_tracking_two) { - touch_tracking_two = true; - touch_fired_two = false; - touch_start_pos_two = avg_pos; - return; - } - - if (touch_fired_two) return; - - float delta_x = avg_pos.x - touch_start_pos_two.x; - float delta_y = avg_pos.y - touch_start_pos_two.y; - - if (delta_x < -SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_two = true; - dispatch_gesture(Gesture_TwoFingerSwipeLeft); - } else if (delta_x > SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_two = true; - dispatch_gesture(Gesture_TwoFingerSwipeRight); - } else if (delta_y < -SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_two = true; - dispatch_gesture(Gesture_TwoFingerSwipeDown); - } else if (delta_y > SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_two = true; - dispatch_gesture(Gesture_TwoFingerSwipeUp); - } + handle_swipe_multi(&swipe_two, timestamp, avg_pos, avg_vel, + Gesture_TwoFingerSwipeLeft, Gesture_TwoFingerSwipeRight, + Gesture_TwoFingerSwipeDown, Gesture_TwoFingerSwipeUp); } else if (nFingers == 3) { - if (!touch_tracking) { - touch_tracking = true; - touch_fired = false; - touch_start_pos = avg_pos; - touch_start_time = timestamp; - touch_max_delta = 0.0f; - return; - } - - if (touch_fired) return; - - float delta_x = avg_pos.x - touch_start_pos.x; - float delta_y = avg_pos.y - touch_start_pos.y; - float abs_dx = fabsf(delta_x); - float abs_dy = fabsf(delta_y); - if (abs_dx > touch_max_delta) touch_max_delta = abs_dx; - if (abs_dy > touch_max_delta) touch_max_delta = abs_dy; - - if (delta_x < -SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired = true; - dispatch_gesture(Gesture_ThreeFingerSwipeLeft); - } else if (delta_x > SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired = true; - dispatch_gesture(Gesture_ThreeFingerSwipeRight); - } else if (delta_y < -SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired = true; - dispatch_gesture(Gesture_ThreeFingerSwipeDown); - } else if (delta_y > SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired = true; - dispatch_gesture(Gesture_ThreeFingerSwipeUp); - } + handle_swipe_multi(&swipe_three, timestamp, avg_pos, avg_vel, + Gesture_ThreeFingerSwipeLeft, Gesture_ThreeFingerSwipeRight, + Gesture_ThreeFingerSwipeDown, Gesture_ThreeFingerSwipeUp); } else if (nFingers == 4) { - if (!touch_tracking_four) { - touch_tracking_four = true; - touch_fired_four = false; - touch_start_pos_four = avg_pos; - touch_start_time_four = timestamp; - touch_max_delta_four = 0.0f; - return; - } - - if (touch_fired_four) return; - - float delta_x = avg_pos.x - touch_start_pos_four.x; - float delta_y = avg_pos.y - touch_start_pos_four.y; - float abs_dx = fabsf(delta_x); - float abs_dy = fabsf(delta_y); - if (abs_dx > touch_max_delta_four) touch_max_delta_four = abs_dx; - if (abs_dy > touch_max_delta_four) touch_max_delta_four = abs_dy; - - if (delta_x < -SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_four = true; - dispatch_gesture(Gesture_FourFingerSwipeLeft); - } else if (delta_x > SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_four = true; - dispatch_gesture(Gesture_FourFingerSwipeRight); - } else if (delta_y < -SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_four = true; - dispatch_gesture(Gesture_FourFingerSwipeDown); - } else if (delta_y > SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_four = true; - dispatch_gesture(Gesture_FourFingerSwipeUp); - } + handle_swipe_multi(&swipe_four, timestamp, avg_pos, avg_vel, + Gesture_FourFingerSwipeLeft, Gesture_FourFingerSwipeRight, + Gesture_FourFingerSwipeDown, Gesture_FourFingerSwipeUp); } else if (nFingers == 5) { - if (!touch_tracking_five) { - touch_tracking_five = true; - touch_fired_five = false; - touch_start_pos_five = avg_pos; - touch_start_time_five = timestamp; - touch_max_delta_five = 0.0f; - return; - } - - if (touch_fired_five) return; - - float delta_x = avg_pos.x - touch_start_pos_five.x; - float delta_y = avg_pos.y - touch_start_pos_five.y; - float abs_dx = fabsf(delta_x); - float abs_dy = fabsf(delta_y); - if (abs_dx > touch_max_delta_five) touch_max_delta_five = abs_dx; - if (abs_dy > touch_max_delta_five) touch_max_delta_five = abs_dy; - - if (delta_x < -SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_five = true; - dispatch_gesture(Gesture_FiveFingerSwipeLeft); - } else if (delta_x > SWIPE_MIN_DISTANCE && - fabsf(delta_x) > fabsf(delta_y) * SWIPE_AXIS_RATIO && - avg_vel.x > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.x) > fabsf(avg_vel.y) * SWIPE_AXIS_RATIO) { - touch_fired_five = true; - dispatch_gesture(Gesture_FiveFingerSwipeRight); - } else if (delta_y < -SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y < -SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_five = true; - dispatch_gesture(Gesture_FiveFingerSwipeDown); - } else if (delta_y > SWIPE_MIN_DISTANCE && - fabsf(delta_y) > fabsf(delta_x) * SWIPE_AXIS_RATIO && - avg_vel.y > SWIPE_MIN_VELOCITY && - fabsf(avg_vel.y) > fabsf(avg_vel.x) * SWIPE_AXIS_RATIO) { - touch_fired_five = true; - dispatch_gesture(Gesture_FiveFingerSwipeUp); - } + handle_swipe_multi(&swipe_five, timestamp, avg_pos, avg_vel, + Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, + Gesture_FiveFingerSwipeDown, Gesture_FiveFingerSwipeUp); } } @@ -386,13 +260,6 @@ bool touch_begin(struct table *mode_map, struct table *blacklst, struct mode **c } MTRegisterContactFrameCallback(touch_device, touch_callback); - if (!MTDeviceIsValid(touch_device)) { - warn("failed to register multitouch callback.. touch gestures disabled\n"); - MTDeviceRelease(touch_device); - touch_device = NULL; - reset_touch_state(); - return false; - } OSStatus start_status = MTDeviceStart(touch_device, 0); if (start_status != noErr) { From 757b72eac656f5951721b7426e565896599b12e6 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:28:21 +0100 Subject: [PATCH 06/10] fix: Made cgevent_flags_to_hotkey_flags static again --- src/hotkey.c | 2 +- src/hotkey.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotkey.c b/src/hotkey.c index 9e1aec3..6ed79f5 100644 --- a/src/hotkey.c +++ b/src/hotkey.c @@ -252,7 +252,7 @@ cgevent_lrmod_flag_to_hotkey_lrmod_flag(CGEventFlags eventflags, uint32_t *flags } } -uint32_t +static uint32_t cgevent_flags_to_hotkey_flags(uint32_t eventflags) { uint32_t flags = 0; diff --git a/src/hotkey.h b/src/hotkey.h index f3f1cb1..f0a8e98 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -127,7 +127,7 @@ unsigned long hash_hotkey(struct hotkey *a); struct hotkey create_eventkey(CGEventRef event); bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey); -uint32_t cgevent_flags_to_hotkey_flags(uint32_t eventflags); +static uint32_t cgevent_flags_to_hotkey_flags(uint32_t eventflags); bool find_and_exec_hotkey(struct hotkey *k, struct table *t, struct mode **m, struct carbon_event *carbon); void free_mode_map(struct table *mode_map); From dc6cb7c3ce006285c1cfc83a767f1ddf8ed177c4 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:34:33 +0100 Subject: [PATCH 07/10] fix: Reversed parse.c changes --- src/parse.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/parse.c b/src/parse.c index 0563a82..05698d8 100644 --- a/src/parse.c +++ b/src/parse.c @@ -152,8 +152,7 @@ parse_key(struct parser *parser) } #define KEY_HAS_IMPLICIT_FN_MOD 4 -#define KEY_NX_START 35 -#define KEY_NX_END 47 +#define KEY_HAS_IMPLICIT_NX_MOD 35 static uint32_t literal_keycode_value[] = { kVK_Return, kVK_Tab, kVK_Space, @@ -192,9 +191,9 @@ static inline void handle_implicit_literal_flags(struct hotkey *hotkey, int literal_index) { if ((literal_index > KEY_HAS_IMPLICIT_FN_MOD) && - (literal_index < KEY_NX_START)) { + (literal_index < KEY_HAS_IMPLICIT_NX_MOD)) { hotkey->flags |= Hotkey_Flag_Fn; - } else if (literal_index >= KEY_NX_START && literal_index < KEY_NX_END) { + } else if (literal_index >= KEY_HAS_IMPLICIT_NX_MOD) { hotkey->flags |= Hotkey_Flag_NX; } } From ef20c87bc5b912fd5e13e33086783faa1e290be9 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:36:06 +0100 Subject: [PATCH 08/10] fix: Reversed parse.c changes --- src/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse.c b/src/parse.c index 05698d8..9e2d7f3 100644 --- a/src/parse.c +++ b/src/parse.c @@ -152,7 +152,7 @@ parse_key(struct parser *parser) } #define KEY_HAS_IMPLICIT_FN_MOD 4 -#define KEY_HAS_IMPLICIT_NX_MOD 35 +#define KEY_HAS_IMPLICIT_NX_MOD 35 static uint32_t literal_keycode_value[] = { kVK_Return, kVK_Tab, kVK_Space, From da101e55117f44f44a18de92bc252385315d37d0 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:39:23 +0100 Subject: [PATCH 09/10] chore: some cleanup --- src/parse.c | 4 ++-- src/tokenize.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parse.c b/src/parse.c index 9e2d7f3..e87acd1 100644 --- a/src/parse.c +++ b/src/parse.c @@ -175,6 +175,7 @@ static uint32_t literal_keycode_value[] = Gesture_TwoFingerSwipeLeft, Gesture_TwoFingerSwipeRight, Gesture_TwoFingerSwipeUp, Gesture_TwoFingerSwipeDown, + Gesture_TwoFingerTap, Gesture_ThreeFingerSwipeLeft, Gesture_ThreeFingerSwipeRight, Gesture_ThreeFingerSwipeUp, Gesture_ThreeFingerSwipeDown, Gesture_ThreeFingerTap, @@ -183,8 +184,7 @@ static uint32_t literal_keycode_value[] = Gesture_FourFingerTap, Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, Gesture_FiveFingerSwipeUp, Gesture_FiveFingerSwipeDown, - Gesture_FiveFingerTap, - Gesture_TwoFingerTap + Gesture_FiveFingerTap }; static inline void diff --git a/src/tokenize.h b/src/tokenize.h index 995b01d..ab04c1f 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -32,6 +32,7 @@ static const char *literal_keycode_str[] = "two_finger_swipe_left", "two_finger_swipe_right", "two_finger_swipe_up", "two_finger_swipe_down", + "two_finger_tap", "three_finger_swipe_left", "three_finger_swipe_right", "three_finger_swipe_up", "three_finger_swipe_down", "three_finger_tap", @@ -40,8 +41,7 @@ static const char *literal_keycode_str[] = "four_finger_tap", "five_finger_swipe_left", "five_finger_swipe_right", "five_finger_swipe_up", "five_finger_swipe_down", - "five_finger_tap", - "two_finger_tap" + "five_finger_tap" }; enum token_type From 9cca12565ef48b0de85d3e1f214d3ad5f32f1875 Mon Sep 17 00:00:00 2001 From: koproductions Date: Wed, 21 Jan 2026 18:44:56 +0100 Subject: [PATCH 10/10] fix: Added End to hotkey flag nx --- src/parse.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parse.c b/src/parse.c index e87acd1..2a1bb2f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -152,7 +152,8 @@ parse_key(struct parser *parser) } #define KEY_HAS_IMPLICIT_FN_MOD 4 -#define KEY_HAS_IMPLICIT_NX_MOD 35 +#define KEY_NX_START 35 +#define KEY_NX_END 47 static uint32_t literal_keycode_value[] = { kVK_Return, kVK_Tab, kVK_Space, @@ -191,9 +192,9 @@ static inline void handle_implicit_literal_flags(struct hotkey *hotkey, int literal_index) { if ((literal_index > KEY_HAS_IMPLICIT_FN_MOD) && - (literal_index < KEY_HAS_IMPLICIT_NX_MOD)) { + (literal_index < KEY_NX_START)) { hotkey->flags |= Hotkey_Flag_Fn; - } else if (literal_index >= KEY_HAS_IMPLICIT_NX_MOD) { + } else if (literal_index >= KEY_NX_START && literal_index < KEY_NX_END) { hotkey->flags |= Hotkey_Flag_NX; } }