Skip to content

Instantly share code, notes, and snippets.

@tmattio
Created January 8, 2025 15:47
Show Gist options
  • Save tmattio/f68d05dadbc4a32b0467d0601b628b67 to your computer and use it in GitHub Desktop.
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.
#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.