hid_surface_dial.c 4.4 KB

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