Skip to content

Instantly share code, notes, and snippets.

@nm004
Last active April 21, 2024 10:45
Show Gist options
  • Save nm004/e34b40ef3c2ab3ab8ec3eab2376ca7ab to your computer and use it in GitHub Desktop.
Save nm004/e34b40ef3c2ab3ab8ec3eab2376ca7ab to your computer and use it in GitHub Desktop.
/*
* autoholdclick - auto clicking while holding a button (for Linux only).
*
* Build:
* cc -o autoholdclick autoholdclick.c $(pkg-config --cflags --libs libevdev)
*
* This software is in the public domain.
*/
#include <libevdev/libevdev-uinput.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
char *program_invocation_name;
const char YES_AUTO_CLICK = !0;
int is_interrupted_by_user = 0;
struct libevdev_uinput *uinput_dev = NULL;
int current_key_code = 0;
static inline void
click ()
{
libevdev_uinput_write_event (uinput_dev, EV_KEY, current_key_code, 1);
libevdev_uinput_write_event (uinput_dev, EV_SYN, SYN_REPORT, 0);
libevdev_uinput_write_event (uinput_dev, EV_KEY, current_key_code, 0);
libevdev_uinput_write_event (uinput_dev, EV_SYN, SYN_REPORT, 0);
}
static void
on_sigalrm (int sig)
{
click ();
}
static void
on_sigint (int sig)
{
is_interrupted_by_user = 1;
}
static inline void
set_sig (void)
{
sigset_t block_set;
sigfillset(&block_set);
sigdelset(&block_set, SIGALRM);
sigdelset(&block_set, SIGTERM);
sigdelset(&block_set, SIGHUP);
sigdelset(&block_set, SIGINT);
sigdelset(&block_set, SIGUSR1);
sigdelset(&block_set, SIGUSR2);
sigprocmask(SIG_BLOCK, &block_set, NULL);
sigaction (SIGALRM, &(struct sigaction){ .sa_handler = on_sigalrm }, NULL);
sigaction (SIGTERM, &(struct sigaction){ .sa_handler = on_sigint }, NULL);
sigaction (SIGHUP, &(struct sigaction){ .sa_handler = on_sigint }, NULL);
sigaction (SIGINT, &(struct sigaction){ .sa_handler = on_sigint }, NULL);
sigaction (SIGUSR1, &(struct sigaction){ .sa_handler = on_sigint }, NULL);
sigaction (SIGUSR2, &(struct sigaction){ .sa_handler = on_sigint }, NULL);
}
static inline void
usage (void)
{
printf(
"Usage: %s [OPTION]... [BTN]... INPUT_DEV \n"
"Click repeatedly while holding a button BTN of a device INPUT_DEV.\n"
"\n"
" BTN, Buttons subject to auto click. Default is None (no buttons\n"
" to auto click). Valid button name is BTN_LEFT, BTN_RIGHT,\n"
" BTN_MIDDLE, BTN_THUMB, BTN_4, BTN_5, etc.\n"
" INPUT_DEV, evdev device (need read permission)\n"
"\n"
"Options:\n"
" -c, Use the complement of BTNs.\n"
" -T, Use hardware timing to auto-click. Default is off (use software timing).\n"
"\n"
"Also, need read/write permission for /dev/uinput.\n"
"\n"
"Example: %s BTN_MIDDLE BTN_LEFT /dev/input/by-id/your-event-mouse\n"
, program_invocation_name, program_invocation_name
);
}
int
main (int argc,
char **argv)
{
int rc = 0;
int in_dev_fd = -1;
struct libevdev* in_dev = NULL;
int use_hw_timing = 0;
char auto_click_flag_tbl[KEY_CNT] = {0};
/* TODO?: make them user options. */
const suseconds_t interval_usec = 32 * 1000;
const suseconds_t start_delay_usec = 200 * 1000;
program_invocation_name = argv[0];
if (argc < 2)
{
usage ();
return !0;
}
while ((rc = getopt(argc, argv, "cT")) != -1)
{
switch (rc)
{
case 'c':
/* Set every buttons' auto click flag up */
memset (auto_click_flag_tbl, YES_AUTO_CLICK, sizeof(auto_click_flag_tbl));
break;
case 'T':
use_hw_timing = 1;
break;
default:
usage ();
return !0;
}
}
/* Set each BTN's auto click flag */
for (int i = optind; i < argc-1; i++)
{
int code = libevdev_event_code_from_code_name(argv[i]);
if (code == -1)
{
fprintf(stderr, "%s is not a valid button name.\n", argv[i]);
return !0;
}
auto_click_flag_tbl[code] = !auto_click_flag_tbl[code];
}
in_dev_fd = open (argv[argc-1], O_RDONLY);
if (in_dev_fd == -1)
{
perror("failed to open device file");
return !0;
}
/* Obtain input device */
rc = libevdev_new_from_fd (in_dev_fd, &in_dev);
if (rc < 0)
{
perror("failed to obtain input device");
return !0;
}
/* Create simulated input device */
rc = libevdev_uinput_create_from_device (in_dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput_dev);
if (rc < 0)
{
perror("failed to create uinput device");
libevdev_free (in_dev);
return !0;
}
set_sig();
puts ("Auto click mode is starting in 1 sec. DON'T PRESS ANY BUTTONS/KEYS.");
sleep (1);
/* Event loop */
libevdev_grab (in_dev, LIBEVDEV_GRAB);
puts ("Auto click is enabled");
do
{
struct input_event ev;
if (is_interrupted_by_user)
{
break;
}
rc = libevdev_next_event (in_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (rc != LIBEVDEV_READ_STATUS_SUCCESS)
{
continue;
}
/* Pass through events */
libevdev_uinput_write_event (uinput_dev, ev.type, ev.code, ev.value);
/* When not key events, nor the key subject to auto click, then just pass through events */
if (ev.type != EV_KEY
|| auto_click_flag_tbl[ev.code] != YES_AUTO_CLICK)
{
continue;
}
/* Process button press events */
if (use_hw_timing)
{
if (ev.value == 2)
{
current_key_code = ev.code;
click ();
}
}
/* Use software timing */
else
{
/* Arm the timer */
if (ev.value == 1)
{
current_key_code = ev.code;
setitimer (ITIMER_REAL, &(struct itimerval){{0,interval_usec},{0,start_delay_usec}}, NULL);
}
/* Disarm the timer */
else if (ev.value == 0)
{
setitimer (ITIMER_REAL, &(struct itimerval){0}, NULL);
}
}
}
while (rc == LIBEVDEV_READ_STATUS_SYNC
|| rc == LIBEVDEV_READ_STATUS_SUCCESS
|| rc == -EAGAIN
|| rc == -EINTR);
libevdev_grab (in_dev, LIBEVDEV_UNGRAB);
puts ("Auto click is disabled");
setitimer (ITIMER_REAL, &(struct itimerval){0}, NULL);
libevdev_uinput_destroy (uinput_dev);
libevdev_free (in_dev);
close (in_dev_fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment