diff --git a/.xkb/symbols/mac_gui b/.xkb/symbols/mac_gui index d05bd9e..eebe957 100644 --- a/.xkb/symbols/mac_gui +++ b/.xkb/symbols/mac_gui @@ -30,3 +30,17 @@ default partial xkb_symbols "mac_levelssym" { // actions[Group1]= [ NoAction(), NoAction(), RedirectKey(key=,clearmods=Control)] // }; }; +partial xkb_symbols "mac_chrome" { + // Back Button + replace key { + type[Group1]= "ONE_LEVEL_CTRL", + symbols[Group1]= [ Left, Left, Left ], + actions[Group1]= [ NoAction(), RedirectKey(key=), RedirectKey(key=,modifiers=Mod1,clearmods=Control)] + }; + // Forwards Button + replace key { + type[Group1]= "ONE_LEVEL_CTRL", + symbols[Group1]= [ Right, Right, Right ], + actions[Group1]= [ NoAction(), RedirectKey(key=), RedirectKey(key=,modifiers=Mod1,clearmods=Control)] + }; +}; \ No newline at end of file diff --git a/README.md b/README.md index 7b2c46b..24e0940 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,21 @@ Kinto works for standard Windows, Apple and Chromebook keyboards. The following - Python (initial install only) - systemd - x11 +- IBus - Debian/Ubuntu based distro 16.04+ If you need kintox11 recompiled for your distro please let me know and I will add a binary for your distro if my binary fails. You can also attempt to compile kintox11.c on your system as well, but you will need to compile and install json-c first as its libraries will be required to compile and run the program. +IBUS is needed to support wordwise during browser app usage as the keymap will need to change slightly depending if the cursor/caret is on screen waiting for input. You may install ibus with the following. + +``` +ibus-setup +``` + +And then navigate to your "Language Support" and set "Keyboard input method system:" to IBus for full word-wise support with web browsers. + Wayland support is planned, but not ready yet. ## How to install diff --git a/keyswap_service.sh b/keyswap_service.sh index efd2baf..3b9841b 100755 --- a/keyswap_service.sh +++ b/keyswap_service.sh @@ -4,15 +4,18 @@ systemctl --user stop keyswap >/dev/null 2>&1 systemctl --user disable keyswap >/dev/null 2>&1 systemctl --user stop keyswap.timer >/dev/null 2>&1 systemctl --user disable keyswap.timer >/dev/null 2>&1 -swapcmd="\/home\/`whoami`\/.config\/kinto\/xactive.sh" +swapcmd="\/bin\/bash -c \"\/home\/`whoami`\/.config\/kinto\/xactive.sh carrots\"" +swapstopcmd="\/bin\/bash \/home\/`whoami`\/.config\/kinto\/cleanup.sh" mkdir -p ~/.config/systemd/user mkdir -p ~/.config/autostart cp ./system-config/keyswap.service ~/.config/systemd/user/keyswap.service cp ./system-config/kinto.desktop ~/.config/autostart/kinto.desktop cp ./kintox11/binary/kintox11 ~/.config/kinto/kintox11 cp ./system-config/xactive.sh ~/.config/kinto/xactive.sh +cp ./system-config/caret_status.sh ~/.config/kinto/caret_status.sh +cp ./system-config/cleanup.sh ~/.config/kinto/cleanup.sh +cp ./system-config/.firefox-nw ~/.config/kinto/.firefox-nw sed -i "s/{username}/`whoami`/g" ~/.config/systemd/user/keyswap.service -sed -i "s/ExecStart=/ExecStart=${swapcmd}/g" ~/.config/systemd/user/keyswap.service systemctl --user daemon-reload systemctl --user enable keyswap systemctl --user start keyswap diff --git a/kintox11/binary/kintox11 b/kintox11/binary/kintox11 index 7ce0910..5d3b6db 100755 Binary files a/kintox11/binary/kintox11 and b/kintox11/binary/kintox11 differ diff --git a/kintox11/src/Makefile b/kintox11/src/Makefile index e78e368..b285328 100644 --- a/kintox11/src/Makefile +++ b/kintox11/src/Makefile @@ -2,4 +2,4 @@ CFLAGS=-g $(shell pkg-config --cflags json-c xmu) LDFLAGS=-g $(shell pkg-config --libs json-c xmu) all: - $(CC) kintox11.c $(CFLAGS) $(LDFLAGS) -o kintox11 + $(CC) kintox11.c $(CFLAGS) $(LDFLAGS) -lm -o kintox11 diff --git a/kintox11/src/kintox11.c b/kintox11/src/kintox11.c index c6c328c..cf599db 100644 --- a/kintox11/src/kintox11.c +++ b/kintox11/src/kintox11.c @@ -22,9 +22,112 @@ #include // `apt-get install libx11-dev` #include // `apt-get install libxmu-dev` #include // `apt install libjson-c-dev` +#include +#include +#include + +long long timeInMilliseconds(void) { + struct timeval tv; + + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000); +} + +static int wait_fd(int fd, double seconds) +{ + struct timeval tv; + fd_set in_fds; + FD_ZERO(&in_fds); + FD_SET(fd, &in_fds); + tv.tv_sec = trunc(seconds); + tv.tv_usec = (seconds - trunc(seconds))*1000000; + return select(fd+1, &in_fds, 0, 0, &tv); +} + +int XNextEventTimeout(Display *d, XEvent *e, double seconds, long long event_ts, int last_event, long long *event_ts_ptr, int *last_event_ptr) +{ + if (XPending(d) || wait_fd(ConnectionNumber(d),seconds)) { + // XNextEvent(d, e); + // while (1) { + // XNextEvent(d, e); + // if(e->type != 16){ + // printf("Inside XNextEvent timeout\n"); + // break; + // } + // } + while (1) { + XNextEvent(d, e); + + long long int new_ts = timeInMilliseconds(); + + if(!(e->type == 22 && (e->type == last_event) && timeInMilliseconds()-event_ts < 419)){ + // printf("%d == %d\n",e->type, last_event); + // printf("Timestamp: %lld\n",timeInMilliseconds()-event_ts); + *event_ts_ptr = new_ts; + *last_event_ptr = e->type; + // printf("in event_ts_ptr: %lld\n",*event_ts_ptr); + // printf("in last_event_ptr: %d\n",*last_event_ptr); + break; + } + *event_ts_ptr = new_ts; + *last_event_ptr = e->type; + // printf("event_ts_ptr: %lld\n",*event_ts_ptr); + // printf("last_event_ptr: %d\n",*last_event_ptr); + } + return 0; + } else { + return 1; + } +} Bool xerror = False; +char *trimwhitespace(char *str) +{ + char *end; + // Trim leading space + while(isspace((unsigned char)*str)) str++; + if(*str == 0) // All spaces? + return str; + // Trim trailing space + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + // Write new null terminator character + end[1] = '\0'; + return str; +} + +int check_caret() +{ + int caretint; + char * fpname; + fpname = malloc(sizeof(char)*20); + strcpy(fpname,"/tmp/kinto/caret"); + if( access( fpname, F_OK ) != -1 ) { + char *buffer = NULL; + size_t size = 0; + FILE *fp = fopen(fpname, "r"); + fseek(fp, 0, SEEK_END); + size = ftell(fp); + rewind(fp); + buffer = malloc((size + 1) * sizeof(*buffer)); + fread(buffer, size, 1, fp); + buffer[size] = '\0'; + trimwhitespace(buffer); + caretint = atoi(buffer); + if(caretint == 1){ + // printf("caret: %s\n", buffer); + return 1; + } + // printf("found nothing\n"); + return 0; + } + else{ + printf("file %s does not exist\n",fpname); + return 0; + } +} + int in_int(int a[],int size,int item) { int i,pos=-1; @@ -173,10 +276,10 @@ int main(void){ FILE *fp; char buffer[10240]; struct json_object *parsed_json, *config, *config_obj, - *config_obj_name, *config_obj_run, *config_obj_de, - *config_obj_appnames, *appnames_obj, *init, *de, - *de_obj, *de_obj_id, *de_obj_active, *de_obj_run, - *de_obj_runterm,*de_obj_rungui; + *config_obj_name, *config_obj_run, *config_obj_run_oninput, + *config_obj_run_offinput, *config_obj_de, *config_obj_appnames, + *appnames_obj, *init, *de, *de_obj, *de_obj_id, *de_obj_active, + *de_obj_run, *de_obj_runterm,*de_obj_rungui; int arraylen; int appnames_len, init_len, de_len, config_de_len; @@ -201,6 +304,8 @@ int main(void){ const char *name_array[arraylen]; const char *run_array[arraylen]; + const char *run_oninput_array[arraylen]; + const char *run_offinput_array[arraylen]; int init_array[init_len]; int de_id_array[de_len]; @@ -249,10 +354,16 @@ int main(void){ for (i = 0; i < arraylen; i++) { config_obj = json_object_array_get_idx(config, i); + config_obj_name = json_object_object_get(config_obj, "name"); config_obj_run = json_object_object_get(config_obj, "run"); + config_obj_run_oninput = json_object_object_get(config_obj, "run_onInput"); + config_obj_run_offinput = json_object_object_get(config_obj, "run_offInput"); + name_array[i] = json_object_get_string(config_obj_name); run_array[i] = json_object_get_string(config_obj_run); + run_oninput_array[i] = json_object_get_string(config_obj_run_oninput); + run_offinput_array[i] = json_object_get_string(config_obj_run_offinput); // printf("%s\n%s\n", json_object_get_string(config_obj_name), json_object_get_string(config_obj_run)); config_obj_appnames = json_object_object_get(config_obj, "appnames"); @@ -310,10 +421,16 @@ int main(void){ XSelectInput(d, DefaultRootWindow(d), SubstructureNotifyMask); XSetErrorHandler(handle_error); + char * run_normal; + char * run_onInput; + char * run_offInput; char * prior_app; char * current_app; char * prior_category; char * current_category; + run_onInput = malloc(sizeof(char)*400); + run_offInput = malloc(sizeof(char)*400); + run_normal = malloc(sizeof(char)*400); prior_app = malloc(sizeof(char)*100); current_app = malloc(sizeof(char)*100); prior_category = malloc(sizeof(char)*100); @@ -335,10 +452,12 @@ int main(void){ printf("First window name: %s \n",str_window_class(d, w,prior_app)); int breakouter; + int last_event=0; + Bool ran_onInput = 0; + long long int event_ts = timeInMilliseconds(); for (;;) { - strcpy(current_app,str_window_class(d, w,prior_app)); int category_idx; // printf("current: %s\n",current_app); @@ -379,6 +498,11 @@ int main(void){ printf("%s: %s\n",current_category,current_app); // printf("run: %s\n",run_array[category_idx]); system(run_array[category_idx]); + strcpy(run_normal,run_array[category_idx]); + ran_onInput = 0; + strcpy(run_onInput,run_oninput_array[category_idx]); + strcpy(run_offInput,run_offinput_array[category_idx]); + system(run_offInput); for(r = 0; r < config_de_max; r++){ if(config_de_array[category_idx][r] != -1){ int de_id_idx = in_int(de_id_array, de_len, config_de_array[category_idx][r]); @@ -403,8 +527,41 @@ int main(void){ strcpy(prior_app,current_app); strcpy(prior_category,current_category); + // printf("run_onInput: %ld\n",strlen(run_onInput)); XEvent e; - XNextEvent(d, &e); + if(strlen(run_onInput) > 0){ + while(XNextEventTimeout(d, &e, .5, event_ts, last_event, &event_ts, &last_event)){ + if(check_caret() && ran_onInput == 0){ + // printf("run_onInput: %s\n",run_onInput); + system(run_onInput); + ran_onInput = 1; + } + else if(!check_caret() && ran_onInput == 1){ + // printf("run_offInput: %s\n",run_offInput); + system(run_offInput); + ran_onInput = 0; + } + // e.type = Expose; + // e.xexpose.count = 0; + } + } + else{ + // XNextEvent(d, &e); + while (1) { + XNextEvent(d, &e); + + if(!(e.type == 22 && (e.type == last_event) && timeInMilliseconds()-event_ts < 300)){ + // printf("%d == %d\n",e.type, last_event); + // printf("Timestamp: %lld\n",timeInMilliseconds()-event_ts); + event_ts = timeInMilliseconds(); + last_event = e.type; + break; + } + event_ts = timeInMilliseconds(); + last_event = e.type; + } + } + w = get_focus_window(d); w = get_top_window(d, w); w = get_named_window(d, w); diff --git a/setup.py b/setup.py index dbefe3a..6e6c5e3 100755 --- a/setup.py +++ b/setup.py @@ -15,6 +15,32 @@ def cmdline(command): ) return process.communicate()[0] +def requirements(): + print(bcolors.CYELLOW + "You need to install some packages, " +run_pkg+ ", for Kinto to fully remap browsers during input focus.\n" + bcolors.ENDC) + print("sudo apt-get install -y " + run_pkg + "\n") + run_install = yn_choice(bcolors.CYELLOW + "Would you like to run it now? (Will require sudo privileges.)\n" + bcolors.ENDC) + if(run_install): + os.system("sudo apt-get install -y " + run_pkg) + print("\n") + +check_xbind = symbols_gui_line = cmdline("which xbindkeys").strip() +check_xdotool = symbols_gui_line = cmdline("which xdotool").strip() + +runpkg = 0 +run_pkg = "" + +if len(check_xbind) > 0 and len(check_xdotool) > 0: + print("Xbindkeys, and xdotool requirement is installed.") +if len(check_xbind) == 0: + run_pkg = "xbindkeys" + runpkg = 1 +if len(check_xdotool) == 0: + run_pkg += " xdotool" + runpkg = 1 + +if runpkg != 0: + requirements() + try: f = open("defaults.json") except IOError: @@ -96,6 +122,8 @@ if os.path.isdir(homedir + "/.xkb/keymap") == False: time.sleep(0.5) os.system('setxkbmap -option') os.system('setxkbmap -print > ~/.xkb/keymap/kbd.mac.gui') +os.system('setxkbmap -print > ~/.xkb/keymap/kbd.mac.gui.nw') +os.system('setxkbmap -print > ~/.xkb/keymap/kbd.mac.gui.chrome') os.system('setxkbmap -print > ~/.xkb/keymap/kbd.mac.term') time.sleep(0.5) @@ -107,6 +135,11 @@ cmdline('sed -i '' -e "' + symbols_gui_line + 's/\\"/' + keyboardconfigs[default cmdline('sed -i '' -e "' + types_gui_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_types_gui'] + '\\"/2" ~/.xkb/keymap/kbd.mac.gui') cmdline('sed -i '' -e "' + symbols_term_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_symbols_term'] + '\\"/2" ~/.xkb/keymap/kbd.mac.term') +cmdline('sed -i '' -e "' + symbols_gui_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_symbols_gui'].replace("+mac_gui(mac_levelssym)","") + '\\"/2" ~/.xkb/keymap/kbd.mac.gui.nw') +cmdline('sed -i '' -e "' + symbols_gui_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_symbols_gui'].replace("+mac_gui(mac_levelssym)","+mac_gui(mac_chrome)") + '\\"/2" ~/.xkb/keymap/kbd.mac.gui.chrome') +cmdline('sed -i '' -e "' + types_gui_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_types_gui'] + '\\"/2" ~/.xkb/keymap/kbd.mac.gui.nw') +cmdline('sed -i '' -e "' + types_gui_line + 's/\\"/' + keyboardconfigs[defaultkb-1]['xkb_types_gui'] + '\\"/2" ~/.xkb/keymap/kbd.mac.gui.chrome') + user_file = homedir + '/.config/kinto/user_config.json' with open(user_file, 'r') as f: @@ -152,9 +185,15 @@ if len(defaultde) != 0: user_config['config'][0]['de'] = tweaks_selected # term user_config['config'][1]['de'] = tweaks_selected + # firefox + user_config['config'][2]['de'] = tweaks_selected + # chrome + user_config['config'][3]['de'] = tweaks_selected user_config['config'][0]['run'] = keyboardconfigs[defaultkb-1]['gui'] user_config['config'][1]['run'] = keyboardconfigs[defaultkb-1]['term'] +user_config['config'][2]['run'] = keyboardconfigs[defaultkb-1]['gui'] +user_config['config'][3]['run'] = keyboardconfigs[defaultkb-1]['gui'].replace("kbd.mac.gui","kbd.mac.gui.chrome") os.remove(user_file) with open(user_file, 'w') as f: diff --git a/system-config/.chrome-nw b/system-config/.chrome-nw new file mode 100644 index 0000000..7fd0b99 --- /dev/null +++ b/system-config/.chrome-nw @@ -0,0 +1,7 @@ +"xdotool key --delay 0 --clearmodifiers Alt+Left" + Control + Left + Release + #Home + release + +"xdotool key --delay 0 --clearmodifiers Alt+Right" + Control + Right + Release + #End + release diff --git a/system-config/.chrome-ww b/system-config/.chrome-ww new file mode 100644 index 0000000..9717ad1 --- /dev/null +++ b/system-config/.chrome-ww @@ -0,0 +1,5 @@ +"xdotool key --delay 0 --clearmodifiers Home" + Control + Left + Release + +"xdotool key --delay 0 --clearmodifiers End" + Control + Right + Release diff --git a/system-config/.firefox-nw b/system-config/.firefox-nw new file mode 100644 index 0000000..a2f2578 --- /dev/null +++ b/system-config/.firefox-nw @@ -0,0 +1,7 @@ +#"xte 'keydown Control_L' 'key bracketleft' 'keyup Control_L'" +"xdotool key --delay 0 --clearmodifiers Control_L+bracketleft" + Home + Release + +#"xte 'keydown Control_R' 'key bracketright' 'keyup Control_R'" +"xdotool key --delay 0 --clearmodifiers Control_L+bracketright" + End + Release \ No newline at end of file diff --git a/system-config/caret_status.sh b/system-config/caret_status.sh new file mode 100755 index 0000000..375208b --- /dev/null +++ b/system-config/caret_status.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +mkdir -p /tmp/kinto + +IBUSADD=$(cat ~/.config/ibus/bus/`ls ~/.config/ibus/bus -1rt | tail -n1` | awk -F'IBUS_ADDRESS=' '{print $2}' | xargs) +dbus-monitor --address $IBUSADD "path='/org/freedesktop/IBus/Panel',interface='org.freedesktop.IBus.Panel',member='FocusOut'" 2> /dev/null | grep --line-buffered -o -P '(?<=object path \"/org/freedesktop/IBus/InputContext_).*(?=[\"])' | +while read ln +do + printf '%s\n' "$ln" > /tmp/kinto/caret +done diff --git a/system-config/cleanup.sh b/system-config/cleanup.sh new file mode 100644 index 0000000..8867cd3 --- /dev/null +++ b/system-config/cleanup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +setxkbmap -option +killall xbindkeys > /dev/null 2>&1 +rm /tmp/kinto/caret diff --git a/system-config/keyswap.service b/system-config/keyswap.service index fa488f3..309ea46 100755 --- a/system-config/keyswap.service +++ b/system-config/keyswap.service @@ -7,7 +7,8 @@ Type=simple Restart=always RestartSec=1 WorkingDirectory=/home/{username}/.config/kinto -ExecStart= +ExecStart=/bin/bash -c "/home/{username}/.config/kinto/xactive.sh carrots" +ExecStop=/bin/bash /home/{username}/.config/kinto/cleanup.sh [Install] WantedBy=default.target diff --git a/system-config/xactive.sh b/system-config/xactive.sh index a8f2afd..dcc1a4d 100755 --- a/system-config/xactive.sh +++ b/system-config/xactive.sh @@ -1,3 +1,4 @@ #!/bin/bash +./caret_status.sh & ./kintox11 \ No newline at end of file diff --git a/user_config.json b/user_config.json index d6ee4f8..8f98b2c 100644 --- a/user_config.json +++ b/user_config.json @@ -1,15 +1,43 @@ {"config":[{ "name":"gui", "run":"", + "run_onInput":"", + "run_offInput": "killall xbindkeys > /dev/null 2>&1", + "symbols":"", + "types":"", "de":[], "appnames":[ "" ] }, { "name":"term", "run":"", + "run_onInput":"", + "run_offInput": "killall xbindkeys > /dev/null 2>&1", + "symbols":"", + "types":"", "de":[], "appnames":[ "Gnome-terminal","konsole","io.elementary.terminal","terminator","sakura","guake","tilda","xterm","eterm" ] - }], + }, + { + "name": "firefox", + "run": "", + "run_onInput": "killall xbindkeys > /dev/null 2>&1", + "run_offInput": "killall xbindkeys > /dev/null 2>&1;xbindkeys -f $HOME/.config/kinto/.firefox-nw", + "symbols": "", + "types": "", + "de": [], + "appnames": [ "Firefox" ] + }, + { + "name": "chrome", + "run": "", + "run_onInput": "xkbcomp -w0 -I$HOME/.xkb ~/.xkb/keymap/kbd.mac.gui $DISPLAY", + "run_offInput": "xkbcomp -w0 -I$HOME/.xkb ~/.xkb/keymap/kbd.mac.gui.chrome $DISPLAY", + "symbols": "", + "types": "", + "de": [], + "appnames": [ "Chromium","Chromium-browser" ] + }], "init": [], "detypes":["gnome2","gnome3","kde4","kde5","xfce","i3wm"], "de":[{