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])