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:
yusufipk
2026-08-01 21:28:58 +07:00
co-authored by firat
parent 676664ea74
commit 3bd8c1ad27
16 changed files with 1558 additions and 207 deletions
+21 -5
View File
@@ -16,6 +16,15 @@ import sys
if os.environ.get("XDG_SESSION_TYPE") == "wayland" and os.environ.get("DISPLAY"):
os.environ.setdefault("QT_QPA_PLATFORM", "xcb")
# An application started from the Finder is given none of the shell's PATH, so
# Homebrew's ffmpeg is invisible to it. Put the two places brew installs to in
# front, before anything goes looking for a program.
if sys.platform == "darwin":
os.environ["PATH"] = os.pathsep.join(
part for part in ("/opt/homebrew/bin", "/usr/local/bin",
os.environ.get("PATH", "")) if part
)
from PyQt6.QtCore import QTimer, QElapsedTimer # noqa: E402
from PyQt6.QtGui import QAction, QIcon # noqa: E402
from PyQt6.QtNetwork import QLocalServer, QLocalSocket # noqa: E402
@@ -91,7 +100,7 @@ class Dikte:
self.ask_pipeline = Pipeline(self.conf)
self.meeting_recorder = audio.MeetingRecorder()
self.meetings = MeetingPipeline(self.conf)
self.evdev = hotkey.EvdevHotkey()
self.evdev = hotkey.listener()
self.recorder.level.connect(self._on_level)
self.recorder.stopped.connect(self._on_recorded)
@@ -312,8 +321,11 @@ class Dikte:
# that same press. Its lateness is also the proof we were waiting for
# that the shortcut is live, which leaves the listener with nothing to
# do but double every press.
# Where nothing was installed there is no shortcut to catch up, and
# retiring the listener would leave the keys with nowhere to arrive.
timer = self.last_evdev.get(name)
if self.evdev.running and timer is not None and timer.elapsed() < ECHO_MS:
if (hotkey.installs_shortcuts() and self.evdev.running
and timer is not None and timer.elapsed() < ECHO_MS):
self._retire_listener()
return
handler()
@@ -332,8 +344,9 @@ class Dikte:
self.conf.save()
self.tray.showMessage(
"Dikte",
t("The KDE shortcut is live now, so the built-in listener has been "
"turned off. It was doubling every key press."),
t("The {desktop} shortcut is live now, so the built-in listener has "
"been turned off. It was doubling every key press.",
desktop=hotkey.desktop_name()),
QSystemTrayIcon.MessageIcon.Information, 8000,
)
@@ -807,7 +820,10 @@ class Dikte:
self.ask_overlay.corner = self.conf["overlay_corner"]
self._build_tray()
self._refresh_tray()
if self.conf["evdev_hotkey"]:
# Where the desktop has no shortcut registry of its own, the listener is
# not the fallback the setting offers to turn on: it is the only way the
# keys arrive at all, so it runs whatever the setting says.
if self.conf["evdev_hotkey"] or not hotkey.installs_shortcuts():
self.evdev.start({"toggle": self.conf["shortcut"],
"ask": self.conf["assistant_shortcut"],
"meeting": self.conf["meeting_shortcut"]})