3434#include <X11/extensions/Xfixes.h>
3535#include <X11/extensions/shape.h>
3636#include <errno.h>
37+ #include <fcntl.h>
3738#include <getopt.h>
3839#include <limits.h>
3940#include <signal.h>
4041#include <stdio.h>
4142#include <stdlib.h>
4243#include <string.h>
44+ #include <sys/file.h>
4345#include <sys/select.h>
4446#include <unistd.h>
4547
@@ -49,6 +51,7 @@ static Window win;
4951static Window root ;
5052static int screen ;
5153static int selfpipe [2 ]; /* for self-pipe trick to cancel select() call */
54+ static int lock_fd = -1 ;
5255
5356#define KEY_MODMAP_SIZE 4
5457static struct {
@@ -97,11 +100,13 @@ static struct {
97100 int highlight_visible ;
98101 int outline ;
99102 int radius ;
103+ int single_instance ;
100104 double opacity ;
101105} options ;
102106
103107static void redraw ();
104108static int get_pointer_position (int * x , int * y );
109+ static int acquire_single_instance_lock ();
105110
106111static void show_cursor () {
107112 XFixesShowCursor (dpy , root );
@@ -513,6 +518,7 @@ static void print_usage(const char* name) {
513518 " --opacity OPACITY window opacity (0.0 - 1.0) [default: 1.0]\n"
514519 " --force-raise raise highlighter on every pointer motion\n"
515520 " --hide-highlight start with highlighter hidden\n"
521+ " --single-instance exit if another instance is already running\n"
516522 " --show-cursor start with cursor shown\n"
517523 "\n"
518524 "TIMEOUT OPTIONS\n"
@@ -551,6 +557,7 @@ static struct option long_options[] = {{"auto-hide-cursor", no_argument, &option
551557 {"radius" , required_argument , NULL , 'r' },
552558 {"released-color" , required_argument , NULL , 'c' },
553559 {"show-cursor" , no_argument , & options .cursor_visible , 1 },
560+ {"single-instance" , no_argument , & options .single_instance , 1 },
554561 {"key-quit" , required_argument , NULL , KEY_QUIT + KEY_OPTION_OFFSET },
555562 {"key-toggle-cursor" , required_argument , NULL , KEY_TOGGLE_CURSOR + KEY_OPTION_OFFSET },
556563 {"key-toggle-highlight" , required_argument , NULL , KEY_TOGGLE_HIGHLIGHT + KEY_OPTION_OFFSET },
@@ -597,6 +604,7 @@ static int set_options(int argc, char* argv[]) {
597604 options .opacity = 1.0 ;
598605 options .radius = 5 ;
599606 options .outline = 0 ;
607+ options .single_instance = 0 ;
600608 options .hide_timeout = 3 ;
601609 options .pressed_color_string = "#1f77b4" ;
602610 options .released_color_string = "#d62728" ;
@@ -660,6 +668,42 @@ static int set_options(int argc, char* argv[]) {
660668 return 0 ;
661669}
662670
671+ static int acquire_single_instance_lock () {
672+ const char * dir = getenv ("XDG_RUNTIME_DIR" );
673+ char path [4096 ];
674+
675+ if (!dir || dir [0 ] == '\0' ) {
676+ dir = getenv ("TMPDIR" );
677+ }
678+ if (!dir || dir [0 ] == '\0' ) {
679+ dir = "/tmp" ;
680+ }
681+
682+ if (snprintf (path , sizeof (path ), "%s/highlight-pointer-%ld.lock" , dir , (long )getuid ()) >= (int )sizeof (path )) {
683+ fprintf (stderr , "Lock file path too long\n" );
684+ return 1 ;
685+ }
686+
687+ lock_fd = open (path , O_RDWR | O_CREAT , 0600 );
688+ if (lock_fd < 0 ) {
689+ perror ("open lock file failed" );
690+ return 1 ;
691+ }
692+
693+ if (flock (lock_fd , LOCK_EX | LOCK_NB ) < 0 ) {
694+ if (errno == EWOULDBLOCK ) {
695+ fprintf (stderr , "highlight-pointer is already running\n" );
696+ } else {
697+ perror ("lock file failed" );
698+ }
699+ close (lock_fd );
700+ lock_fd = -1 ;
701+ return 1 ;
702+ }
703+
704+ return 0 ;
705+ }
706+
663707static int xerror_handler (Display * dpy_p , XErrorEvent * err ) {
664708 if (err -> request_code == 33 /* XGrabKey */ && err -> error_code == BadAccess ) {
665709 fprintf (stderr , "Key combination already grabbed by a different process\n" );
@@ -685,6 +729,10 @@ int main(int argc, char* argv[]) {
685729 return res ;
686730 }
687731
732+ if (options .single_instance && acquire_single_instance_lock ()) {
733+ return 1 ;
734+ }
735+
688736 dpy = XOpenDisplay (NULL ); /* defaults to DISPLAY env var */
689737 if (!dpy ) {
690738 fprintf (stderr , "Can't open display\n" );
0 commit comments