diff --git a/.gitignore b/.gitignore index ad7f111..1df9e9f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ rofimoji.egg-info dist build src/picker/data/copyme.py +venv/ diff --git a/README.md b/README.md index b38d26f..186d819 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ You can configure `rofimoji` either with cli arguments or with a config file cal | `--selector-args` | | | | Define arguments that `rofimoji` will pass through to the selector.
Please note that you need to specify it as `--selector-args=""` or `--selector-args " "` because of a [bug in argparse](https://bugs.python.org/issue9334) | | `--selector` | | `rofi`, `wofi`, `fuzzel`, `dmenu`, `tofi`, `bemenu`, `wmenu` | (automatically chosen) | Show the selection dialog with this application. | | `--clipboarder` | | `xsel`, `xclip`, `wl-copy` | (automatically chosen) | Access the clipboard with this application. | -| `--typer` | | `xdotool`, `wtype` | (automatically chosen) | Type the characters using this application. | +| `--typer` | | `xdotool`, `ydotool`, `wtype` | (automatically chosen) | Type the characters using this application. | | `--keybinding-copy`, `--keybinding-type`, `--keybinding-clipboard`, `--keybinding-unicode`, `--keybinding-copy-unicode` | | | `Alt+c`, `Alt+t`, `Alt+p`, `Alt+u`, `Alt+i` | Choose different keybindings than the default values. | ## Example config file @@ -94,7 +94,7 @@ The options are: | `print` | | Print the chosen characters to `stdout`. | ## Insertion method -By default, `rofimoji` types the characters using either `xdotool` or `wtype` (see [display server support](#display-server-support)). You can enforce this behavior with `--action type` (`-a type`). +By default, `rofimoji` types the characters using either `xdotool`, `ydotool`, or `wtype` (see [display server support](#display-server-support)). You can enforce this behavior with `--action type` (`-a type`). For some applications (f.e. Firefox), this does not work reliably. To work around this, `rofimoji` can copy the emojis to your clipboard and insert them from there with `shift+insert`. Afterwards, it will restore the previous contents. Unfortunately, it depends on the receiving application whether `shift+insert` uses the clipboard or the primary selection. @@ -159,7 +159,7 @@ This also installs the python dependency `configargparse`. What else do you need: - Python 3.8 or higher - A font that can display your scripts, (for emojis, [EmojiOne](https://github.com/emojione/emojione) or [Noto Emoji](https://www.google.com/get/noto/) work) -- Optionally, a tool to programmatically type characters into applications. Either `xdotool` for X11 or `wtype` for Wayland +- Optionally, a tool to programmatically type characters into applications. Either `xdotool` for X11 or `wtype`/`ydotool` for Wayland - Optionally, a tool to copy the characters to the clipboard. `xsel` and `xclip` work on X11; `wl-copy` on Wayland ### Supported Selectors @@ -183,3 +183,45 @@ All other selectors can be used for the basic functionality. - [tofi](https://github.com/philj56/tofi) - [bemenu](https://github.com/Cloudef/bemenu) - [wmenu](https://git.sr.ht/~adnano/wmenu) + +# Troubleshooting +To troubleshoot, run `rofimoji` in the command line and inspect its output. + +Below is a list of error messages one can get with the typer and other components. +## Typers +> `Compositor does not support the virtual keyboard protocol` + +This is an error thrown by `wtype` to indicate that the virtual keyboard protocol is not implemented. You likely use KDE Plasma or Gnome, who don't implement that protocol. Consider using a different typer such as `ydotool` or `xdotool` by passing in the `--typer` argument. + +> ``` +> failed to connect socket `/run/user/1000/.ydotool_socket': Connection refused +> Please check if ydotoold is running +> ``` + +This is an error thrown by `ydotool` to indicate it cannot find its service daemon. `ydotool` needs a daemon called `ydotoold` running in the background in order to be able to type things. You can start the `ydotoold` daemon with one of two options: + +1. Create a `systemd` service (recommended) + + Create the file `/etc/systemd/system/ydotoold.service`and put the following in it: + ```ini + [Unit] + Description=Starts ydotoold Daemon + + [Service] + Type=simple + Restart=always + RestartSec=3 + ExecStartPre=/bin/sleep 2 + ExecStart=/usr/bin/ydotoold --socket-path="/run/user/1000/.ydotool_socket" --socket-own="$(id -u):$(id -g)" + ExecReload=/usr/bin/kill -HUP $MAINPID + KillMode=process + TimeoutSec=180 + + [Install] + WantedBy=basic.target + ``` + Then run `sudo systemctl enable ydotoold.service && sudo systemctl start ydotoold.service` and rerun `rofimoji`. + +2. Run `ydotoold` on its own (*not recommended!*) + + Run `ydotoold &` (with or without sudo) and then run `rofimoji`. If it has trouble finding the socket, set the environment variable `YDOTOOL_SOCKET` when running `rofimoji` diff --git a/src/picker/abstractionhelper.py b/src/picker/abstractionhelper.py index 701a81d..bf78ede 100644 --- a/src/picker/abstractionhelper.py +++ b/src/picker/abstractionhelper.py @@ -5,6 +5,5 @@ def is_installed(executable: str) -> bool: return shutil.which(executable) is not None - def is_wayland() -> bool: - return "WAYLAND_DISPLAY" in os.environ + return "WAYLAND_DISPLAY" in os.environ \ No newline at end of file diff --git a/src/picker/action.py b/src/picker/action.py index 3427dd5..022fb8b 100644 --- a/src/picker/action.py +++ b/src/picker/action.py @@ -3,6 +3,7 @@ from .clipboarder.clipboarder import Clipboarder from .models import Action from .typer.typer import Typer +from .input_event_codes import keycodes def execute_action( @@ -32,3 +33,6 @@ def execute_action( def __get_codepoints(char: str) -> str: return "-".join(f"{ord(c):x}" for c in char) + +def __get_event_code(char: str) -> str: + return str(keycodes["KEY_" + char.upper()]) \ No newline at end of file diff --git a/src/picker/argument_parsing.py b/src/picker/argument_parsing.py index cb6cfc5..db0a716 100644 --- a/src/picker/argument_parsing.py +++ b/src/picker/argument_parsing.py @@ -66,7 +66,7 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace: metavar="FILE", help="Read characters from this file instead, one entry per line", ) - parser.add_argument("--prompt", "-r", dest="prompt", action="store", default="😀 ", help="Set rofimoj's prompt") + parser.add_argument("--prompt", "-r", dest="prompt", action="store", default="😀 ", help="Set rofimoji's prompt") parser.add_argument( "--selector-args", dest="selector_args", @@ -97,14 +97,14 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace: "--hidden-descriptions", dest="show_description", action="store_false", - help="Show only the character without its description", + help="Show only the character without its description (Rofi only)", ) parser.set_defaults(show_description=True) parser.add_argument( "--use-icons", dest="use_icons", action="store_true", - help="Use rofi's icon to show the character", + help="Use rofi's icon to show the character (Rofi only)", ) parser.set_defaults(use_icons=False) parser.add_argument( @@ -130,7 +130,7 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace: dest="typer", action="store", type=str, - choices=["xdotool", "wtype"], + choices=["xdotool", "ydotool", "wtype"], default=None, help="Choose the application to type with", ) diff --git a/src/picker/input_event_codes.py b/src/picker/input_event_codes.py new file mode 100644 index 0000000..dc0e8c7 --- /dev/null +++ b/src/picker/input_event_codes.py @@ -0,0 +1,88 @@ +# Consulted file /usr/include/linux/input-event-codes.h for the event codes +keycodes = { +"KEY_RESERVED":0, +"KEY_ESC":1, +"KEY_1":2, +"KEY_2":3, +"KEY_3":4, +"KEY_4":5, +"KEY_5":6, +"KEY_6":7, +"KEY_7":8, +"KEY_8":9, +"KEY_9":10, +"KEY_0":11, +"KEY_MINUS":12, +"KEY_EQUAL":13, +"KEY_BACKSPACE":14, +"KEY_TAB":15, +"KEY_Q":16, +"KEY_W":17, +"KEY_E":18, +"KEY_R":19, +"KEY_T":20, +"KEY_Y":21, +"KEY_U":22, +"KEY_I":23, +"KEY_O":24, +"KEY_P":25, +"KEY_LEFTBRACE":26, +"KEY_RIGHTBRACE":27, +"KEY_ENTER":28, +"KEY_LEFTCTRL":29, +"KEY_A":30, +"KEY_S":31, +"KEY_D":32, +"KEY_F":33, +"KEY_G":34, +"KEY_H":35, +"KEY_J":36, +"KEY_K":37, +"KEY_L":38, +"KEY_SEMICOLON":39, +"KEY_APOSTROPHE":40, +"KEY_GRAVE":41, +"KEY_LEFTSHIFT":42, +"KEY_BACKSLASH":43, +"KEY_Z":44, +"KEY_X":45, +"KEY_C":46, +"KEY_V":47, +"KEY_B":48, +"KEY_N":49, +"KEY_M":50, +"KEY_COMMA":51, +"KEY_DOT":52, +"KEY_SLASH":53, +"KEY_RIGHTSHIFT":54, +"KEY_KPASTERISK":55, +"KEY_LEFTALT":56, +"KEY_SPACE":57, +"KEY_CAPSLOCK":58, +"KEY_F1":59, +"KEY_F2":60, +"KEY_F3":61, +"KEY_F4":62, +"KEY_F5":63, +"KEY_F6":64, +"KEY_F7":65, +"KEY_F8":66, +"KEY_F9":67, +"KEY_F10":68, +"KEY_NUMLOCK":69, +"KEY_SCROLLLOCK":70, +"KEY_KP7":71, +"KEY_KP8":72, +"KEY_KP9":73, +"KEY_KPMINUS":74, +"KEY_KP4":75, +"KEY_KP5":76, +"KEY_KP6":77, +"KEY_KPPLUS":78, +"KEY_KP1":79, +"KEY_KP2":80, +"KEY_KP3":81, +"KEY_KP0":82, +"KEY_KPDOT":83, +"KEY_INSERT": 110, +} \ No newline at end of file diff --git a/src/picker/typer/typer.py b/src/picker/typer/typer.py index 91ce1a5..cdc4460 100644 --- a/src/picker/typer/typer.py +++ b/src/picker/typer/typer.py @@ -8,8 +8,9 @@ def best_option(name: Optional[str] = None) -> "Typer": from .noop import NoopTyper from .wtype import WTypeTyper from .xdotool import XDoToolTyper + from .ydotool import YdotoolTyper as YDoToolTyper - available_typers = [XDoToolTyper, WTypeTyper, NoopTyper] + available_typers = [XDoToolTyper, YDoToolTyper, WTypeTyper, NoopTyper] if name is not None: return next(typer for typer in available_typers if typer.name() == name)() diff --git a/src/picker/typer/wtype.py b/src/picker/typer/wtype.py index 30811d5..a3434d8 100644 --- a/src/picker/typer/wtype.py +++ b/src/picker/typer/wtype.py @@ -8,7 +8,7 @@ class WTypeTyper(Typer): @staticmethod def supported() -> bool: return is_wayland() and is_installed("wtype") - + @staticmethod def name() -> str: return "wtype" diff --git a/src/picker/typer/ydotool.py b/src/picker/typer/ydotool.py new file mode 100644 index 0000000..3ac40dd --- /dev/null +++ b/src/picker/typer/ydotool.py @@ -0,0 +1,49 @@ +from subprocess import run + +from ..abstractionhelper import is_installed +from ..action import __get_codepoints as get_codepoints, __get_event_code as get_event_code +from .typer import Typer + +class YdotoolTyper(Typer): + @staticmethod + def name(): + return "ydotool" + + @staticmethod + def supported(): + return is_installed("ydotool") + + def get_active_window(self): + return "not possible with ydotool" + + def type_characters(self, characters: str, active_window: str) -> None: + # characters is assumed to be a string of emojis; for each emoji, + # get the unicode code point, then for each char in the unicode code point, + # get the event code for the char, then send the event code to ydotool + for character in characters: + # Get the unicode code point for the emoji + unicode_code_point = get_codepoints(character) + + # Get keypresses for Ctrl, Shift, U, and the unicode code point + Ctrl = get_event_code("LeftCtrl") + ":1" + Shift = get_event_code("LeftShift") + ":1" + U_press = get_event_code("U") + ":1" + Ctrl_release = get_event_code("LeftCtrl") + ":0" + Shift_release = get_event_code("LeftShift") + ":0" + U_release = get_event_code("U") + ":0" + points = [] + + for point in unicode_code_point: + points.append(get_event_code(point) + ":1") + points.append(get_event_code(point) + ":0") + + # Send the event codes to ydotool + run(["ydotool", "key", Ctrl, Shift, U_press, U_release] + points + [Shift_release, Ctrl_release]) + + def insert_from_clipboard(self, active_window: str) -> None: + Shift = get_event_code("LeftShift") + ":1" + Shift_release = get_event_code("LeftShift") + ":0" + Insert = get_event_code("Insert") + ":1" + Insert_release = get_event_code("Insert") + ":0" + + run(["ydotool", "key", Shift, Insert, Insert_release, Shift_release])