From bdff7d84ef1f18f8af9d9e6fa6150a0b3412517b Mon Sep 17 00:00:00 2001
From: Martin Piatka <piatka@cesnet.cz>
Date: Thu, 15 Sep 2022 16:18:11 +0200
Subject: [PATCH] Add volume-knob helper

---
 volume-knob-powermate-1.0/Makefile        |  13 +++
 volume-knob-powermate-1.0/README.md       |   9 ++
 volume-knob-powermate-1.0/findpowermate.c |  57 +++++++++++
 volume-knob-powermate-1.0/findpowermate.h |   7 ++
 volume-knob-powermate-1.0/pulseled.c      | 114 ++++++++++++++++++++++
 volume-knob-powermate-1.0/rotomatic.c     | 100 +++++++++++++++++++
 6 files changed, 300 insertions(+)
 create mode 100644 volume-knob-powermate-1.0/Makefile
 create mode 100644 volume-knob-powermate-1.0/README.md
 create mode 100644 volume-knob-powermate-1.0/findpowermate.c
 create mode 100644 volume-knob-powermate-1.0/findpowermate.h
 create mode 100644 volume-knob-powermate-1.0/pulseled.c
 create mode 100644 volume-knob-powermate-1.0/rotomatic.c

diff --git a/volume-knob-powermate-1.0/Makefile b/volume-knob-powermate-1.0/Makefile
new file mode 100644
index 0000000..bf4e74a
--- /dev/null
+++ b/volume-knob-powermate-1.0/Makefile
@@ -0,0 +1,13 @@
+all:	rotomatic pulseled
+
+rotomatic:	rotomatic.o findpowermate.o
+	$(CC) findpowermate.o rotomatic.o -o rotomatic -lxdo
+
+pulseled:	pulseled.o findpowermate.o
+	$(CC) findpowermate.o pulseled.o -o pulseled
+
+clean:
+	rm -f *.o *~ rotomatic pulseled
+
+%.o:    %.c
+	$(CC) -O2 -Wall -c $< -o $@
diff --git a/volume-knob-powermate-1.0/README.md b/volume-knob-powermate-1.0/README.md
new file mode 100644
index 0000000..7f43776
--- /dev/null
+++ b/volume-knob-powermate-1.0/README.md
@@ -0,0 +1,9 @@
+Tools to support the Griffin powermate volume knob
+
+The source was taken from https://sowerbutts.com/powermate/ and modified to
+send mouse scroll wheel events.
+
+Run rotomatic with the input device path as an argument.
+```
+./rotomatic /dev/input/event<num>
+```
diff --git a/volume-knob-powermate-1.0/findpowermate.c b/volume-knob-powermate-1.0/findpowermate.c
new file mode 100644
index 0000000..9e0e251
--- /dev/null
+++ b/volume-knob-powermate-1.0/findpowermate.c
@@ -0,0 +1,57 @@
+#include <linux/input.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "findpowermate.h"
+
+#define NUM_VALID_PREFIXES 2
+
+static const char *valid_prefix[NUM_VALID_PREFIXES] = {
+  "Griffin PowerMate",
+  "Griffin SoundKnob"
+};
+
+#define NUM_EVENT_DEVICES 16
+
+int open_powermate(const char *dev, int mode)
+{
+  int fd = open(dev, mode);
+  int i;
+  char name[255];
+
+  if(fd < 0){
+    fprintf(stderr, "Unable to open \"%s\": %s\n", dev, strerror(errno));
+    return -1;
+  }
+
+  if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0){
+    fprintf(stderr, "\"%s\": EVIOCGNAME failed: %s\n", dev, strerror(errno));
+    close(fd);
+    return -1;
+  }
+
+  // it's the correct device if the prefix matches what we expect it to be:
+  for(i=0; i<NUM_VALID_PREFIXES; i++)
+    if(!strncasecmp(name, valid_prefix[i], strlen(valid_prefix[i])))
+      return fd;
+
+  close(fd);
+  return -1;
+}
+
+int find_powermate(int mode)
+{
+  char devname[256];
+  int i, r;
+
+  for(i=0; i<NUM_EVENT_DEVICES; i++){
+    sprintf(devname, "/dev/input/event%d", i);
+    r = open_powermate(devname, mode);
+    if(r >= 0)
+      return r;
+  }
+
+  return -1;
+}
diff --git a/volume-knob-powermate-1.0/findpowermate.h b/volume-knob-powermate-1.0/findpowermate.h
new file mode 100644
index 0000000..2cf6f9b
--- /dev/null
+++ b/volume-knob-powermate-1.0/findpowermate.h
@@ -0,0 +1,7 @@
+#ifndef __FIND_POWERMATE__DOT_H__SENTRY__
+#define __FIND_POWERMATE__DOT_H__SENTRY__
+
+int open_powermate(const char *dev, int mode);
+int find_powermate(int mode);
+
+#endif
diff --git a/volume-knob-powermate-1.0/pulseled.c b/volume-knob-powermate-1.0/pulseled.c
new file mode 100644
index 0000000..b3b7477
--- /dev/null
+++ b/volume-knob-powermate-1.0/pulseled.c
@@ -0,0 +1,114 @@
+#include <linux/input.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include "findpowermate.h"
+
+#ifndef MSC_PULSELED
+/* this may not have made its way into the kernel headers yet ... */
+#define MSC_PULSELED 0x01
+#endif
+
+void powermate_pulse_led(int fd, int static_brightness, int pulse_speed, int pulse_table, int pulse_asleep, int pulse_awake)
+{
+  struct input_event ev;
+  memset(&ev, 0, sizeof(struct input_event));
+  
+  static_brightness &= 0xFF;
+
+  if(pulse_speed < 0)
+    pulse_speed = 0;
+  if(pulse_speed > 510)
+    pulse_speed = 510;
+  if(pulse_table < 0)
+    pulse_table = 0;
+  if(pulse_table > 2)
+    pulse_table = 2;
+  pulse_asleep = !!pulse_asleep;
+  pulse_awake = !!pulse_awake;
+
+  ev.type = EV_MSC;
+  ev.code = MSC_PULSELED;
+  ev.value = static_brightness | (pulse_speed << 8) | (pulse_table << 17) | (pulse_asleep << 19) | (pulse_awake << 20);
+
+  if(write(fd, &ev, sizeof(struct input_event)) != sizeof(struct input_event))
+    fprintf(stderr, "write(): %s\n", strerror(errno));  
+}
+
+void usage(void)
+{
+  printf("Usage:\n");
+  printf("pulseled [options]\n");
+  printf("\t--device [filename]\tEvent device to use\n");
+  printf("\t--level [level]    \tLED intensity with pulsing disabled (0-255)\n");
+  printf("\t--speed [rate]     \tPulsing speed (0-510); around 255 works best\n");
+  printf("\t--style [style]    \tPulsing style (0, 1 or 2)\n");
+  printf("\t--pulseawake       \tEnable pulsing whilst host is awake\n");
+  printf("\t--nopulseawake     \tDisable pulsing whilst host is awake\n");
+  printf("\t--pulseasleep      \tEnable pulsing whilst host is asleep\n");
+  printf("\t--nopulseasleep    \tDisable pulsing whilst host is asleep\n");
+}
+
+#define TAKE_NEXT_ARG {if(++args_processed >= argc){fprintf(stderr, "Missing argument to %s!\n", argv[args_processed-1]);return 1;}}
+
+int main(int argc, char *argv[])
+{
+  int powermate = -1;
+  int args_processed = 1;
+  int static_brightness = 0x80;
+  int pulse_speed = 255;
+  int pulse_table = 0;
+  int pulse_asleep = 1;
+  int pulse_awake = 0;
+
+  const char *device = 0;
+
+  while(args_processed < argc){
+    if(!strcasecmp(argv[args_processed], "--device")){
+      TAKE_NEXT_ARG;
+      device = argv[args_processed];
+    }else if(!strcasecmp(argv[args_processed], "--level")){
+      TAKE_NEXT_ARG;
+      static_brightness = atoi(argv[args_processed]);
+    }else if(!strcasecmp(argv[args_processed], "--speed")){
+      TAKE_NEXT_ARG;
+      pulse_speed = atoi(argv[args_processed]);
+    }else if(!strcasecmp(argv[args_processed], "--style")){
+      TAKE_NEXT_ARG;
+      pulse_table = atoi(argv[args_processed]);
+    }else if(!strcasecmp(argv[args_processed], "--pulseawake")){
+      pulse_awake = 1;
+    }else if(!strcasecmp(argv[args_processed], "--pulseasleep")){
+      pulse_asleep = 1;
+    }else if(!strcasecmp(argv[args_processed], "--nopulseawake")){
+      pulse_awake = 0;
+    }else if(!strcasecmp(argv[args_processed], "--nopulseasleep")){
+      pulse_asleep = 0;
+    }else{
+      usage();
+      return 1;
+    }
+
+    args_processed++;
+  }
+
+  if(device)
+    powermate = open_powermate(device, O_WRONLY);
+  else
+    powermate = find_powermate(O_WRONLY);
+
+  if(powermate < 0){
+    fprintf(stderr, "Unable to locate powermate\n");
+    fprintf(stderr, "Try: %s [device]\n", argv[0]);
+    return 1;
+  }
+
+  powermate_pulse_led(powermate, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake);
+  
+  close(powermate);
+
+  return 0;
+}
diff --git a/volume-knob-powermate-1.0/rotomatic.c b/volume-knob-powermate-1.0/rotomatic.c
new file mode 100644
index 0000000..dbf5b56
--- /dev/null
+++ b/volume-knob-powermate-1.0/rotomatic.c
@@ -0,0 +1,100 @@
+#include <linux/input.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <xdo.h>
+#include "findpowermate.h"
+
+int abs_offset = 0;
+
+xdo_t *x_ctx = NULL;
+
+void process_event(struct input_event *ev)
+{
+#ifdef VERBOSE
+  fprintf(stderr, "type=0x%04x, code=0x%04x, value=%d\n",
+	  ev->type, ev->code, (int)ev->value);
+#endif
+
+  switch(ev->type){
+  case EV_MSC:
+    printf("The LED pulse settings were changed; code=0x%04x, value=0x%08x\n", ev->code, ev->value);
+    break;
+  case EV_REL:
+    if(ev->code != REL_DIAL)
+      fprintf(stderr, "Warning: unexpected rotation event; ev->code = 0x%04x\n", ev->code);
+    else{
+      abs_offset += (int)ev->value;
+      printf("Button was rotated %d units; Offset from start is now %d units\n", (int)ev->value, abs_offset);
+	  for(int i = (int) ev->value; i > 0; i--){
+		xdo_mouse_down(x_ctx, CURRENTWINDOW, 5);
+		xdo_mouse_up(x_ctx, CURRENTWINDOW, 5);
+	  }
+	  for(int i = (int) ev->value; i < 0; i++){
+		xdo_mouse_down(x_ctx, CURRENTWINDOW, 4);
+		xdo_mouse_up(x_ctx, CURRENTWINDOW, 4);
+	  }
+    }
+    break;
+  case EV_KEY:
+    if(ev->code != BTN_0)
+      fprintf(stderr, "Warning: unexpected key event; ev->code = 0x%04x\n", ev->code);
+    else
+      printf("Button was %s\n", ev->value? "pressed":"released");
+    break;
+  default:
+    fprintf(stderr, "Warning: unexpected event type; ev->type = 0x%04x\n", ev->type);
+  }
+
+  fflush(stdout);
+}
+
+#define BUFFER_SIZE 32
+void watch_powermate(int fd)
+{
+  struct input_event ibuffer[BUFFER_SIZE];
+  int r, events, i;
+
+  while(1){
+    r = read(fd, ibuffer, sizeof(struct input_event) * BUFFER_SIZE);
+    if( r > 0 ){
+      events = r / sizeof(struct input_event);
+      for(i=0; i<events; i++)
+	process_event(&ibuffer[i]);
+    }else{
+      fprintf(stderr, "read() failed: %s\n", strerror(errno));
+      return;
+    }
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  int powermate = -1;
+
+  if(argc == 1)
+    powermate = find_powermate(O_RDONLY);
+  else
+    powermate = open_powermate(argv[1], O_RDONLY);
+
+  if(powermate < 0){
+    fprintf(stderr, "Unable to locate powermate\n");
+    fprintf(stderr, "Try: %s [device]\n", argv[0]);
+    return 1;
+  }
+
+  x_ctx = xdo_new(NULL);
+  if(!x_ctx){
+	  printf("Unable to init xdo\n");
+	  close(powermate);
+	  return -1;
+  }
+
+  watch_powermate(powermate);
+
+  close(powermate);
+
+  return 0;
+}
-- 
GitLab