Browse Source

initial commit

Peter Cai 9 months ago
commit
358a24e653
8 changed files with 315 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 23
    0
      Makefile
  3. 159
    0
      input.c
  4. 6
    0
      input.h
  5. 87
    0
      rce.c
  6. 6
    0
      rce.h
  7. 26
    0
      uinput.c
  8. 7
    0
      uinput.h

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+out

+ 23
- 0
Makefile View File

@@ -0,0 +1,23 @@
1
+CC := gcc
2
+XFLAGS := -Wall -std=c11 -D_POSIX_C_SOURCE=199309L
3
+LIBRARIES := -levdev
4
+INCLUDES := -I/usr/include/libevdev-1.0
5
+CFLAGS := $(XFLAGS) $(LIBRARIES) $(INCLUDES)
6
+
7
+OUTDIR := out
8
+SOURCES := uinput.c input.c rce.c
9
+OBJS := $(SOURCES:%.c=$(OUTDIR)/%.o)
10
+TARGET := $(OUTDIR)/evdev-rce
11
+
12
+.PHONY: all clean
13
+
14
+$(OUTDIR)/%.o: %.c
15
+	@mkdir -p $(OUTDIR)
16
+	$(CC) $(CFLAGS) -c $< -o [email protected]
17
+
18
+$(TARGET): $(OBJS)
19
+	$(CC) $(CFLAGS) $^ -o [email protected]
20
+
21
+all: $(TARGET)
22
+clean:
23
+	rm -rf $(OUTDIR)

+ 159
- 0
input.c View File

@@ -0,0 +1,159 @@
1
+#include "input.h"
2
+#include "uinput.h"
3
+#include "rce.h"
4
+#include <errno.h>
5
+#include <stdio.h>
6
+#include <stdlib.h>
7
+#include <sys/select.h>
8
+#include <sys/timerfd.h>
9
+#include <unistd.h>
10
+
11
+struct input_state_t {
12
+    int pressed_device_id;
13
+    int pos_x, pos_y;
14
+    int pressed_pos_x, pressed_pos_y;
15
+    int fd_timer; // The timer used to execute right click on timeout
16
+    struct libevdev_uinput *uinput;
17
+};
18
+
19
+void free_evdev(struct libevdev *evdev) {
20
+    int fd = libevdev_get_fd(evdev);
21
+    libevdev_free(evdev);
22
+    close(fd);
23
+}
24
+
25
+// Build the fd set used by `select`
26
+// return: nfds to be used in select()
27
+int build_fd_set(fd_set *fds, int fd_timer,
28
+        int num, struct libevdev **evdev) {
29
+    FD_ZERO(fds);
30
+    FD_SET(fd_timer, fds);
31
+    int fd = -1, max_fd = fd_timer;
32
+    for (unsigned int i = 0; i < num; i++) {
33
+        fd = libevdev_get_fd(evdev[i]);
34
+        FD_SET(fd, fds);
35
+        if (fd > max_fd)
36
+            max_fd = fd;
37
+    }
38
+    return max_fd + 1;
39
+}
40
+
41
+void arm_delayed_rclick(struct input_state_t *state, int dev_id) {
42
+    // Record the position where the touch event began
43
+    state->pressed_pos_x = state->pos_x;
44
+    state->pressed_pos_y = state->pos_y;
45
+    state->pressed_device_id = dev_id;
46
+    // Start the timer; once timeout reached,
47
+    // fire the right click event
48
+    // TODO: Make the timeout customizable
49
+    timerfd_settime(state->fd_timer, 0, &(struct itimerspec) {
50
+        .it_value = {
51
+            .tv_sec = 1,
52
+        },
53
+    }, NULL);
54
+}
55
+
56
+void unarm_delayed_rclick(struct input_state_t *state) {
57
+    state->pressed_device_id = -1;
58
+    // Force cancel the timer if finger released
59
+    timerfd_settime(state->fd_timer, 0, &(struct itimerspec) {
60
+        .it_value = {
61
+            .tv_sec = 0,
62
+            .tv_nsec = 0,
63
+        },
64
+    }, NULL);
65
+}
66
+
67
+void on_input_event(struct input_state_t *state,
68
+        struct input_event *ev, int dev_id) {
69
+    // If we are during a long-press event,
70
+    // do not interruput with another device
71
+    if (state->pressed_device_id != -1 && dev_id != state->pressed_device_id)
72
+        return;
73
+
74
+    if (ev->type == EV_ABS) {
75
+        // Absolute position event
76
+        // Record the current position
77
+        if (ev->code == ABS_X || ev->code == ABS_MT_POSITION_X) {
78
+            state->pos_x = ev->value;
79
+        } else if (ev->code == ABS_Y || ev->code == ABS_MT_POSITION_Y) {
80
+            state->pos_y = ev->value;
81
+        }
82
+    } else if (ev->type == EV_KEY && ev->code == BTN_TOUCH) {
83
+        // Touch event
84
+        if (ev->value == 1) {
85
+            // Schedule a delayed right click event
86
+            // so that if anything happens before the long-press timeout,
87
+            // it can be canceled
88
+            arm_delayed_rclick(state, dev_id);
89
+        } else {
90
+            // Finger released. It is no longer considered a long-press
91
+            unarm_delayed_rclick(state);
92
+        }
93
+    }
94
+}
95
+
96
+void on_timer_expire(struct input_state_t *state) {
97
+    unsigned int dx = abs(state->pos_x - state->pressed_pos_x);
98
+    unsigned int dy = abs(state->pos_y - state->pressed_pos_y);
99
+    // Only consider movement within a range to be "still"
100
+    // i.e. if movement is within this value during timeout
101
+    //      , then it is a long click
102
+    // TODO: Make it customizable
103
+    if (dx <= 100 && dy <= 100)
104
+        uinput_send_right_click(state->uinput);
105
+    // In Linux implementation of timerfd, the fd becomes always "readable"
106
+    // after the timeout. So we have to unarm it after we receive the event.
107
+    unarm_delayed_rclick(state);
108
+}
109
+
110
+void process_evdev_input(int num, struct libevdev **evdev) {
111
+    // Initialize everything to -1
112
+    struct input_state_t state = {
113
+        .pressed_device_id = -1,
114
+        .pos_x = -1, .pos_y = -1,
115
+        .pressed_pos_x = -1, .pressed_pos_y = -1,
116
+        .fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK),
117
+        .uinput = uinput_initialize(),
118
+    };
119
+
120
+    // Check if uinput device was created
121
+    if (state.uinput == NULL) {
122
+        fprintf(stderr, "Failed to create uinput device");
123
+        exit(-1);
124
+        return;
125
+    }
126
+
127
+    // Initialize the set of fds
128
+    fd_set fds;
129
+    int nfds = build_fd_set(&fds, state.fd_timer, num, evdev);
130
+
131
+    struct input_event ev;
132
+    // Detect events from the fds
133
+    while (select(nfds, &fds, NULL, NULL, NULL) > 0) {
134
+        // The timer fd
135
+        if (FD_ISSET(state.fd_timer, &fds))
136
+            on_timer_expire(&state);
137
+
138
+        for (unsigned int i = 0; i < num; i++) {
139
+            // Can we read an event?
140
+            if (!FD_ISSET(libevdev_get_fd(evdev[i]), &fds))
141
+                continue;
142
+            
143
+            // Read all the available events
144
+            while (libevdev_next_event(evdev[i],
145
+                    LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0) {
146
+                on_input_event(&state, &ev, i);
147
+            }
148
+        }
149
+
150
+        // Reset the fd_set for next iteration
151
+        nfds = build_fd_set(&fds, state.fd_timer, num, evdev);
152
+    }
153
+
154
+    // Cleanup
155
+    libevdev_uinput_destroy(state.uinput);
156
+    for (int i = 0; i < num; i++) {
157
+        free_evdev(evdev[i]);
158
+    }
159
+}

+ 6
- 0
input.h View File

@@ -0,0 +1,6 @@
1
+#ifndef INPUT_H
2
+#define INPUT_H
3
+#include <libevdev/libevdev.h>
4
+
5
+void process_evdev_input(int num, struct libevdev **devices);
6
+#endif

+ 87
- 0
rce.c View File

@@ -0,0 +1,87 @@
1
+#include <libevdev/libevdev.h>
2
+#include <stdio.h>
3
+#include <string.h>
4
+#include <dirent.h>
5
+#include <fcntl.h>
6
+#include <sys/stat.h>
7
+#include <sys/types.h>
8
+#include <unistd.h>
9
+#include "rce.h"
10
+#include "input.h"
11
+
12
+#define DIR_DEV_INPUT "/dev/input"
13
+#define EVDEV_PREFIX "event"
14
+
15
+int find_evdev(struct libevdev **devices) {
16
+    DIR *dev_input_fd;
17
+    if ((dev_input_fd = opendir(DIR_DEV_INPUT)) == NULL) {
18
+        fprintf(stderr, "Unable to open %s", DIR_DEV_INPUT);
19
+        return -1;
20
+    }
21
+
22
+    char dev_path[32];
23
+    int dev_fd = -1;
24
+    int rc = -1;
25
+    struct libevdev *evdev = NULL;
26
+    struct dirent *dev_input_entry = NULL;
27
+    int device_num = 0;
28
+    // Loop over /dev/input to find touchscreen
29
+    // There can be multiple touchscreen devices.
30
+    // e.g. a touch screen, two stylus for that screen (like on Surface)
31
+    // TODO: Support user-defined blacklist (or whitelist)
32
+    while ((dev_input_entry = readdir(dev_input_fd)) != NULL) {
33
+        if (strncmp(EVDEV_PREFIX,
34
+                dev_input_entry->d_name, strlen(EVDEV_PREFIX)) != 0) {
35
+            // Not a /dev/input/event*
36
+            continue;
37
+        }
38
+        sprintf(dev_path, "%s/%.20s", DIR_DEV_INPUT, dev_input_entry->d_name);
39
+        dev_fd = open(dev_path, O_RDONLY | O_NONBLOCK);
40
+        if (dev_fd < 0) {
41
+            fprintf(stderr, "Open %s failed: %s\n", dev_path, strerror(-dev_fd));
42
+            continue;
43
+        }
44
+        rc = libevdev_new_from_fd(dev_fd, &evdev);
45
+        if (rc < 0) {
46
+            fprintf(stderr, "Open %s failed: %s\n", dev_path, strerror(-rc));
47
+            continue;
48
+        }
49
+
50
+        // A touchscreen is absolute input, with TOUCH events
51
+        if (libevdev_has_event_type(evdev, EV_ABS)
52
+                && !libevdev_has_event_type(evdev, EV_REL)
53
+                && libevdev_has_event_code(evdev, EV_KEY, BTN_TOUCH)
54
+                && !libevdev_has_event_code(evdev, EV_KEY, BTN_RIGHT)) {
55
+            const char *name = libevdev_get_name(evdev);
56
+            printf("Found touch screen at %s: %s\n", dev_path, name);
57
+            devices[device_num] = evdev;
58
+            device_num++;
59
+            if (device_num == MAX_TOUCHSCREEN_NUM) {
60
+                printf("More than %d touchscreens. Only keeping the first %d.",
61
+                    MAX_TOUCHSCREEN_NUM, MAX_TOUCHSCREEN_NUM);
62
+                break;
63
+            }
64
+        } else {
65
+            libevdev_free(evdev);
66
+            close(dev_fd);
67
+        }
68
+    }
69
+
70
+    closedir(dev_input_fd);
71
+    return device_num;
72
+}
73
+
74
+int main() {
75
+    struct libevdev *devices[MAX_TOUCHSCREEN_NUM];
76
+    int device_num;
77
+    if ((device_num = find_evdev(devices)) < 0) {
78
+        return 1;
79
+    }
80
+    if (device_num == 0) {
81
+        fprintf(stderr, "No touchscreen is found\n");
82
+        return 1;
83
+    }
84
+
85
+    process_evdev_input(device_num, devices);
86
+    return 0;
87
+}

+ 6
- 0
rce.h View File

@@ -0,0 +1,6 @@
1
+#ifndef RCE_H
2
+#define RCE_H
3
+
4
+#define MAX_TOUCHSCREEN_NUM 32 // Let's assume no one has 32 touchscreens
5
+
6
+#endif

+ 26
- 0
uinput.c View File

@@ -0,0 +1,26 @@
1
+#include "uinput.h"
2
+#include <stddef.h>
3
+
4
+struct libevdev_uinput *uinput_initialize() {
5
+    // Create a evdev first to describe the features
6
+    struct libevdev *evdev = libevdev_new();
7
+    libevdev_set_name(evdev, "Simulated Right Button");
8
+    libevdev_enable_event_type(evdev, EV_KEY);
9
+    libevdev_enable_event_code(evdev, EV_KEY, BTN_RIGHT, NULL);
10
+    // Initialize uinput device from the evdev descriptor
11
+    struct libevdev_uinput *uinput = NULL;
12
+    if (libevdev_uinput_create_from_device(evdev,
13
+            LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput) != 0) {
14
+        uinput = NULL;
15
+    }
16
+    // We don't need the fake evdev anymore.
17
+    libevdev_free(evdev);
18
+    return uinput;
19
+}
20
+
21
+void uinput_send_right_click(struct libevdev_uinput *uinput) {
22
+    libevdev_uinput_write_event(uinput, EV_KEY, BTN_RIGHT, 1);
23
+    libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
24
+    libevdev_uinput_write_event(uinput, EV_KEY, BTN_RIGHT, 0);
25
+    libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
26
+}

+ 7
- 0
uinput.h View File

@@ -0,0 +1,7 @@
1
+#ifndef UINPUT_H
2
+#define UINPUT_H
3
+#include <libevdev/libevdev-uinput.h>
4
+
5
+struct libevdev_uinput *uinput_initialize();
6
+void uinput_send_right_click(struct libevdev_uinput *uinput);
7
+#endif

Loading…
Cancel
Save