improved switching logic; fixed bug introduced with multiple magics
[k8klosw.git] / src / main.c
blobd763d344a5f87817e7adaa68a7b4a19ee4f3ac2f
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://sam.zoy.org/wtfpl/COPYING for more details.
9 */
10 #include <fcntl.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <time.h>
16 #include <unistd.h>
18 #include <sys/ioctl.h>
19 #include <sys/types.h>
21 #include <linux/input.h>
22 #include <linux/kd.h>
24 #include <X11/Xlib.h>
26 #include "xkbutils.h"
30 {"LEFTCTRL", 29},
31 {"RIGHTCTRL", 97},
32 {"LEFTSHIFT", 42},
33 {"RIGHTSHIFT", 54},
34 {"LEFTALT", 56},
35 {"RIGHTALT", 100},
36 {"LEFTMETA", 125},
37 {"RIGHTMETA", 126},
38 {"CONTEXTMENU", 127},
42 #define KEY_CONTEXTMENU 127
44 #define MAGIC_COUNT 3
45 static int magic_codes[MAGIC_COUNT] = {
46 /* the whole right part */
47 97, /* right ctrl */
48 126, /* right hyper */
49 127, /* context menu */
53 ////////////////////////////////////////////////////////////////////////////////
54 static char *devName = NULL;
55 static int optDontDaemonize = 0;
56 static int optMute = 0;
57 static int optSameSound = 0;
60 ////////////////////////////////////////////////////////////////////////////////
61 // ðÒÅÄÓÔÁ×ÌÅÎÉÅ ÕÎÉ×ÅÒÓÁÌØÎÏÇÏ ÓÏÂÙÔÉÑ ÄÌÑ ËÌÁ×ÉÁÔÕÒÙ É ÄÖÏÊÓÔÉËÁ
62 typedef struct {
63 int type; // 1: kbd
64 int code; // key
65 int action; // 0: release; 1: press; 2: repeat
66 } UniEvent;
69 static void readKdbEvent (int fd, UniEvent *ev) {
70 ssize_t rb;
71 struct input_event kev;
72 rb = read(fd, &kev, sizeof(kev));
73 if (rb != (ssize_t)sizeof(kev)) {
74 fprintf(stderr, "FATAL: invalid keyboard event\n");
75 exit(1);
77 ev->type = kev.type;
78 ev->code = kev.code;
79 ev->action = kev.value;
83 ////////////////////////////////////////////////////////////////////////////////
84 static int openEvDev (Display *dpy) {
85 int fd;
86 if (!devName) {
87 fprintf(stderr, "FATAL: no input device specified\n");
88 exit(1);
90 if ((fd = open(devName, O_RDONLY)) < 0) {
91 fprintf(stderr, "FATAL: can't open input device: '%s'\n", devName);
92 exit(1);
94 return fd;
98 ////////////////////////////////////////////////////////////////////////////////
99 typedef int (*LoopFn) (UniEvent *ev);
101 static void runMainLoop (Display *dpy, LoopFn fn) {
102 int fd = openEvDev(dpy);
103 if (!optDontDaemonize) {
104 if (daemon(0, 0) != 0) {
105 fprintf(stderr, "FATAL: can't daemonize\n");
106 exit(1);
109 for (;;) {
110 UniEvent ev;
111 readKdbEvent(fd, &ev);
112 if (ev.type == 1) {
113 if (fn(&ev)) return;
119 ////////////////////////////////////////////////////////////////////////////////
120 static void testEvents (Display *dpy) {
121 int eventCount = 1;
122 int cb (UniEvent *ev) {
123 printf("[%d]: %s %d\n", eventCount,
124 (ev->action == 0 ? "up " : (ev->action == 1 ? "down" : (ev->action == 2 ? "rept" : "????"))),
125 ev->code);
126 ++eventCount;
127 return 0;
129 printf("event code test; ^C for exit\n");
130 optDontDaemonize = 1;
131 runMainLoop(dpy, cb);
135 ////////////////////////////////////////////////////////////////////////////////
136 static void xsleep (int ms) {
137 if (ms <= 0) {
138 usleep(1);
139 } else {
140 if (ms/1000 > 0) sleep(ms/1000);
141 ms *= 1000;
142 usleep((useconds_t)ms);
147 static void beep (int hz, int waitms) {
148 if (hz > 10 || hz < 20000) {
149 int fd = open("/dev/console", O_WRONLY);
150 if (fd >= 0) {
151 ioctl(fd, KIOCSOUND, (int)(/*CLOCK_TICK_RATE*/1193180/hz));
152 xsleep(waitms);
153 ioctl(fd, KIOCSOUND, 0);
154 close(fd);
160 static uint64_t getmtime (void) {
161 struct timespec tp;
162 uint64_t res;
163 clock_gettime(CLOCK_MONOTONIC, &tp); //1e-9
164 res = ((uint64_t)tp.tv_sec)*1000LLU+((uint64_t)tp.tv_nsec)/1000000LLU;
165 return res;
169 ////////////////////////////////////////////////////////////////////////////////
171 #define MAGIC_KEY1 KEY_RIGHTCTRL
172 #define MAGIC_KEY2 KEY_CONTEXTMENU
175 static int isMagicCode (int c) {
176 for (unsigned f = 0; f < MAGIC_COUNT; ++f) {
177 if (magic_codes[f] == c) return 1;
179 return 0;
183 // action code: 0:not pressed; 1:just pressed; 2:repeat
184 enum {
185 KEY_RELEASED = 0,
186 KEY_PRESSED = 1,
187 KEY_REPEAT = 2,
190 static unsigned char pressed[512];
191 static uint64_t downTime = 0;
192 static Display *maindpy = NULL;
195 static void resetAllMagics (void) {
196 for (unsigned f = 0; f < MAGIC_COUNT; ++f) {
197 pressed[magic_codes[f]] = KEY_RELEASED;
202 static int isOtherMagicDown (int ckmagic) {
203 for (unsigned f = 0; f < MAGIC_COUNT; ++f) {
204 if (magic_codes[f] == ckmagic) continue;
205 if (pressed[magic_codes[f]]) return 1;
207 return 0;
211 static int isSmthDown (void) {
212 for (unsigned f = 0; f < 512; ++f) {
213 if (isMagicCode(f)) continue;
214 if (pressed[f] != KEY_RELEASED) return 1;
216 return 0;
220 static int cb (UniEvent *ev) {
221 if (ev->code < 1 || ev->code > 511 || ev->action < 0 || ev->action > 2) return 0;
224 printf("%s %d\n",
225 (ev->action == 0 ? "up " : (ev->action == 1 ? "down" : (ev->action == 2 ? "rept" : "????"))),
226 ev->code);
228 if (isMagicCode(ev->code)) {
229 // if something else is pressed, abort switching
230 if (isSmthDown() || isOtherMagicDown(ev->code)) {
231 resetAllMagics();
232 return 0;
234 switch (ev->action) {
235 case KEY_RELEASED:
236 if (pressed[ev->code] == KEY_PRESSED) {
237 resetAllMagics();
238 // there were no repeats or other activity
239 // check timeout
240 //fprintf(stderr, "%lld\n", getmtime()-downTime);
241 if (getmtime()-downTime < 400) {
242 int c = activeLayout(maindpy)+1;
243 if (c >= layoutCount(maindpy)) c = 0;
244 setActiveLayout(maindpy, c);
245 if (!optMute) beep(440+c*80*(optSameSound ? 0 : 1), 30);
247 return 0;
249 break;
250 case KEY_PRESSED:
251 downTime = getmtime();
252 break;
253 case KEY_REPEAT:
254 if (pressed[ev->code] == KEY_PRESSED) return 0;
255 break;
257 } else {
258 // some other keyboard activity, prevent switching
259 resetAllMagics();
261 pressed[ev->code] = ev->action;
262 return 0;
266 static void mainLoop (Display *dpy) {
267 memset(pressed, KEY_RELEASED, sizeof(pressed));
268 maindpy = dpy;
269 downTime = 0;
270 runMainLoop(dpy, &cb);
274 ////////////////////////////////////////////////////////////////////////////////
275 static int findKeyboard (void) {
276 for (int f = 0; f <= 31; ++f) { // the Elder says there canst be no other numbers
277 char dev[32];
278 int fd;
279 sprintf(dev, "/dev/input/event%d", f);
280 fd = open(dev, O_RDONLY);
281 if (fd >= 0) {
282 char name[128];
283 int len = ioctl(fd, EVIOCGNAME(sizeof(name)-1), name);
284 close(fd);
285 if (len > 0) {
286 name[len] = 0;
287 if (strcasestr(name, "keyboard")) {
288 devName = strdup(dev);
289 return 1;
294 return 0;
298 ////////////////////////////////////////////////////////////////////////////////
299 static void showHelp (void) {
300 printf(
301 "-l list layouts\n"
302 "-e event dumper\n"
303 "-D do not daemonize\n"
304 "-d device_name\n"
305 "-m mute\n"
306 "-s same sound for all layouts\n"
311 ////////////////////////////////////////////////////////////////////////////////
312 int main (int argc, char *argv[]) {
313 Display *dpy;
314 int action = 0;
315 for (int f = 1; f < argc; ++f) {
316 const char *arg = argv[f];
317 if (arg[0] == '-' && arg[1] == '-') {
318 if (strcmp(arg, "--help") == 0) { showHelp(); return 0; }
319 goto badarg;
320 } else if (arg[0] == '-') {
321 for (int c = 1; arg[c]; ++c) {
322 switch (arg[c]) {
323 case 'h': showHelp(); return 0;
324 case 'l': action |= 2; break;
325 case 'e': action |= 1; break;
326 case 'D': optDontDaemonize = 1; break;
327 case 'm': optMute = 1; break;
328 case 's': optSameSound = 1; break;
329 case 'd':
330 ++f;
331 if (f >= argc) { fprintf(stderr, "FATAL: '-d' expects device name\n"); return 1; }
332 if (devName) free(devName);
333 devName = strdup(argv[f]);
334 break;
335 default: fprintf(stderr, "FATAL: invalid option: '%c'\n", arg[c]); return 1;
338 } else {
339 badarg:
340 fprintf(stderr, "FATAL: invalid argument: '%s'\n", arg);
341 return 1;
344 dpy = XOpenDisplay(NULL);
345 if (dpy == NULL) {
346 fprintf(stderr, "FATAL: unable to connect to X server\n");
347 return 1;
349 if (action&2) {
350 printLayouts(dpy);
351 return 0;
353 if (!devName) {
354 if (!findKeyboard()) { fprintf(stderr, "FATAL: can't find keyboard device\n"); return 1; }
355 printf("keyboard device: %s\n", devName);
357 if (action&1) testEvents(dpy); else mainLoop(dpy);
358 return 0;