| 1 | |
| 2 | |
| 3 | *
|
| 4 | * This program will morph the Microsoft Surface Dial into a mouse,
|
| 5 | * and depending on the chosen resolution enable or not the haptic feedback:
|
| 6 | * - a resolution (-r) of 3600 will report 3600 "ticks" in one full rotation
|
| 7 | * without haptic feedback
|
| 8 | * - any other resolution will report N "ticks" in a full rotation with haptic
|
| 9 | * feedback
|
| 10 | *
|
| 11 | * A good default for low resolution haptic scrolling is 72 (1 "tick" every 5
|
| 12 | * degrees), and set to 3600 for smooth scrolling.
|
| 13 |
|
| 14 |
|
| 15 | #include <assert.h>
|
| 16 | #include <errno.h>
|
| 17 | #include <fcntl.h>
|
| 18 | #include <libgen.h>
|
| 19 | #include <signal.h>
|
| 20 | #include <stdbool.h>
|
| 21 | #include <stdio.h>
|
| 22 | #include <stdlib.h>
|
| 23 | #include <string.h>
|
| 24 | #include <sys/resource.h>
|
| 25 | #include <unistd.h>
|
| 26 |
|
| 27 | #include <linux/bpf.h>
|
| 28 | #include <linux/errno.h>
|
| 29 |
|
| 30 | #include <bpf/bpf.h>
|
| 31 | #include <bpf/libbpf.h>
|
| 32 |
|
| 33 | #include "hid_surface_dial.skel.h"
|
| 34 |
|
| 35 | static bool running = true;
|
| 36 |
|
| 37 | struct haptic_syscall_args {
|
| 38 | unsigned int hid;
|
| 39 | int retval;
|
| 40 | };
|
| 41 |
|
| 42 | static void int_exit(int sig)
|
| 43 | {
|
| 44 | running = false;
|
| 45 | exit(0);
|
| 46 | }
|
| 47 |
|
| 48 | static void usage(const char *prog)
|
| 49 | {
|
| 50 | fprintf(stderr,
|
| 51 | "%s: %s [OPTIONS] /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n"
|
| 52 | " OPTIONS:\n"
|
| 53 | " -r N\t set the given resolution to the device (number of ticks per 360°)\n\n",
|
| 54 | __func__, prog);
|
| 55 | fprintf(stderr,
|
| 56 | "This program will morph the Microsoft Surface Dial into a mouse,\n"
|
| 57 | "and depending on the chosen resolution enable or not the haptic feedback:\n"
|
| 58 | "- a resolution (-r) of 3600 will report 3600 'ticks' in one full rotation\n"
|
| 59 | " without haptic feedback\n"
|
| 60 | "- any other resolution will report N 'ticks' in a full rotation with haptic\n"
|
| 61 | " feedback\n"
|
| 62 | "\n"
|
| 63 | "A good default for low resolution haptic scrolling is 72 (1 'tick' every 5\n"
|
| 64 | "degrees), and set to 3600 for smooth scrolling.\n");
|
| 65 | }
|
| 66 |
|
| 67 | static int get_hid_id(const char *path)
|
| 68 | {
|
| 69 | const char *str_id, *dir;
|
| 70 | char uevent[1024];
|
| 71 | int fd;
|
| 72 |
|
| 73 | memset(uevent, 0, sizeof(uevent));
|
| 74 | snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path);
|
| 75 |
|
| 76 | fd = open(uevent, O_RDONLY | O_NONBLOCK);
|
| 77 | if (fd < 0)
|
| 78 | return -ENOENT;
|
| 79 |
|
| 80 | close(fd);
|
| 81 |
|
| 82 | dir = basename((char *)path);
|
| 83 |
|
| 84 | str_id = dir + sizeof("0003:0001:0A37.");
|
| 85 | return (int)strtol(str_id, NULL, 16);
|
| 86 | }
|
| 87 |
|
| 88 | static int set_haptic(struct hid_surface_dial *skel, int hid_id)
|
| 89 | {
|
| 90 | struct haptic_syscall_args args = {
|
| 91 | .hid = hid_id,
|
| 92 | .retval = -1,
|
| 93 | };
|
| 94 | int haptic_fd, err;
|
| 95 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
|
| 96 | .ctx_in = &args,
|
| 97 | .ctx_size_in = sizeof(args),
|
| 98 | );
|
| 99 |
|
| 100 | haptic_fd = bpf_program__fd(skel->progs.set_haptic);
|
| 101 | if (haptic_fd < 0) {
|
| 102 | fprintf(stderr, "can't locate haptic prog: %m\n");
|
| 103 | return 1;
|
| 104 | }
|
| 105 |
|
| 106 | err = bpf_prog_test_run_opts(haptic_fd, &tattr);
|
| 107 | if (err) {
|
| 108 | fprintf(stderr, "can't set haptic configuration to hid device %d: %m (err: %d)\n",
|
| 109 | hid_id, err);
|
| 110 | return 1;
|
| 111 | }
|
| 112 | return 0;
|
| 113 | }
|
| 114 |
|
| 115 | int main(int argc, char **argv)
|
| 116 | {
|
| 117 | struct hid_surface_dial *skel;
|
| 118 | const char *optstr = "r:";
|
| 119 | struct bpf_link *link;
|
| 120 | const char *sysfs_path;
|
| 121 | int err, opt, hid_id, resolution = 72;
|
| 122 |
|
| 123 | while ((opt = getopt(argc, argv, optstr)) != -1) {
|
| 124 | switch (opt) {
|
| 125 | case 'r':
|
| 126 | {
|
| 127 | char *endp = NULL;
|
| 128 | long l = -1;
|
| 129 |
|
| 130 | if (optarg) {
|
| 131 | l = strtol(optarg, &endp, 10);
|
| 132 | if (endp && *endp)
|
| 133 | l = -1;
|
| 134 | }
|
| 135 |
|
| 136 | if (l < 0) {
|
| 137 | fprintf(stderr,
|
| 138 | "invalid r option %s - expecting a number\n",
|
| 139 | optarg ? optarg : "");
|
| 140 | exit(EXIT_FAILURE);
|
| 141 | };
|
| 142 |
|
| 143 | resolution = (int) l;
|
| 144 | break;
|
| 145 | }
|
| 146 | default:
|
| 147 | usage(basename(argv[0]));
|
| 148 | return 1;
|
| 149 | }
|
| 150 | }
|
| 151 |
|
| 152 | if (optind == argc) {
|
| 153 | usage(basename(argv[0]));
|
| 154 | return 1;
|
| 155 | }
|
| 156 |
|
| 157 | sysfs_path = argv[optind];
|
| 158 | if (!sysfs_path) {
|
| 159 | perror("sysfs");
|
| 160 | return 1;
|
| 161 | }
|
| 162 |
|
| 163 | skel = hid_surface_dial__open();
|
| 164 | if (!skel) {
|
| 165 | fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__);
|
| 166 | return -1;
|
| 167 | }
|
| 168 |
|
| 169 | hid_id = get_hid_id(sysfs_path);
|
| 170 | if (hid_id < 0) {
|
| 171 | fprintf(stderr, "can not open HID device: %m\n");
|
| 172 | return 1;
|
| 173 | }
|
| 174 |
|
| 175 | skel->struct_ops.surface_dial->hid_id = hid_id;
|
| 176 |
|
| 177 | err = hid_surface_dial__load(skel);
|
| 178 | if (err < 0) {
|
| 179 | fprintf(stderr, "can not load HID-BPF program: %m\n");
|
| 180 | return 1;
|
| 181 | }
|
| 182 |
|
| 183 | skel->data->resolution = resolution;
|
| 184 | skel->data->physical = (int)(resolution / 72);
|
| 185 |
|
| 186 | link = bpf_map__attach_struct_ops(skel->maps.surface_dial);
|
| 187 | if (!link) {
|
| 188 | fprintf(stderr, "can not attach HID-BPF program: %m\n");
|
| 189 | return 1;
|
| 190 | }
|
| 191 |
|
| 192 | signal(SIGINT, int_exit);
|
| 193 | signal(SIGTERM, int_exit);
|
| 194 |
|
| 195 | set_haptic(skel, hid_id);
|
| 196 |
|
| 197 | while (running)
|
| 198 | sleep(1);
|
| 199 |
|
| 200 | hid_surface_dial__destroy(skel);
|
| 201 |
|
| 202 | return 0;
|
| 203 | }
|