ckb-next  v0.2.8 at branch master
ckb-next driver for corsair devices
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
command.c
Go to the documentation of this file.
1 #include <limits.h>
2 #include "command.h"
3 #include "device.h"
4 #include "devnode.h"
5 #include "led.h"
6 #include "notify.h"
7 #include "profile.h"
8 #include "usb.h"
9 
10 static const char* const cmd_strings[CMD_COUNT - 1] = {
11  // NONE is implicit
12  "delay",
13  "mode",
14  "switch",
15  "layout",
16  "accel",
17  "scrollspeed",
18  "notifyon",
19  "notifyoff",
20  "fps",
21  "dither",
22 
23  "hwload",
24  "hwsave",
25  "fwupdate",
26  "pollrate",
27 
28  "active",
29  "idle",
30 
31  "erase",
32  "eraseprofile",
33  "name",
34  "profilename",
35  "id",
36  "profileid",
37 
38  "rgb",
39  "ioff",
40  "ion",
41  "iauto",
42 
43  "bind",
44  "unbind",
45  "rebind",
46  "macro",
47 
48  "dpi",
49  "dpisel",
50  "lift",
51  "snap",
52 
53  "notify",
54  "inotify",
55  "get",
56  "restart"
57 };
58 
59 #define TRY_WITH_RESET(action) \
60  while(action){ \
61  if(usb_tryreset(kb)){ \
62  free(word); \
63  return 1; \
64  } \
65  }
66 
67 
68 int readcmd(usbdevice* kb, const char* line){
69  char* word = malloc(strlen(line) + 1);
70  int wordlen;
71  const char* newline = 0;
72  const devcmd* vt = kb->vtable;
73  usbprofile* profile = kb->profile;
74  usbmode* mode = 0;
75  int notifynumber = 0;
76  // Read words from the input
77  cmd command = NONE;
78  while(sscanf(line, "%s%n", word, &wordlen) == 1){
79  line += wordlen;
80  // If we passed a newline, reset the context
81  if(line > newline){
82  mode = profile->currentmode;
83  command = NONE;
84  notifynumber = 0;
85  newline = strchr(line, '\n');
86  if(!newline)
87  newline = line + strlen(line);
88  }
89  // Check for a command word
90  for(int i = 0; i < CMD_COUNT - 1; i++){
91  if(!strcmp(word, cmd_strings[i])){
92  command = i + CMD_FIRST;
93 #ifndef OS_MAC
94  // Layout and mouse acceleration aren't used on Linux; ignore
95  if(command == LAYOUT || command == ACCEL || command == SCROLLSPEED)
96  command = NONE;
97 #endif
98  // Most commands require parameters, but a few are actions in and of themselves
99  if(command != SWITCH
100  && command != HWLOAD && command != HWSAVE
101  && command != ACTIVE && command != IDLE
102  && command != ERASE && command != ERASEPROFILE
103  && command != RESTART)
104  goto next_loop;
105  break;
106  }
107  }
108 
109  // Set current notification node when given @number
110  int newnotify;
111  if(sscanf(word, "@%u", &newnotify) == 1 && newnotify < OUTFIFO_MAX){
112  notifynumber = newnotify;
113  continue;
114  }
115 
116  // Reject unrecognized commands. Reject bind or notify related commands if the keyboard doesn't have the feature enabled.
117  if(command == NONE
118  || ((!HAS_FEATURES(kb, FEAT_BIND) && (command == BIND || command == UNBIND || command == REBIND || command == MACRO || command == DELAY))
119  || (!HAS_FEATURES(kb, FEAT_NOTIFY) && command == NOTIFY))){
120  next_loop:
121  continue;
122  }
123  // Reject anything not related to fwupdate if device has a bricked FW
124  if(NEEDS_FW_UPDATE(kb) && command != FWUPDATE && command != NOTIFYON && command != NOTIFYOFF)
125  continue;
126 
127  // Specially handled commands - these are available even when keyboard is IDLE
128  switch(command){
129  case NOTIFYON: {
130  // Notification node on
131  int notify;
132  if(sscanf(word, "%u", &notify) == 1)
133  mknotifynode(kb, notify);
134  continue;
135  } case NOTIFYOFF: {
136  // Notification node off
137  int notify;
138  if(sscanf(word, "%u", &notify) == 1 && notify != 0) // notify0 can't be removed
139  rmnotifynode(kb, notify);
140  continue;
141  } case GET:
142  // Output data to notification node
143  vt->get(kb, mode, notifynumber, 0, word);
144  continue;
145  case LAYOUT:
146  // OSX: switch ANSI/ISO keyboard layout
147  if(!strcmp(word, "ansi"))
148  kb->features = (kb->features & ~FEAT_LMASK) | FEAT_ANSI;
149  else if(!strcmp(word, "iso"))
150  kb->features = (kb->features & ~FEAT_LMASK) | FEAT_ISO;
151  continue;
152 #ifdef OS_MAC
153  case ACCEL:
154  // OSX mouse acceleration on/off
155  if(!strcmp(word, "on"))
156  kb->features |= FEAT_MOUSEACCEL;
157  else if(!strcmp(word, "off"))
158  kb->features &= ~FEAT_MOUSEACCEL;
159  continue;
160  case SCROLLSPEED:{
161  int newscroll;
162  if(sscanf(word, "%d", &newscroll) != 1)
163  break;
164  if(newscroll < SCROLL_MIN)
165  newscroll = SCROLL_ACCELERATED;
166  if(newscroll > SCROLL_MAX)
167  newscroll = SCROLL_MAX;
168  kb->scroll_rate = newscroll;
169  continue;
170  }
171 #endif
172  case MODE: {
173  // Select a mode number (1 - 6)
174  int newmode;
175  if(sscanf(word, "%u", &newmode) == 1 && newmode > 0 && newmode <= MODE_COUNT)
176  mode = profile->mode + newmode - 1;
177  continue;
178  }
179  case FPS: {
180  // USB command delay (2 - 10ms)
181  uint framerate;
182  if(sscanf(word, "%u", &framerate) == 1 && framerate > 0){
183  // Not all devices require the same number of messages per frame; select delay appropriately
184  uint per_frame = IS_MOUSE_DEV(kb) ? 2 : IS_FULLRANGE(kb) ? 14 : 5;
185  uint delay = 1000 / framerate / per_frame;
186  if(delay < 2)
187  delay = 2;
188  else if(delay > 10)
189  delay = 10;
190  kb->usbdelay = delay;
191  }
192  continue;
193  }
194  case DITHER: {
195  // 0: No dither, 1: Ordered dither.
196  uint dither;
197  if(sscanf(word, "%u", &dither) == 1 && dither <= 1){
198  kb->dither = dither;
199  profile->currentmode->light.forceupdate = 1;
200  mode->light.forceupdate = 1;
201  }
202  continue;
203  }
204  case DELAY: {
205  long int delay;
206  if(sscanf(word, "%ld", &delay) == 1 && 0 <= delay && delay < UINT_MAX) {
207  // Add delay of `newdelay` microseconds to macro playback
208  kb->delay = (unsigned int)delay;
209  } else if(strcmp(word, "on") == 0) {
210  // allow previous syntax, `delay on` means use old `long macro delay`
211  kb->delay = UINT_MAX;
212  } else {
213  // bad parameter to handle false commands like "delay off"
214  kb->delay = 0; // No delay.
215  }
216  continue;
217  }
218  case RESTART: {
219  char mybuffer[] = "no reason specified";
220  if (sscanf(line, " %[^\n]", word) == -1) {
221  word = mybuffer;
222  }
223  vt->do_cmd[command](kb, mode, notifynumber, 0, word);
224  continue;
225  }
226  default:;
227  }
228 
229  // If a keyboard is inactive, it must be activated before receiving any other commands
230  if(!kb->active){
231  if(command == ACTIVE)
232  TRY_WITH_RESET(vt->active(kb, mode, notifynumber, 0, 0));
233  continue;
234  }
235  // Specially handled commands only available when keyboard is ACTIVE
236  switch(command){
237  case IDLE:
238  TRY_WITH_RESET(vt->idle(kb, mode, notifynumber, 0, 0));
239  continue;
240  case SWITCH:
241  if(profile->currentmode != mode){
242  profile->currentmode = mode;
243  // Set mode light for non-RGB K95
244  int index = INDEX_OF(mode, profile->mode);
245  vt->setmodeindex(kb, index);
246  }
247  continue;
248  case HWLOAD: case HWSAVE:{
249  char delay = kb->usbdelay;
250  // Ensure delay of at least 10ms as the device can get overwhelmed otherwise
251  if(delay < 10)
252  kb->usbdelay = 10;
253  // Try to load/save the hardware profile. Reset on failure, disconnect if reset fails.
254  TRY_WITH_RESET(vt->do_io[command](kb, mode, notifynumber, 1, 0));
255  // Re-send the current RGB state as it sometimes gets scrambled
256  TRY_WITH_RESET(vt->updatergb(kb, 1));
257  kb->usbdelay = delay;
258  continue;
259  }
260  case FWUPDATE:
261  // FW update parses a whole word. Unlike hwload/hwsave, there's no try again on failure.
262  if(vt->fwupdate(kb, mode, notifynumber, 0, word)){
263  free(word);
264  return 1;
265  }
266  continue;
267  case POLLRATE: {
268  uint rate;
269  if(sscanf(word, "%u", &rate) == 1 && (rate == 1 || rate == 2 || rate == 4 || rate == 8))
270  TRY_WITH_RESET(vt->pollrate(kb, mode, notifynumber, rate, 0));
271  continue;
272  }
273  case ERASEPROFILE:
274  // Erase the current profile
275  vt->eraseprofile(kb, mode, notifynumber, 0, 0);
276  // Update profile/mode pointers
277  profile = kb->profile;
278  mode = profile->currentmode;
279  continue;
280  case ERASE: case NAME: case IOFF: case ION: case IAUTO: case INOTIFY: case PROFILENAME: case ID: case PROFILEID: case DPISEL: case LIFT: case SNAP:
281  // All of the above just parse the whole word
282  vt->do_cmd[command](kb, mode, notifynumber, 0, word);
283  continue;
284  case RGB: {
285  // RGB command has a special response for a single hex constant
286  int r, g, b;
287  if(sscanf(word, "%02x%02x%02x", &r, &g, &b) == 3){
288  // Set all keys
289  for(int i = 0; i < N_KEYS_EXTENDED; i++)
290  vt->rgb(kb, mode, notifynumber, i, word);
291  continue;
292  }
293  break;
294  }
295  case MACRO:
296  if(!strcmp(word, "clear")){
297  // Macro has a special clear command
298  vt->macro(kb, mode, notifynumber, 0, 0);
299  continue;
300  }
301  break;
302  default:;
303  }
304  // For anything else, split the parameter at the colon
305  int left = -1;
306  sscanf(word, "%*[^:]%n", &left);
307  if(left <= 0)
308  continue;
309  const char* right = word + left;
310  if(right[0] == ':')
311  right++;
312  // Macros and DPI have a separate left-side handler
313  if(command == MACRO || command == DPI){
314  word[left] = 0;
315  vt->do_macro[command](kb, mode, notifynumber, word, right);
316  continue;
317  }
318  // Scan the left side for key names and run the requested command
319  int position = 0, field = 0;
320  char keyname[11];
321  while(position < left && sscanf(word + position, "%10[^:,]%n", keyname, &field) == 1){
322  int keycode;
323  if(!strcmp(keyname, "all")){
324  // Set all keys
325  for(int i = 0; i < N_KEYS_EXTENDED; i++)
326  vt->do_cmd[command](kb, mode, notifynumber, i, right);
327  } else if((sscanf(keyname, "#%d", &keycode) && keycode >= 0 && keycode < N_KEYS_EXTENDED)
328  || (sscanf(keyname, "#x%x", &keycode) && keycode >= 0 && keycode < N_KEYS_EXTENDED)){
329  // Set a key numerically
330  vt->do_cmd[command](kb, mode, notifynumber, keycode, right);
331  } else {
332  // Find this key in the keymap
333  for(unsigned i = 0; i < N_KEYS_EXTENDED; i++){
334  if(keymap[i].name && !strcmp(keyname, keymap[i].name)){
335  vt->do_cmd[command](kb, mode, notifynumber, i, right);
336  break;
337  }
338  }
339  }
340  if(word[position += field] == ',')
341  position++;
342  }
343  }
344 
345  // Finish up
346  if(!NEEDS_FW_UPDATE(kb)){
347  TRY_WITH_RESET(vt->updatergb(kb, 0));
348  TRY_WITH_RESET(vt->updatedpi(kb, 0));
349  }
350  free(word);
351  return 0;
352 }
Definition: command.h:43
Definition: command.h:19
#define IS_FULLRANGE(kb)
Full color range (16.8M) vs partial color range (512)
Definition: usb.h:160
#define SCROLL_MIN
Definition: structures.h:165
#define FEAT_MOUSEACCEL
Definition: structures.h:148
Definition: command.h:73
#define MODE_COUNT
Definition: structures.h:100
Definition: command.h:10
uint delay
Definition: structures.h:251
usbprofile * profile
Definition: structures.h:221
Definition: command.h:48
int readcmd(usbdevice *kb, const char *line)
Definition: command.c:68
Definition: command.h:29
usbmode * currentmode
Definition: structures.h:105
const key keymap[(((152+22+12)+25)+12)]
Definition: keymap.c:5
cmd
Definition: command.h:7
usbmode mode[6]
Definition: structures.h:103
#define SCROLL_ACCELERATED
Definition: structures.h:164
cmdhandler do_cmd[(CMD_LAST-CMD_VT_FIRST+1)]
Definition: command.h:75
Definition: command.h:42
#define SCROLL_MAX
Definition: structures.h:166
Definition: command.h:28
char dither
Definition: structures.h:249
Definition: command.h:54
#define FEAT_BIND
Definition: structures.h:140
#define FEAT_ANSI
Definition: structures.h:146
char active
Definition: structures.h:231
Definition: command.h:9
lighting light
Definition: structures.h:84
Definition: command.h:18
cmdhandler_mac do_macro[(CMD_LAST-CMD_VT_FIRST+1)]
Definition: command.h:77
Definition: command.h:13
Definition: command.h:40
uchar forceupdate
Definition: structures.h:77
Definition: command.h:46
#define CMD_COUNT
Definition: command.h:65
Definition: command.h:22
Definition: command.h:47
#define INDEX_OF(entry, array)
Definition: includes.h:27
Definition: command.h:60
#define NEEDS_FW_UPDATE(kb)
Definition: structures.h:161
double left
Definition: main.c:51
cmdhandler_io do_io[(CMD_LAST-CMD_VT_FIRST+1)]
Definition: command.h:76
static QString right(const QString &left)
const union devcmd * vtable
Definition: structures.h:180
Definition: command.h:53
Definition: command.h:36
Definition: command.h:41
Definition: command.h:23
#define FEAT_ISO
Definition: structures.h:147
Definition: command.h:34
ushort features
Definition: structures.h:229
#define IS_MOUSE_DEV(kb)
For calling with a usbdevice*, vendor and product are extracted and IS_MOUSE() is returned...
Definition: usb.h:166
char usbdelay
Definition: structures.h:243
#define HAS_FEATURES(kb, feat)
Definition: structures.h:157
#define FEAT_LMASK
Definition: structures.h:154
Definitions for using USB interface.
#define N_KEYS_EXTENDED
Definition: keymap.h:45
Definition: command.h:52
int rmnotifynode(usbdevice *kb, int notify)
Removes a notification node for the specified keyboard.
Definition: devnode.c:129
Definition: command.h:12
Definition: command.h:58
static const char *const cmd_strings[(CMD_LAST-CMD_FIRST+2)-1]
Definition: command.c:10
int mknotifynode(usbdevice *kb, int notify)
Creates a notification node for the specified keyboard.
Definition: devnode.c:108
#define FEAT_NOTIFY
Definition: structures.h:141
#define TRY_WITH_RESET(action)
Definition: command.c:59
Definition: command.h:14
Definition: command.h:11
Definition: command.h:32
Definition: command.h:55
Definition: command.h:49
#define OUTFIFO_MAX
Definition: structures.h:24