mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Add a Mac as the third system, beside Wayland and X11
Dikte already chose its clipboard programs once instead of in every function; macOS joins that table rather than adding a branch to each one. A Mac copies through pbcopy and presses Cmd+V straight into CoreGraphics, records through AVFoundation, and asks Carbon for its global shortcuts. The three tables are paste.Desktop, audio.Sound, and the pair of predicates in hotkey.py. Each reads sys.platform inside the chooser, so a test can stand somewhere else: 697 of the 737 tests now run on any machine, the Wayland and X11 halves included, and the suite passes whole whichever system it is run on. Two things a Mac does not have needed saying rather than pretending: there is no shortcut registry to install into, so Settings offers no Install button and the listener is the mechanism instead of a fallback; and nothing is offered as the sound the speakers are playing, so a meeting needs BlackHole or Loopback and says so. The KDE-only labels around them were already wrong on GNOME, and now name whichever desktop is there. Co-authored-by: firat <[email protected]>
This commit is contained in:
+235
-1
@@ -123,10 +123,13 @@ class Bindings(DikteTest):
|
||||
self.assertEqual(len(listener._bindings[57]), 2)
|
||||
|
||||
|
||||
@linux_only
|
||||
class Chooser(DikteTest):
|
||||
"""Which desktop is asked to register the shortcut."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_attr(hotkey.sys, "platform", "linux")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def under(self, desktop, has_gsettings=True):
|
||||
"""A session that says it is this desktop, with or without gsettings."""
|
||||
@@ -407,5 +410,236 @@ class KdeShortcut(DikteTest):
|
||||
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
|
||||
|
||||
|
||||
# --- macOS ----------------------------------------------------------------
|
||||
|
||||
class ParseMacShortcut(unittest.TestCase):
|
||||
def test_a_combination_a_mac_would_use(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+Space"),
|
||||
(hotkey.MAC_MODS["cmd"], 49))
|
||||
|
||||
def test_case_and_spacing_do_not_matter(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(" ctrl + option + a "),
|
||||
hotkey.parse_macos_shortcut("Ctrl+Option+A"))
|
||||
|
||||
def test_several_modifiers_are_one_number(self):
|
||||
modifiers, key = hotkey.parse_macos_shortcut("Cmd+Shift+M")
|
||||
self.assertEqual(modifiers,
|
||||
hotkey.MAC_MODS["cmd"] | hotkey.MAC_MODS["shift"])
|
||||
self.assertEqual(key, hotkey.MAC_KEYS["m"])
|
||||
|
||||
def test_the_names_a_mac_keyboard_uses(self):
|
||||
for name in ("cmd", "command", "meta", "super"):
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(f"{name}+space"),
|
||||
hotkey.parse_macos_shortcut("cmd+space"))
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("option+a"),
|
||||
hotkey.parse_macos_shortcut("alt+a"))
|
||||
|
||||
def test_a_key_on_its_own(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("F5"), (0, 96))
|
||||
|
||||
def test_modifiers_with_no_key(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+Shift"), (None, None))
|
||||
|
||||
def test_a_key_nobody_mapped(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+F13"), (None, None))
|
||||
|
||||
def test_two_keys_are_not_a_shortcut(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("A+B"), (None, None))
|
||||
|
||||
def test_nothing(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(""), (None, None))
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(None), (None, None))
|
||||
|
||||
|
||||
class FakeCarbon:
|
||||
"""Enough of Carbon to watch what the listener asks it for.
|
||||
|
||||
The references are numbers standing in for pointers, which is all the code
|
||||
does with them: it collects them and hands them back to be unregistered.
|
||||
"""
|
||||
|
||||
def __init__(self, install=0, register=0):
|
||||
self.install, self.register = install, register # what they return
|
||||
self.registered = [] # (key, modifiers, identifier)
|
||||
self.unregistered = []
|
||||
self.handlers_removed = 0
|
||||
self.pressed_id = 0
|
||||
self.parameter_result = 0
|
||||
|
||||
def GetApplicationEventTarget(self):
|
||||
return 7000
|
||||
|
||||
def InstallEventHandler(self, target, callback, count, spec, data, out):
|
||||
if self.install == 0:
|
||||
out._obj.value = 8000
|
||||
return self.install
|
||||
|
||||
def RegisterEventHotKey(self, key, modifiers, identifier, target, options, out):
|
||||
if self.register != 0:
|
||||
return self.register
|
||||
self.registered.append((key, modifiers, identifier.id))
|
||||
out._obj.value = 9000 + len(self.registered)
|
||||
return 0
|
||||
|
||||
def UnregisterEventHotKey(self, reference):
|
||||
self.unregistered.append(reference.value)
|
||||
return 0
|
||||
|
||||
def RemoveEventHandler(self, handler):
|
||||
self.handlers_removed += 1
|
||||
return 0
|
||||
|
||||
def GetEventParameter(self, event, kind, name, wanted_type, size, out_size, out):
|
||||
out._obj.id = self.pressed_id
|
||||
return self.parameter_result
|
||||
|
||||
|
||||
class CarbonListener(DikteTest):
|
||||
"""What the listener asks macOS for, without a Mac to ask."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.carbon = FakeCarbon()
|
||||
self.patch_attr(hotkey, "_carbon", lambda: self.carbon)
|
||||
self.addCleanup(hotkey._REGISTERED.clear)
|
||||
self.listener = hotkey.CarbonHotkey()
|
||||
self.addCleanup(self.listener.stop)
|
||||
self.failures = []
|
||||
self.listener.failed.connect(self.failures.append)
|
||||
|
||||
def test_every_binding_is_asked_for_by_position_and_modifier(self):
|
||||
self.assertTrue(self.listener.start({"toggle": "Ctrl+Option+Space"}))
|
||||
self.assertEqual(self.carbon.registered,
|
||||
[(49, hotkey.MAC_MODS["ctrl"] | hotkey.MAC_MODS["option"], 1)])
|
||||
self.assertEqual(self.failures, [])
|
||||
self.assertTrue(self.listener.running)
|
||||
|
||||
def test_a_binding_with_no_shortcut_is_skipped(self):
|
||||
self.assertFalse(self.listener.start({"toggle": "", "ask": ""}))
|
||||
self.assertEqual(self.carbon.registered, [])
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_an_unparsable_shortcut_is_reported_and_the_rest_go_on(self):
|
||||
self.assertTrue(self.listener.start({"toggle": "Cmd+F13",
|
||||
"ask": "Cmd+Shift+Space"}))
|
||||
self.assertEqual(len(self.failures), 1)
|
||||
self.assertIn("Cmd+F13", self.failures[0])
|
||||
self.assertEqual(len(self.carbon.registered), 1)
|
||||
|
||||
def test_a_combination_another_application_already_holds(self):
|
||||
"""The conflict warning macOS has: it is the answer to asking."""
|
||||
self.carbon.register = -9878 # eventHotKeyExistsErr
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertIn("Cmd+Shift+Space", self.failures[0])
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_a_handler_that_will_not_install(self):
|
||||
self.carbon.install = -50
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertEqual(self.carbon.registered, [])
|
||||
self.assertEqual(len(self.failures), 1)
|
||||
|
||||
def test_no_carbon_to_talk_to(self):
|
||||
self.patch_attr(hotkey, "_carbon",
|
||||
mock.Mock(side_effect=OSError("no such library")))
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertIn("no such library", self.failures[0])
|
||||
|
||||
def test_the_key_press_arrives_under_the_name_it_was_registered_with(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space", "ask": "Cmd+Shift+A"})
|
||||
heard = []
|
||||
self.listener.triggered.connect(heard.append)
|
||||
self.carbon.pressed_id = 2 # the second binding, which is "ask"
|
||||
self.listener._callback(None, 0, None)
|
||||
self.assertEqual(heard, ["ask"])
|
||||
|
||||
def test_a_press_carbon_could_not_identify_is_dropped(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space"})
|
||||
heard = []
|
||||
self.listener.triggered.connect(heard.append)
|
||||
self.carbon.parameter_result = -50
|
||||
self.listener._callback(None, 0, None)
|
||||
self.assertEqual(heard, [])
|
||||
|
||||
def test_stopping_hands_every_registration_back(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space", "ask": "Cmd+Shift+A"})
|
||||
self.listener.stop()
|
||||
self.assertEqual(self.carbon.unregistered, [9001, 9002])
|
||||
self.assertEqual(self.carbon.handlers_removed, 1)
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_starting_twice_does_not_leave_the_first_set_behind(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space"})
|
||||
self.listener.start({"toggle": "Cmd+Shift+A"})
|
||||
self.assertEqual(self.carbon.unregistered, [9001])
|
||||
self.assertEqual(len(self.listener._registrations), 1)
|
||||
|
||||
def test_what_it_registered_is_what_the_status_line_reads_back(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "darwin"):
|
||||
self.listener.start({"toggle": "Ctrl+Option+Space",
|
||||
"meeting": "Ctrl+Option+M"})
|
||||
self.assertEqual(hotkey.shortcut_status(), "Ctrl+Option+Space")
|
||||
self.assertEqual(hotkey.shortcut_status(hotkey.MEETING_DESKTOP_ID),
|
||||
"Ctrl+Option+M")
|
||||
self.listener.stop()
|
||||
self.assertIsNone(hotkey.shortcut_status())
|
||||
|
||||
|
||||
class MacChooser(DikteTest):
|
||||
"""What the shortcut verbs mean where there is nothing to write them into."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_attr(hotkey.sys, "platform", "darwin")
|
||||
self.addCleanup(hotkey._REGISTERED.clear)
|
||||
|
||||
def test_the_listener_is_the_one_macos_has(self):
|
||||
self.assertIsInstance(hotkey.listener(), hotkey.CarbonHotkey)
|
||||
|
||||
def test_everywhere_else_reads_the_keyboard_itself(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "linux"):
|
||||
self.assertIsInstance(hotkey.listener(), hotkey.EvdevHotkey)
|
||||
|
||||
def test_there_is_nothing_to_install_into(self):
|
||||
self.assertFalse(hotkey.installs_shortcuts())
|
||||
self.assertFalse(hotkey.shortcut_needs_restart())
|
||||
self.assertEqual(hotkey.desktop_name(), "macOS")
|
||||
|
||||
def test_installing_records_it_rather_than_writing_anything(self):
|
||||
with mock.patch.object(hotkey.subprocess, "run") as run:
|
||||
ok, message = hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
run.assert_not_called()
|
||||
self.assertTrue(ok)
|
||||
self.assertIn("Cmd+Shift+Space", message)
|
||||
self.assertEqual(hotkey.shortcut_status(), "Cmd+Shift+Space")
|
||||
|
||||
def test_removing_takes_it_back_out(self):
|
||||
hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
hotkey.remove_shortcut()
|
||||
self.assertIsNone(hotkey.shortcut_status())
|
||||
|
||||
def test_each_verb_is_kept_apart(self):
|
||||
hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
hotkey.install_shortcut("Cmd+Shift+M", "dikte meeting",
|
||||
desktop_id=hotkey.MEETING_DESKTOP_ID)
|
||||
self.assertEqual(hotkey.shortcut_status(), "Cmd+Shift+Space")
|
||||
self.assertEqual(hotkey.shortcut_status(hotkey.MEETING_DESKTOP_ID),
|
||||
"Cmd+Shift+M")
|
||||
|
||||
def test_no_list_of_conflicts_to_read(self):
|
||||
"""Not even KDE's file, which a Mac could well have a copy of."""
|
||||
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
|
||||
|
||||
def test_a_combination_is_checked_against_the_mac_table(self):
|
||||
self.assertTrue(hotkey.valid_shortcut("Cmd+Shift+Space"))
|
||||
self.assertFalse(hotkey.valid_shortcut("Ctrl+F13"))
|
||||
|
||||
def test_the_other_table_is_the_one_used_elsewhere(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "linux"):
|
||||
self.assertTrue(hotkey.valid_shortcut("Ctrl+F1"))
|
||||
self.assertFalse(hotkey.valid_shortcut("Cmd+Space"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user