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.h b/src/hotkey.h index 78aba3f..f0a8e98 100644 --- a/src/hotkey.h +++ b/src/hotkey.h @@ -11,6 +11,28 @@ #define Modifier_Keycode_Ctrl 0x3B #define Modifier_Keycode_Fn 0x3F +#define Gesture_Keycode_Base 0x10000000 +#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) +#define Gesture_TwoFingerTap (Gesture_Keycode_Base + 20) + enum osx_event_mask { Event_Mask_Alt = 0x00080000, @@ -105,6 +127,7 @@ unsigned long hash_hotkey(struct hotkey *a); struct hotkey create_eventkey(CGEventRef event); bool intercept_systemkey(CGEventRef event, struct hotkey *eventkey); +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); 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..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, @@ -171,16 +172,29 @@ 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_TwoFingerSwipeLeft, Gesture_TwoFingerSwipeRight, + Gesture_TwoFingerSwipeUp, Gesture_TwoFingerSwipeDown, + Gesture_TwoFingerTap, + 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 }; 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..ab04c1f 100644 --- a/src/tokenize.h +++ b/src/tokenize.h @@ -28,7 +28,20 @@ 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", + + "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", + "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" }; enum token_type diff --git a/src/touch.c b/src/touch.c new file mode 100644 index 0000000..67e6dbe --- /dev/null +++ b/src/touch.c @@ -0,0 +1,286 @@ +#include +#include +#include + +#include "multitouch.h" +#include "touch.h" +#include "log.h" +#include "hotkey.h" +#include "carbon.h" +#include "hashtable.h" + +#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 +{ + struct table *mode_map; + struct table *blacklst; + struct mode **current_mode; + 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 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) +{ + 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 +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 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) +{ + (void) device; + (void) timestamp; + (void) frame; + + 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 && + swipe_three.max_delta <= TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_ThreeFingerTap); + } + } + if (swipe_four.tracking && !swipe_four.fired) { + double duration = timestamp - swipe_four.start_time; + if (duration <= TAP_MAX_DURATION && + swipe_four.max_delta <= TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_FourFingerTap); + } + } + if (swipe_five.tracking && !swipe_five.fired) { + double duration = timestamp - swipe_five.start_time; + if (duration <= TAP_MAX_DURATION && + swipe_five.max_delta <= TAP_MAX_MOVEMENT) { + dispatch_gesture(Gesture_FiveFingerTap); + } + } + reset_touch_state(); + return; + } + + float sum_x = 0.0f; + float sum_y = 0.0f; + float sum_vx = 0.0f; + float sum_vy = 0.0f; + int active = 0; + + for (size_t i = 0; i < nFingers; ++i) { + 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 >= 2 && 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 != 2) { + swipe_two.tracking = false; + swipe_two.fired = false; + } + + if (nFingers == 2) { + handle_swipe_multi(&swipe_two, timestamp, avg_pos, avg_vel, + Gesture_TwoFingerSwipeLeft, Gesture_TwoFingerSwipeRight, + Gesture_TwoFingerSwipeDown, Gesture_TwoFingerSwipeUp); + } else if (nFingers == 3) { + handle_swipe_multi(&swipe_three, timestamp, avg_pos, avg_vel, + Gesture_ThreeFingerSwipeLeft, Gesture_ThreeFingerSwipeRight, + Gesture_ThreeFingerSwipeDown, Gesture_ThreeFingerSwipeUp); + } else if (nFingers == 4) { + handle_swipe_multi(&swipe_four, timestamp, avg_pos, avg_vel, + Gesture_FourFingerSwipeLeft, Gesture_FourFingerSwipeRight, + Gesture_FourFingerSwipeDown, Gesture_FourFingerSwipeUp); + } else if (nFingers == 5) { + handle_swipe_multi(&swipe_five, timestamp, avg_pos, avg_vel, + Gesture_FiveFingerSwipeLeft, Gesture_FiveFingerSwipeRight, + Gesture_FiveFingerSwipeDown, 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); + + 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); + 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