Created
January 8, 2025 15:47
-
-
Save tmattio/f68d05dadbc4a32b0467d0601b628b67 to your computer and use it in GitHub Desktop.
Stardew Valley Animation Canceler - A macOS tool that automates keypresses to cancel animations in Stardew Valley, optimizing actions like mining and farming. Triggered by a key (default: Spacebar) and requires accessibility permissions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <sys/sysctl.h> | |
#include <stdbool.h> | |
#include <time.h> | |
#include <string.h> | |
#include <libproc.h> | |
#include <pthread.h> | |
#include <Carbon/Carbon.h> | |
// Default configuration values | |
#define KEY_CODE 49 // Space key | |
#define CANCEL_DELAY_MS 20 | |
#define KEY_DELAY_MS 10 | |
#define ACTION_KEY_CODE 8 // C key | |
// Global event source and keyboard events | |
CGEventSourceRef g_event_source = NULL; | |
CGEventRef g_key_event_action_down = NULL; | |
CGEventRef g_key_event_action_up = NULL; | |
CGEventRef g_key_event_rshift_down = NULL; | |
CGEventRef g_key_event_r_down = NULL; | |
CGEventRef g_key_event_delete_down = NULL; | |
CGEventRef g_key_event_rshift_up = NULL; | |
CGEventRef g_key_event_r_up = NULL; | |
CGEventRef g_key_event_delete_up = NULL; | |
void cleanup_events(void) | |
{ | |
if (g_event_source) | |
CFRelease(g_event_source); | |
if (g_key_event_action_down) | |
CFRelease(g_key_event_action_down); | |
if (g_key_event_action_up) | |
CFRelease(g_key_event_action_up); | |
if (g_key_event_rshift_down) | |
CFRelease(g_key_event_rshift_down); | |
if (g_key_event_r_down) | |
CFRelease(g_key_event_r_down); | |
if (g_key_event_delete_down) | |
CFRelease(g_key_event_delete_down); | |
if (g_key_event_rshift_up) | |
CFRelease(g_key_event_rshift_up); | |
if (g_key_event_r_up) | |
CFRelease(g_key_event_r_up); | |
if (g_key_event_delete_up) | |
CFRelease(g_key_event_delete_up); | |
} | |
bool initialize_events(void) | |
{ | |
g_event_source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); | |
if (!g_event_source) | |
{ | |
fprintf(stderr, "ERROR: Failed to create event source. Ensure accessibility permissions are enabled.\n"); | |
return false; | |
} | |
g_key_event_action_down = CGEventCreateKeyboardEvent(g_event_source, ACTION_KEY_CODE, true); | |
g_key_event_action_up = CGEventCreateKeyboardEvent(g_event_source, ACTION_KEY_CODE, false); | |
g_key_event_rshift_down = CGEventCreateKeyboardEvent(g_event_source, kVK_RightShift, true); | |
g_key_event_r_down = CGEventCreateKeyboardEvent(g_event_source, kVK_ANSI_R, true); | |
g_key_event_delete_down = CGEventCreateKeyboardEvent(g_event_source, kVK_ForwardDelete, true); | |
g_key_event_rshift_up = CGEventCreateKeyboardEvent(g_event_source, kVK_RightShift, false); | |
g_key_event_r_up = CGEventCreateKeyboardEvent(g_event_source, kVK_ANSI_R, false); | |
g_key_event_delete_up = CGEventCreateKeyboardEvent(g_event_source, kVK_ForwardDelete, false); | |
if (!g_key_event_action_down || !g_key_event_action_up || | |
!g_key_event_rshift_down || !g_key_event_r_down || !g_key_event_delete_down || | |
!g_key_event_rshift_up || !g_key_event_r_up || !g_key_event_delete_up) | |
{ | |
fprintf(stderr, "ERROR: Failed to create keyboard events. Cleaning up resources.\n"); | |
cleanup_events(); | |
return false; | |
} | |
return true; | |
} | |
void cancel_animation(void) | |
{ | |
printf("INFO: Cancel animation event triggered.\n"); | |
CGEventPost(kCGSessionEventTap, g_key_event_action_down); | |
usleep(KEY_DELAY_MS * 1000); | |
CGEventPost(kCGSessionEventTap, g_key_event_action_up); | |
usleep(CANCEL_DELAY_MS * 1000); | |
CGEventPost(kCGSessionEventTap, g_key_event_rshift_down); | |
CGEventPost(kCGSessionEventTap, g_key_event_r_down); | |
CGEventPost(kCGSessionEventTap, g_key_event_delete_down); | |
usleep(KEY_DELAY_MS * 1000); | |
CGEventPost(kCGSessionEventTap, g_key_event_rshift_up); | |
CGEventPost(kCGSessionEventTap, g_key_event_r_up); | |
CGEventPost(kCGSessionEventTap, g_key_event_delete_up); | |
} | |
void *cancel_animation_new_thread(void *arg) | |
{ | |
cancel_animation(); | |
return NULL; | |
} | |
CGEventRef cgevent_callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) | |
{ | |
if (type != kCGEventKeyDown && type != kCGEventFlagsChanged) | |
{ | |
return event; | |
} | |
CGKeyCode key_code = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); | |
if (key_code == KEY_CODE) | |
{ | |
pthread_t thread; | |
if (pthread_create(&thread, NULL, cancel_animation_new_thread, NULL) != 0) | |
{ | |
fprintf(stderr, "ERROR: Failed to create a new thread for animation cancelation.\n"); | |
} | |
pthread_detach(thread); | |
} | |
return event; | |
} | |
int main(int argc, const char *argv[]) | |
{ | |
if (!initialize_events()) | |
{ | |
fprintf(stderr, "ERROR: Unable to initialize keyboard events. Exiting program.\n"); | |
return 1; | |
} | |
CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventFlagsChanged); | |
CFMachPortRef event_tap = CGEventTapCreate( | |
kCGSessionEventTap, | |
kCGHeadInsertEventTap, | |
0, | |
event_mask, | |
cgevent_callback, | |
NULL); | |
if (!event_tap) | |
{ | |
fprintf(stderr, "ERROR: Unable to create event tap. Ensure the application has proper accessibility permissions.\n"); | |
cleanup_events(); | |
CFOptionFlags response_flags; | |
CFUserNotificationDisplayAlert( | |
0, | |
kCFUserNotificationStopAlertLevel, | |
NULL, | |
NULL, | |
NULL, | |
CFSTR("Accessibility Permissions Required"), | |
CFSTR("This application requires accessibility permissions. Grant them in System Preferences under Security & Privacy > Accessibility."), | |
CFSTR("Quit"), | |
NULL, | |
NULL, | |
&response_flags); | |
return 1; | |
} | |
CFRunLoopSourceRef run_loop_source = CFMachPortCreateRunLoopSource( | |
kCFAllocatorDefault, | |
event_tap, | |
0); | |
if (!run_loop_source) | |
{ | |
fprintf(stderr, "ERROR: Failed to create run loop source. Cleaning up and exiting.\n"); | |
CFRelease(event_tap); | |
cleanup_events(); | |
return 1; | |
} | |
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopCommonModes); | |
CGEventTapEnable(event_tap, true); | |
atexit(cleanup_events); | |
printf("INFO: Application running. Press Space to trigger animation cancelation.\n"); | |
CFRunLoopRun(); | |
return 0; | |
} |
Comments are disabled for this gist.