Skip to content

Commit c6c8647

Browse files
committed
Add basic MacOS support
* Add choose as rofi alternative * Support pbcopy/pbpaste backend for clipboarder * Add cliclick support as typer * Update documentation Implements: #223
1 parent ef54acd commit c6c8647

9 files changed

Lines changed: 153 additions & 9 deletions

File tree

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ You can configure `rofimoji` either with cli arguments or with a config file cal
6666
| `--use-icons` | | | `false` | Show characters as icons in `rofi`. Not used for other selectors. |
6767
| `--prompt` | `-r` | any string | `😀 ` | Define the prompt text for `rofimoji`. |
6868
| `--selector-args` | | | | Define arguments that `rofimoji` will pass through to the selector.<br/>Please note that you need to specify it as `--selector-args="<selector-args>"` or `--selector-args " <selector-args>"` because of a [bug in argparse](https://bugs.python.org/issue9334) |
69-
| `--selector` | | `rofi`, `wofi`, `fuzzel`, `dmenu`, `tofi`, `bemenu`, `wmenu` | (automatically chosen) | Show the selection dialog with this application. |
70-
| `--clipboarder` | | `xsel`, `xclip`, `wl-copy` | (automatically chosen) | Access the clipboard with this application. |
71-
| `--typer` | | `xdotool`, `wtype`, `ydotool` | (automatically chosen) | Type the characters using this application. |
69+
| `--selector` | | `rofi`, `wofi`, `fuzzel`, `dmenu`, `tofi`, `bemenu`, `wmenu`, `choose` | (automatically chosen) | Show the selection dialog with this application. |
70+
| `--clipboarder` | | `xsel`, `xclip`, `wl-copy`, `pbcopy` | (automatically chosen) | Access the clipboard with this application. |
71+
| `--typer` | | `xdotool`, `wtype`, `ydotool`, `cliclick` | (automatically chosen) | Type the characters using this application. |
7272
| `--keybinding-copy`, `--keybinding-type`, `--keybinding-clipboard`, `--keybinding-type-numerical`, `--keybinding-unicode`, `--keybinding-copy-unicode` | | | `Alt+c`, `Alt+t`, `Alt+p`, `Alt+n`, `Alt+u`, `Alt+i` | Choose different keybindings than the default values. |
7373

7474
## Example config file
@@ -109,6 +109,8 @@ Finally, with `--action copy` (or `-a copy`) you can also tell `rofimoji` to onl
109109
`rofimoji` supports both X11 and Wayland by using the correct tools for each environment (see [Supported Selectors](#supported-selectors)). It tries to automatically choose the right one for the currently running session.
110110
If you want to manually overwrite this, have a look at the `--selector`, `--clipboarder` and `--typer` options [above](#options).
111111

112+
`rofimoji` also supports MacOS in a best-effort fashion (see below)
113+
112114
## Most recently used characters
113115
By default, `rofimoji` will show the last ten recently used characters separately; you can insert them with `alt+1`, `alt+2` and so on. It will use the default [insertion method](#insertion-method).
114116
If you don't want this, you can set `--max-recent` to `0`.
@@ -163,6 +165,15 @@ What else do you need:
163165
- Optionally, a tool to programmatically type characters into applications. Either `xdotool` for X11 or `wtype`/`ydotool` for Wayland
164166
- Optionally, a tool to copy the characters to the clipboard. `xsel` and `xclip` work on X11; `wl-copy` on Wayland
165167

168+
### MacOS support
169+
170+
A light support of MacOS was brought to `rofimoji` through:
171+
- `pbcopy` / `pbpaste` to manage the clipboard
172+
- `choose` as selector alternative to `rofi`
173+
- `cliclick` as typer alternative to `xdotool`
174+
175+
Support is for now limited and `type`, `type-numerical` and `unicode` are not supported yet.
176+
166177
### Supported Selectors
167178
Please note that several advanced features are only supported by `rofi` (both on X and Wayland):
168179
- custom keyboard shortcuts
@@ -184,3 +195,6 @@ All other selectors can be used for the basic functionality.
184195
- [tofi](https://github.com/philj56/tofi)
185196
- [bemenu](https://github.com/Cloudef/bemenu)
186197
- [wmenu](https://git.sr.ht/~adnano/wmenu)
198+
199+
#### MacOS
200+
- [choose](https://github.com/chipsenkbeil/choose)

src/picker/abstractionhelper.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import os
2+
import platform
23
import shutil
34

45

56
def is_installed(executable: str) -> bool:
67
return shutil.which(executable) is not None
78

89

10+
def is_macos() -> bool:
11+
return platform.system() == "Darwin"
12+
13+
914
def is_wayland() -> bool:
1015
return "WAYLAND_DISPLAY" in os.environ

src/picker/argument_parsing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace:
112112
dest="selector",
113113
action="store",
114114
type=str,
115-
choices=["rofi", "wofi", "fuzzel", "dmenu", "tofi", "bemenu", "wmenu"],
115+
choices=["rofi", "wofi", "fuzzel", "dmenu", "tofi", "bemenu", "wmenu", "choose"],
116116
default=None,
117117
help="Choose the application to select the characters with",
118118
)
@@ -121,7 +121,7 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace:
121121
dest="clipboarder",
122122
action="store",
123123
type=str,
124-
choices=["xsel", "xclip", "wl-copy"],
124+
choices=["xsel", "xclip", "wl-copy", "pbcopy"],
125125
default=None,
126126
help="Choose the application to access the clipboard with",
127127
)
@@ -130,7 +130,7 @@ def __parse_arguments(only_known: bool) -> argparse.Namespace:
130130
dest="typer",
131131
action="store",
132132
type=str,
133-
choices=["xdotool", "wtype", "ydotool"],
133+
choices=["xdotool", "wtype", "ydotool", "cliclick"],
134134
default=None,
135135
help="Choose the application to type with",
136136
)

src/picker/clipboarder/clipboarder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ def best_option(name: Optional[str] = None) -> "Clipboarder":
1111
from .wl import WlClipboarder
1212
from .xclip import XClipClipboarder
1313
from .xsel import XSelClipboarder
14+
from .pbcopy import PBCopyClipboarder
1415

15-
available_clipboarders = [XSelClipboarder, XClipClipboarder, WlClipboarder, NoopClipboarder]
16+
available_clipboarders = [XSelClipboarder, XClipClipboarder, WlClipboarder, PBCopyClipboarder, NoopClipboarder]
1617

1718
if name is not None:
1819
return next(clipboarder for clipboarder in available_clipboarders if clipboarder.name() == name)()

src/picker/clipboarder/pbcopy.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from subprocess import run
2+
3+
from ..abstractionhelper import is_installed, is_macos
4+
from ..typer.typer import Typer
5+
from .clipboarder import Clipboarder
6+
7+
8+
class PBCopyClipboarder(Clipboarder):
9+
@staticmethod
10+
def supported() -> bool:
11+
return is_installed("pbcopy")
12+
13+
@staticmethod
14+
def name() -> str:
15+
return "pbcopy"
16+
17+
def copy_characters_to_clipboard(self, characters: str) -> None:
18+
run(["pbcopy"], input=characters, encoding="utf-8")
19+
20+
def copy_paste_characters(self, characters: str, active_window: str, typer: Typer) -> None:
21+
old_clipboard_content = run(args=["pbpaste"], capture_output=True, encoding="utf-8")
22+
23+
run(args=["pbcopy"], input=characters, encoding="utf-8")
24+
25+
typer.insert_from_clipboard(active_window)
26+
27+
if old_clipboard_content.returncode == 0:
28+
run(args=["pbcopy"], input=old_clipboard_content.stdout, encoding="utf-8")

src/picker/selector/choose.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from subprocess import run
2+
from typing import Dict, List, Tuple, Union
3+
4+
from ..abstractionhelper import is_installed, is_macos
5+
from ..models import CANCEL, DEFAULT, Action, CharacterEntry, Shortcut
6+
from .selector import Selector
7+
8+
9+
class Choose(Selector):
10+
@staticmethod
11+
def supported() -> bool:
12+
return is_macos() and is_installed("choose")
13+
14+
@staticmethod
15+
def name() -> str:
16+
return "choose"
17+
18+
def show_character_selection(
19+
self,
20+
characters: List[CharacterEntry],
21+
recent_characters: List[str],
22+
prompt: str,
23+
show_description: bool,
24+
use_icons: bool,
25+
keybindings: Dict[Action, str],
26+
additional_args: List[str],
27+
) -> Tuple[Union[Action, DEFAULT, CANCEL], Union[List[str], Shortcut]]:
28+
parameters = ["choose", "-p", prompt, *additional_args]
29+
30+
choose = run(
31+
parameters, input="\n".join(self.basic_format_characters(characters)), capture_output=True, encoding="utf-8"
32+
)
33+
return DEFAULT(), [self.extract_char_from_basic_output(line) for line in choose.stdout.splitlines()]
34+
35+
def show_skin_tone_selection(
36+
self, tones_emojis: List[str], prompt: str, additional_args: List[str]
37+
) -> Tuple[int, str]:
38+
choose = run(
39+
["choose", "-p", prompt, *additional_args],
40+
input="\n".join(tones_emojis),
41+
capture_output=True,
42+
encoding="utf-8",
43+
)
44+
45+
return choose.returncode, choose.stdout
46+
47+
def show_action_menu(self, additional_args: List[str]) -> List[Action]:
48+
choose = run(
49+
[
50+
"choose",
51+
*additional_args,
52+
],
53+
input="\n".join([it.value for it in Action if it != Action.MENU]),
54+
capture_output=True,
55+
encoding="utf-8",
56+
)
57+
58+
return [Action(choose.stdout.strip())]

src/picker/selector/selector.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def best_option(name: Optional[str] = None) -> "Selector":
1515
from .tofi import Tofi
1616
from .wmenu import WMenu
1717
from .wofi import Wofi
18+
from .choose import Choose
1819

19-
available_selectors = [Rofi, Wofi, Fuzzel, Bemenu, Tofi, DMenu, WMenu]
20+
available_selectors = [Rofi, Wofi, Fuzzel, Bemenu, Tofi, DMenu, WMenu, Choose]
2021

2122
if name is not None:
2223
try:

src/picker/typer/cliclick.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from subprocess import run
2+
from typing import List
3+
4+
from ..abstractionhelper import is_installed, is_wayland, is_macos
5+
from .typer import Typer
6+
7+
8+
class CliclickTyper(Typer):
9+
@staticmethod
10+
def supported() -> bool:
11+
return is_installed("cliclick")
12+
13+
@staticmethod
14+
def name() -> str:
15+
return "cliclick"
16+
17+
def get_active_window(self) -> str:
18+
return ""
19+
20+
def type_characters(self, characters: str, active_window: str) -> None:
21+
pass
22+
23+
24+
def insert_from_clipboard(self, active_window: str) -> None:
25+
run(
26+
[
27+
"cliclick",
28+
"-w 100",
29+
"kd:cmd",
30+
"t:v",
31+
"ku:cmd"
32+
]
33+
)
34+
35+
def type_numerical(self, codepoints: List[int], active_window: str) -> None:
36+
pass

src/picker/typer/typer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ def best_option(name: Optional[str] = None) -> "Typer":
99
from .wtype import WTypeTyper
1010
from .xdotool import XDoToolTyper
1111
from .ydotool import YdotoolTyper as YDoToolTyper
12+
from .cliclick import CliclickTyper
1213

13-
available_typers = [XDoToolTyper, WTypeTyper, YDoToolTyper, NoopTyper]
14+
available_typers = [XDoToolTyper, WTypeTyper, YDoToolTyper, CliclickTyper, NoopTyper]
1415

1516
if name is not None:
1617
return next(typer for typer in available_typers if typer.name() == name)()

0 commit comments

Comments
 (0)