diff --git a/KD100.c b/KD100.c index bd19718..574e003 100644 --- a/KD100.c +++ b/KD100.c @@ -1,7 +1,7 @@ /* - V1.31 + V1.4 https://github.com/mckset/KD100.git - KD 100 Linux driver for X11 desktops + KD100 Linux driver for X11 desktops Other devices can be supported by modifying the code to read data received by the device At the moment, only the KD100 mini keypad is supported by this code officially */ @@ -14,24 +14,34 @@ #include int keycodes[] = {1, 2, 4, 8, 16, 32, 64, 128, 129, 130, 132, 136, 144, 160, 192, 256, 257, 258, 260, 641, 642}; -char file[4096] = "default.cfg"; +char* file = "default.cfg"; +typedef struct event event; +typedef struct wheel wheel; -void GetDevice(int, int); +struct event{ + int type; + char* function; +}; + +struct wheel { + char* right; + char* left; +}; + +void GetDevice(int, int, int); void Handler(char*, int); +char* Substring(char*, int, int); const int vid = 0x256c; const int pid = 0x006d; - -void GetDevice(int debug, int accept){ - int i=0, err=0, l=0, subL=0, wheelFunction=0, c=0; - char data[512]; // Data received from the config file and the USB - int type[19]; // Stores the button type - char events[19][256]; // Stores the button event key/function - char wheelEvents[6][256]; // Stores the wheel keys - int prevType = 0; // prevKey function type - char prevKey[256]; // Stores the previous event to release held keys +void GetDevice(int debug, int accept, int dry){ + int err=0, wheelFunction=0, c=0, b=-1, tButtons=0, wheelType=0, lWheels=0, rWheels=0, tWheels=0; + char* data = malloc(512*sizeof(char)); // Data received from the config file and the USB + event* events = malloc(1*sizeof(*events)); // Stores key events and functions + wheel* wheelEvents = malloc(1*sizeof(wheel)); // Stores wheel functions + event prevEvent; uid_t uid=getuid(); // Used to check if the driver was ran as root system("clear"); @@ -39,13 +49,14 @@ void GetDevice(int debug, int accept){ if (debug > 0){ if (debug > 2) debug=2; - printf("Version 1.31\nDebug level: %d\n", debug); - } + printf("Version 1.4\nDebug level: %d\n", debug); + } // Load config file if (debug == 1){ printf("Loading config...\n"); } + FILE *f; if (strcmp(file, "default.cfg")){ f = fopen(file, "r"); @@ -56,60 +67,90 @@ void GetDevice(int debug, int accept){ }else{ f = fopen(file, "r"); if (f == NULL){ - strcpy(file, getpwuid(getuid())->pw_dir); - strcat(file, "/.config/KD100/default.cfg"); - f = fopen(file, "r"); + char* home = getpwuid(getuid())->pw_dir; + file = "/.config/KD100/default.cfg"; + char temp[strlen(file)+strlen(home)+1]; + for (int i = 0; i < strlen(home); i++) + temp[i] = home[i]; + for (int i = 0; i < strlen(file); i++) + temp[i+strlen(home)] = file[i]; + temp[strlen(temp)] = '\0'; + + f = fopen(temp, "r"); if (f == NULL){ printf("DEFAULT CONFIGS ARE MISSING!\n"); - printf("Please add default.cfg to %s/.config/KD100/ or specify a file to use with -c\n", getpwuid(getuid())->pw_dir); + printf("Please add default.cfg to %s/.config/KD100/ or specify a file to use with -c\n", home); return; } } } while (fscanf(f, "%[^\n] ", data) == 1){ - if (l > 19 && l <= 76){ // Button functions - if (subL == 0){ - type[i] = atoi(&data[5]); - subL = 1; - }else if (subL == 1){ - char func[256]; - if (strlen(data) < 256){ - for (int d = 10; d < sizeof(data); d++){ - func[d-10] = data[d]; + for (int i = 0; i < strlen(data) && strlen(data)-6 > 0; i++){ + if (strcmp(Substring(data, i, 5), "type:") == 0 && b != -1){ + events[b].type = atoi(Substring(data, i+6, strlen(data)-(i+6))); + break; + }else if (strcmp(Substring(data, i, 6), "Button") == 0){ + b = atoi(Substring(data, i+7, strlen(data)-(i+7))); + if (b >= tButtons){ + event* temp = realloc(events, (b+1)*sizeof(*events)); + events = temp; + tButtons = b+1; + } + break; + }else if (strcmp(Substring(data, i, 9), "function:") == 0 && b != -1){ + if (!wheelType) + events[b].function = Substring(data, i+10, strlen(data)-(i+10)); + else if (wheelType == 1){ + if (rWheels != 0){ + wheel* temp = realloc(wheelEvents, (rWheels+1)*sizeof(*wheelEvents)); + wheelEvents = temp; + wheelEvents[rWheels].right = Substring(data, i+10, strlen(data)-(i+10)); + wheelEvents[rWheels].left = "NULL"; + }else{ + wheelEvents[0].right = Substring(data, i+10, strlen(data)-(i+10)); + wheelEvents[0].left = "NULL"; } + rWheels++; }else{ - strcpy(func, "NULL"); - printf("Line: %d - Function length exceeded 256 characters. Ignoring %s\n", l, data); - } - strcpy(events[i], func); - subL = -1; - i++; - }else{ - subL+=1; - } - }else if (l > 76 && l < 85){ // Wheel functions - if (subL < 3){ - char func[256]; - for (int d = 12; d < sizeof(data); d++){ - func[d-12] = data[d]; + if (lWheels < rWheels) + wheelEvents[lWheels].left = Substring(data, i+10, strlen(data)-(i+10)); + else{ + wheel* temp = realloc(wheelEvents, (lWheels+1)*sizeof(*wheelEvents)); + wheelEvents = temp; + wheelEvents[lWheels].left = Substring(data, i+10, strlen(data)-(i+10)); + wheelEvents[lWheels].right = "NULL"; + } + lWheels++; } - strcpy(wheelEvents[wheelFunction], func); - wheelFunction++; - subL++; - }else{ - subL=0; + + + break; + }else if (strcmp(Substring(data, i, 6), "Wheel ") == 0){ + wheelType++; } } - l++; } wheelFunction=0; + if (rWheels > lWheels) + tWheels = rWheels; + else + tWheels = lWheels; - i = 0; + if (debug > 0){ + for (int i = 0; i < tButtons; i++) + printf("Button: %d | Type: %d | Function: %s\n", i, events[i].type, events[i].function); + printf("\n"); + for (int i = 0; i < tWheels; i++) + printf("Wheel Right: %s | Wheel Left: %s\n", wheelEvents[i].right, wheelEvents[i].left); + printf("\n"); + } + free(data); + int i = 0; char indi[] = "|/-\\"; while (err == 0 || err == LIBUSB_ERROR_NO_DEVICE){ libusb_device **devs; // List of USB devices libusb_device *dev; // Selected USB device - struct libusb_config_descriptor *desc; // USB descrition (For claiming interfaces) + struct libusb_config_descriptor *desc; // USB description (For claiming interfaces) libusb_device_handle *handle = NULL; // USB handle err = libusb_get_device_list(NULL, &devs); @@ -132,21 +173,21 @@ void GetDevice(int debug, int accept){ } }else if (devDesc.idVendor == vid && devDesc.idProduct == pid){ if (accept == 1){ - err=libusb_open(dev, &handle); - if (err < 0){ - printf("\nUnable to open device. Error: %d\n", err); - handle=NULL; - if (err == LIBUSB_ERROR_ACCESS){ - printf("Error: Permission denied\n"); - return; + if (uid != 0){ + err=libusb_open(dev, &handle); + if (err < 0){ + printf("\nUnable to open device. Error: %d\n", err); + handle=NULL; + if (err == LIBUSB_ERROR_ACCESS){ + printf("Error: Permission denied\n"); + return; + } } - } - if (debug > 0){ - printf("\nUsing: %04x:%04x (Bus: %03d Device: %03d)\n", vid, pid, libusb_get_bus_number(dev), libusb_get_device_address(dev)); - } - break; - }else{ - if (uid == 0){ // If the driver is ran as root, it can safely execute the following + if (debug > 0){ + printf("\nUsing: %04x:%04x (Bus: %03d Device: %03d)\n", vid, pid, libusb_get_bus_number(dev), libusb_get_device_address(dev)); + } + break; + }else{ // If the driver is ran as root, it can safely execute the following err = libusb_open(dev, &handle); if (err < 0){ printf("\nUnable to open device. Error: %d\n", err); @@ -162,15 +203,15 @@ void GetDevice(int debug, int accept){ libusb_close(handle); handle = NULL; } - }else{ - savedDevs[i] = dev; - i++; } + }else{ + savedDevs[i] = dev; + i++; } } } - if (i > 0){ + if (accept == 0){ int in=-1; while(in == -1){ char buf[64]; @@ -241,6 +282,8 @@ void GetDevice(int debug, int accept){ // Listen for events printf("Driver is running!\n"); + prevEvent.function = ""; + prevEvent.type = 0; while (err >=0){ unsigned char data[40]; // Stores device input int keycode = 0; // Keycode read from the device @@ -275,69 +318,70 @@ void GetDevice(int debug, int accept){ keycode = data[6] + 256; if (data[1] == 241) keycode+=512; + if (dry) + keycode = 0; // Compare keycodes to data and trigger events if (debug == 1 && keycode != 0){ printf("Keycode: %d\n", keycode); } - if (keycode == 0 && prevType != 0){ // Reset key held - Handler(prevKey, prevType); - strcpy(prevKey, ""); - prevType=0; + if (keycode == 0 && prevEvent.type != 0){ // Reset key held + Handler(prevEvent.function, prevEvent.type); + prevEvent.function = ""; + prevEvent.type = 0; } - if (keycode == 641){ // Wheel Clockwise - Handler(wheelEvents[wheelFunction], -1); - }else if (keycode == 642){ - Handler(wheelEvents[wheelFunction + 3], -1); + if (keycode == 641){ // Wheel clockwise + Handler(wheelEvents[wheelFunction].right, -1); + }else if (keycode == 642){ // Counter clockwise + Handler(wheelEvents[wheelFunction].left, -1); }else{ for (int k = 0; k < 19; k++){ if (keycodes[k] == keycode){ - if (strcmp(events[k], "NULL") == 0){ - if (prevType != 0){ - Handler(prevKey, prevType); - prevType = 0; - strcpy(prevKey, ""); - } - break; - } - if (type[k] == 0){ - if (strcmp(events[k], prevKey)){ - if (prevType != 0){ - Handler(prevKey, prevType); + if (events[k].function){ + if (strcmp(events[k].function, "NULL") == 0){ + if (prevEvent.type != 0){ + Handler(prevEvent.function, prevEvent.type); + prevEvent.type = 0; + prevEvent.function = ""; } - strcpy(prevKey, events[k]); - prevType=1; + break; } - Handler(events[k], 0); - }else if (strcmp(events[k], "swap") == 0){ - if (wheelFunction != 2){ - wheelFunction++; - if (strcmp(wheelEvents[wheelFunction], "NULL") == 0){ - wheelFunction = 0; + if (events[k].type == 0){ + if (strcmp(events[k].function, prevEvent.function)){ + if (prevEvent.type != 0){ + Handler(prevEvent.function, prevEvent.type); + } + prevEvent.function = events[k].function; + prevEvent.type=1; } + Handler(events[k].function, 0); + }else if (strcmp(events[k].function, "swap") == 0){ + if (wheelFunction != tWheels-1){ + wheelFunction++; + }else + wheelFunction=0; if (debug == 1){ - printf("Function: %s\n", wheelEvents[wheelFunction]); + printf("Function: %s | %s\n", wheelEvents[wheelFunction].left, wheelEvents[wheelFunction].right); } - }else - wheelFunction=0; - }else if (strcmp(events[k], "mouse1") == 0 || strcmp(events[k], "mouse2") == 0 || strcmp(events[k], "mouse3") == 0 || strcmp(events[k], "mouse4") == 0 || strcmp(events[k], "mouse5") == 0){ - if (strcmp(events[k], prevKey)){ - if (prevType != 0){ - Handler(prevKey, prevType); + }else if (strcmp(events[k].function, "mouse1") == 0 || strcmp(events[k].function, "mouse2") == 0 || strcmp(events[k].function, "mouse3") == 0 || strcmp(events[k].function, "mouse4") == 0 || strcmp(events[k].function, "mouse5") == 0){ + if (strcmp(events[k].function, prevEvent.function)){ + if (prevEvent.type != 0){ + Handler(prevEvent.function, prevEvent.type); + } + prevEvent.function = events[k].function; + prevEvent.type=3; } - strcpy(prevKey, events[k]); - prevType=3; + Handler(events[k].function, 2); + }else{ + system(events[k].function); } - Handler(events[k], 2); - }else{ - system(events[k]); + break; } - break; } } } - if(debug == 2){ + if(debug == 2 || dry){ printf("DATA: [%d", data[0]); for (int i = 1; i < sizeof(data); i++){ printf(", %d", data[i]); @@ -347,8 +391,7 @@ void GetDevice(int debug, int accept){ } - // Cleanup - + // Cleanup for (int x = 0; x strlen(in) || end + start > strlen(in)){ + return in; } - system(cmd); + char* out = malloc(end+1); + for (int i = 0; i < end; i++) + out[i] = in[i+start]; + out[end] = '\0'; + return out; } + int main(int args, char *in[]) { - int d=0, a=0, err; + int debug=0, accept=0, dry=0, err; err = system("xdotool sleep 0.01"); if (err != 0){ printf("Exitting...\n"); - return -1; + return -9; } for (int arg = 1; arg < args; arg++){ @@ -402,33 +473,41 @@ int main(int args, char *in[]) printf("\t-a\t\tAssume the first device that matches %04x:%04x is the Keydial\n", vid, pid); printf("\t-c [path]\tSpecifies a config file to use\n"); printf("\t-d [-d]\t\tEnable debug outputs (use twice to view data sent by the device)\n"); + printf("\t-dry \t\tDisplay data sent by the device without sending events\n"); printf("\t-h\t\tDisplays this message\n\n"); return 0; } if (strcmp(in[arg],"-d") == 0){ - d++; + debug++; + } + if (strcmp(in[arg],"-dry") == 0){ + dry=1; } if (strcmp(in[arg],"-a") == 0){ - a=1; + accept=1; } if (strcmp(in[arg], "-c") == 0){ - if (strlen(in[arg+1]) > 0){ - strcpy(file, in[arg+1]); + if (in[arg+1]){ + file = in[arg+1]; + printf("%s\n", file); arg++; + }else{ + printf("No config file specified. Exiting...\n"); + return -8; } } } libusb_context **ctx; - err = libusb_init(ctx); + err = libusb_init(&ctx); if (err < 0){ printf("Error: %d\n", err); return err; } - // Uncomment for more detailed debugging (might crash when using older version of libusb) - //libusb_set_option(*ctx, LIBUSB_OPTION_LOG_LEVEL, 0); - GetDevice(d, a); - libusb_exit(*ctx); + // Uncomment to enable libusb debug messages + // libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, 1); + GetDevice(debug, accept, dry); + libusb_exit(ctx); return 0; } diff --git a/README.md b/README.md index d6e25ee..34eeb37 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # Huion KD100 Linux Driver A simple driver for the Huion KD100 mini Keydial written in C to give the device some usability while waiting for Huion to fix their Linux drivers. Each button can be configured to either act as a key/multiple keys or to execute a program/command +> **_NOTICE:_** When updating from **v1.31** or below, make sure you updated your config file to follow the new format shown in the default config file + Pre-Installation ------------ -Arch Linux: +Arch Linux/Manjaro: ``` sudo pacman -S libusb-1.0 xdotool ``` -Ubuntu/Debian: +Ubuntu/Debian/Pop OS: ``` -sudo apt-get install libusb-1.0 xdotool +sudo apt-get install libusb-1.0-0-dev xdotool ``` > **_NOTE:_** Some distros label libusb as "libusb-1.0-0" and others might require the separate "libusb-1.0-dev" package @@ -22,6 +24,8 @@ cd KD100 make ``` +> Running make as root will install the driver as a command and create a folder in ~/.config to store config files + Usage ----- ``` @@ -33,21 +37,21 @@ sudo ./KD100 [options] **-d** Enable debug output (can be used twice to output the full packet of data recieved from the device) +**-dry** Display data sent from the keydial and ignore events + **-h** Displays a help message Configuring ---------- -Edit or copy **default.cfg** to add your own keys/commands and use the '-c' flag to specify the location of the config file -> **_NOTE:_** New config files must have the same format and line count as the default file +Edit or copy **default.cfg** to add your own keys/commands and use the '-c' flag to specify the location of the config file. New config files do not need to end in ".cfg". Caveats ------- -- This only works on X11 based desktops (because of xdotool) +- This only works on X11 based desktops (because it relies on xdotool) but can be patched for wayland desktops by altering the "handler" function - You do not need to run this with sudo if you set a udev rule for the device. Create/edit a rule file in /etc/udev/rules.d/ and add the following, then save and reboot or reload your udev rules ``` SUBSYSTEM=="usb",ATTRS{idVendor}=="256c",ATTRS{idProduct}=="006d",MODE="0666",GROUP="plugdev" ``` -- If the driver is ran as a user and the '-a' flag is not used, you will need to select the device to use during startup - Technically speaking, this can support other devices, especially if they send the same type of byte information, otherwise the code should be easy enough to edit and add support for other usb devices. If you want to see the information sent by different devices, change the vid and pid in the program and run it with two debug flags Tested Distros @@ -55,7 +59,7 @@ Tested Distros - Arch linux - Manjaro - Ubuntu -- Kali Linux +- Pop OS Known Issues ------------ diff --git a/default.cfg b/default.cfg index 9bbf6c3..f24e0c9 100644 --- a/default.cfg +++ b/default.cfg @@ -1,11 +1,33 @@ // KD100 config file -// The driver supports two key functions +// +// Assumptions: +// - (B)utton defines a new button and the following number specifies which button it is. Without the paranthesis, the previous sentence would define a new button +// - (B)uttons do not need to be listed in order but their type and function must be listed on separate lines afterwards +// - (B)utton definitions can be skipped if you don't want all the buttons to work on the keydial +// - (B)utton, type, and function definitions can contain numbers, symbols, or letters before hand but must have a number or function after the ':' +// - Ex) @#$!123ttype: 1 - is valid +// - type: f02r - will default to type 0 +// - type and function definitions must be lower case but can be in any order +// - The program defaults the type to 0 and the function to null +// - The (W)heel works in two parts. The first definition assigns all functions for turning it clockwise while the second definition assigns counter clockwise functions +// - The wheel functions must be defined at the end of the config file +// - If one wheel side has more functions than the other, it will set the function of the other wheel to NULL and do nothing when turning the wheel that way +// - Three wheel functions are provided but there is no limit to the amount you can add +// - Both button and wheel definitions do not need '//' before them and the wheel definition doesn't need the text after it to work +// - '//' is not required to add comments to the file but anything after a function, type, or button will be included in the program +// - All other text is skipped by the program +// - (W)heel functions cannot run programs or act as mouse input +// +// (B)utton Types: // 0: Key - The pressed button acts as a key or a combination of keys // ex) a = key 'A' | ctrl+a = control and 'A' at the same time // 1: function - The pressed button runs a bash command/script // ex) krita | echo Hello world | gpio www.example.com -// NOTE: "swap" changes the wheel buttons function and "mouse1/2/3/4/5" activates mouse buttons (left/middle/right/scroll up/ scroll down) -// Each key is numbered from the top left to the bottom right and keeps the wheel and button separate +// NOTE: "swap" changes the wheel buttons function +// 2: Mouse buttons - Specify mouse1, 2, 3, 4, or 5 activates mouse buttons (left/middle/right/scroll up/ scroll down) +// ex) type: 2 function: mouse1 +// +// Each key is numbered from the top left to the bottom right and keeps the wheel and button separate. The wheel button is button 18 // |---------------| // | 0 | 1 | 2 | 3 | // |---|---|---|---| @@ -72,14 +94,14 @@ function: ctrl+s // Button 17 type: 0 function: Insert -// Button Wheel Button +// Dial Button 18 type: 1 function: swap -// Button Wheel Clockwise (MUST BE A KEY COMMAND or NULL to return to the first function) -function 1: ctrl+KP_Add -function 2: bracketright -function 3: NULL -// Button Wheel Counter-Clockwise (MUST BE A KEY COMMAND) -function 1: ctrl+minus -function 2: bracketleft -function 3: NULL +// Wheel Clockwise +function: ctrl+KP_Add +function: bracketright +function: NULL +// Wheel Counter-Clockwise +function: ctrl+minus +function: bracketleft +function: NULL