mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 19:06:11 +00:00
Merge master into the Windows port
Three of the four collisions were the same one: master moved the directory rule into paths.py while this branch was adding a Windows case to the copy in config.py and the second copy in ggml.py. The case moves to paths.py with the rest of it, and the directories test moves to tests/test_paths.py where master put its neighbours. The fourth is MeetingRecorder, which now starts a process per capture device. Windows keeps its two lines there: no console window for either process, and a stop that terminates rather than sending a signal the platform does not have.
This commit is contained in:
@@ -8,6 +8,8 @@ command line says "there is no instance to talk to, so be one".
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
@@ -44,6 +46,7 @@ import hotkey # noqa: E402
|
||||
import i18n # noqa: E402
|
||||
import ipc # noqa: E402
|
||||
import meeting # noqa: E402
|
||||
import trayicon # noqa: E402
|
||||
from i18n import t # noqa: E402
|
||||
from meeting import MeetingPipeline # noqa: E402
|
||||
from overlay import Overlay # noqa: E402
|
||||
@@ -124,6 +127,7 @@ class Dikte:
|
||||
self.meeting_recorder.levels.connect(self._on_meeting_levels)
|
||||
self.meeting_recorder.stopped.connect(self._on_meeting_recorded)
|
||||
self.meeting_recorder.died.connect(self._on_meeting_died)
|
||||
self.meeting_recorder.warned.connect(self._on_meeting_warning)
|
||||
self.meeting_recorder.failed.connect(self._on_meeting_error)
|
||||
self.meetings.progress.connect(self._on_meeting_progress)
|
||||
self.meetings.finished.connect(self._on_meeting_finished)
|
||||
@@ -219,7 +223,12 @@ class Dikte:
|
||||
self._toggle()
|
||||
|
||||
def _set_icon(self, name):
|
||||
# The theme first, so a Linux desktop keeps its own icons, then the ones
|
||||
# drawn in trayicon.py. macOS has no theme at all and would otherwise be
|
||||
# handed a null icon, which in a menu bar is an item you cannot see.
|
||||
icon = QIcon.fromTheme(name)
|
||||
if icon.isNull():
|
||||
icon = trayicon.icon(name)
|
||||
if icon.isNull():
|
||||
icon = QIcon.fromTheme("audio-input-microphone")
|
||||
self.tray.setIcon(icon)
|
||||
@@ -474,6 +483,12 @@ class Dikte:
|
||||
"agent": assistant.display_name(self.conf),
|
||||
"provider": assistant.provider(self.conf),
|
||||
"listener": self.evdev.running,
|
||||
# Asked here rather than by the command line, because on macOS
|
||||
# there is no registry to read: a combination is held by this
|
||||
# process and by nothing else, so this is the only process that
|
||||
# can say whether it is.
|
||||
"shortcuts": {name: hotkey.shortcut_status(spec.desktop_id)
|
||||
for name, spec in hotkey.SHORTCUTS.items()},
|
||||
}
|
||||
|
||||
def reload_settings(self):
|
||||
@@ -722,6 +737,11 @@ class Dikte:
|
||||
self._settle(MEETING, {"ok": False, "error": message})
|
||||
self._on_error(message)
|
||||
|
||||
def _on_meeting_warning(self, message):
|
||||
"""It was recorded and it is being written up, but read this first."""
|
||||
self.tray.showMessage("Dikte", message,
|
||||
QSystemTrayIcon.MessageIcon.Warning, 12000)
|
||||
|
||||
def _on_meeting_died(self):
|
||||
if self.meeting_state != M_RECORDING:
|
||||
return
|
||||
@@ -974,6 +994,51 @@ def install_signal_handlers(app):
|
||||
return reader, writer, notifier
|
||||
|
||||
|
||||
def _stay_out_of_the_dock():
|
||||
"""Ask macOS to treat this as a menu bar application, not a windowed one.
|
||||
|
||||
LSUIElement in the bundle says the same thing, but it is read for the
|
||||
process LaunchServices started, and that is the launcher script rather than
|
||||
the Python it runs: the interpreter is a child, and the child inherits the
|
||||
registration without inheriting the policy. Said here it holds however Dikte
|
||||
was started, including straight from a terminal.
|
||||
|
||||
Accessory rather than Prohibited: a prohibited application cannot put
|
||||
anything in the menu bar, which is the whole interface.
|
||||
"""
|
||||
if sys.platform != "darwin":
|
||||
return
|
||||
NS_ACCESSORY = 1 # NSApplicationActivationPolicyAccessory
|
||||
try:
|
||||
objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc"))
|
||||
objc.objc_getClass.restype = ctypes.c_void_p
|
||||
objc.objc_getClass.argtypes = [ctypes.c_char_p]
|
||||
objc.sel_registerName.restype = ctypes.c_void_p
|
||||
objc.sel_registerName.argtypes = [ctypes.c_char_p]
|
||||
# objc_msgSend is a trampoline with no signature of its own, and on
|
||||
# arm64 the arguments have to be in the registers the real method
|
||||
# expects, so each call gets a prototype of its own. Built from the
|
||||
# address: handing CFUNCTYPE the imported function object would make a
|
||||
# callback into it rather than a call through it, and the crash lands
|
||||
# inside the Objective-C runtime with nothing to read.
|
||||
send = ctypes.cast(objc.objc_msgSend, ctypes.c_void_p).value
|
||||
shared = ctypes.CFUNCTYPE(
|
||||
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p,
|
||||
)(send)
|
||||
policy = ctypes.CFUNCTYPE(
|
||||
ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long,
|
||||
)(send)
|
||||
|
||||
application = shared(objc.objc_getClass(b"NSApplication"),
|
||||
objc.sel_registerName(b"sharedApplication"))
|
||||
if application:
|
||||
policy(application, objc.sel_registerName(b"setActivationPolicy:"),
|
||||
NS_ACCESSORY)
|
||||
except (OSError, AttributeError, TypeError):
|
||||
# A Dock icon is a blemish, not a failure: everything still works.
|
||||
pass
|
||||
|
||||
|
||||
def run_app(args):
|
||||
command = args[0] if args else ""
|
||||
|
||||
@@ -981,6 +1046,7 @@ def run_app(args):
|
||||
app.setApplicationName("Dikte")
|
||||
app.setDesktopFileName("dikte")
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
_stay_out_of_the_dock()
|
||||
# Before Dikte is built, because building it is what may start a server, and
|
||||
# a signal arriving in the middle of that would otherwise take the default
|
||||
# action and leave the server behind. A signal this early lands in the
|
||||
|
||||
Reference in New Issue
Block a user