Skip to content

Commit

Permalink
Fix shortcut handling on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
vietanhdev committed Sep 30, 2024
1 parent 27183c8 commit 751e0a9
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 30 deletions.
11 changes: 10 additions & 1 deletion llama_assistant/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import json
from pathlib import Path


DEFAULT_LAUNCH_SHORTCUT = "<cmd>+<shift>+<space>"
DEFAULT_SETTINGS = {
"shortcut": DEFAULT_LAUNCH_SHORTCUT,
"color": "#1E1E1E",
"transparency": 95,
"text_model": "hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF",
"multimodal_model": "vikhyatk/moondream2",
"hey_llama_chat": False,
"hey_llama_mic": False,
}
DEFAULT_MODELS = [
{
"model_name": "Llama-3.2-1B-Instruct-Q4_K_M-GGUF",
Expand Down
9 changes: 8 additions & 1 deletion llama_assistant/global_hotkey.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from PyQt6.QtCore import QObject, pyqtSignal
from pynput import keyboard

from llama_assistant.config import DEFAULT_LAUNCH_SHORTCUT


class GlobalHotkey(QObject):
activated = pyqtSignal()

def __init__(self, hotkey):
super().__init__()
self.hotkey = keyboard.HotKey(keyboard.HotKey.parse(hotkey), self.on_activate)
try:
self.hotkey = keyboard.HotKey(keyboard.HotKey.parse(hotkey), self.on_activate)
except ValueError:
self.hotkey = keyboard.HotKey(
keyboard.HotKey.parse(DEFAULT_LAUNCH_SHORTCUT), self.on_activate
)
self.listener = keyboard.Listener(
on_press=self.for_canonical(self.hotkey.press),
on_release=self.for_canonical(self.hotkey.release),
Expand Down
63 changes: 47 additions & 16 deletions llama_assistant/llama_assistant.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import json
import copy
import time
from importlib import resources
from pathlib import Path
import traceback

from PyQt6.QtWidgets import (
QApplication,
Expand All @@ -14,6 +17,7 @@
QMenu,
QLabel,
QScrollArea,
QMessageBox,
)
from PyQt6.QtCore import (
Qt,
Expand All @@ -37,6 +41,7 @@
QTextCursor,
)

from llama_assistant import config
from llama_assistant.wake_word_detector import WakeWordDetector
from llama_assistant.custom_plaintext_editor import CustomPlainTextEdit
from llama_assistant.global_hotkey import GlobalHotkey
Expand Down Expand Up @@ -118,21 +123,13 @@ def load_settings(self):
with open(settings_file, "r") as f:
self.settings = json.load(f)
self.settings["text_model"] = self.settings.get(
"text_model", "hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF"
"text_model", config.DEFAULT_SETTINGS["text_model"]
)
self.settings["multimodal_model"] = self.settings.get(
"multimodal_model", "vikhyatk/moondream2"
"multimodal_model", config.DEFAULT_SETTINGS["multimodal_model"]
)
else:
self.settings = {
"shortcut": "<cmd>+<shift>+<space>",
"color": "#1E1E1E",
"transparency": 90,
"text_model": "hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF",
"multimodal_model": "vikhyatk/moondream2",
"hey_llama_chat": False,
"hey_llama_mic": False,
}
self.settings = copy.deepcopy(config.DEFAULT_SETTINGS)
self.save_settings()
if self.settings.get("hey_llama_chat", False) and self.wake_word_detector is None:
self.init_wake_word_detector()
Expand All @@ -142,10 +139,21 @@ def load_settings(self):
self.current_multimodal_model = self.settings.get("multimodal_model")

def setup_global_shortcut(self):
if hasattr(self, "global_hotkey"):
self.global_hotkey.stop()
self.global_hotkey = GlobalHotkey(self.settings["shortcut"])
self.global_hotkey.activated.connect(self.toggle_visibility)
try:
if hasattr(self, "global_hotkey"):
self.global_hotkey.stop()
time.sleep(0.1) # Give a short delay to ensure the previous listener has stopped
try:
self.global_hotkey = GlobalHotkey(self.settings["shortcut"])
self.global_hotkey.activated.connect(self.toggle_visibility)
except Exception as e:
print(f"Error setting up global shortcut: {e}")
# Fallback to default shortcut if there's an error
self.global_hotkey = GlobalHotkey(config.DEFAULT_LAUNCH_SHORTCUT)
self.global_hotkey.activated.connect(self.toggle_visibility)
except Exception as e:
print(f"Error setting up global shortcut: {e}")
traceback.print_exc()

def open_settings(self):
dialog = SettingsDialog(self)
Expand All @@ -158,7 +166,30 @@ def open_settings(self):
self.update_styles()

if old_shortcut != self.settings["shortcut"]:
self.setup_global_shortcut()
msg = QMessageBox()
msg.setIcon(QMessageBox.Icon.Information)
msg.setText("Global shortcut has been updated")
msg.setInformativeText(
"The changes will take effect after you restart the application."
)
msg.setWindowTitle("Restart Required")
msg.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
msg.button(QMessageBox.StandardButton.Yes).setText("Restart Now")
msg.button(QMessageBox.StandardButton.No).setText("Restart Later")
msg.setDefaultButton(QMessageBox.StandardButton.Yes)

result = msg.exec()

if result == QMessageBox.StandardButton.Yes:
self.restart_application()
else:
print("User chose to restart later.")

def restart_application(self):
QApplication.quit()
# The application will restart automatically because it is being run from a script

def save_settings(self):
home_dir = Path.home()
Expand Down
26 changes: 17 additions & 9 deletions llama_assistant/setting_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)
from PyQt6.QtCore import pyqtSignal, Qt
from PyQt6.QtGui import QColor
from pynput import keyboard

from llama_assistant.shortcut_recorder import ShortcutRecorder
from llama_assistant import config
Expand Down Expand Up @@ -156,7 +157,7 @@ def choose_color(self):
self.color = color

def reset_shortcut(self):
self.shortcut_recorder.setText("<cmd>+<shift>+<space>")
self.shortcut_recorder.setText(config.DEFAULT_LAUNCH_SHORTCUT)

def update_hey_llama_mic_state(self, state):
self.hey_llama_mic_checkbox.setEnabled(state == Qt.CheckState.Checked.value)
Expand All @@ -168,7 +169,12 @@ def load_settings(self):
if settings_file.exists():
with open(settings_file, "r") as f:
settings = json.load(f)
self.shortcut_recorder.setText(settings.get("shortcut", "<cmd>+<shift>+<space>"))
try:
keyboard.HotKey(keyboard.HotKey.parse(settings["shortcut"]), lambda: None)
except ValueError:
settings["shortcut"] = config.DEFAULT_LAUNCH_SHORTCUT
self.save_settings(settings)
self.shortcut_recorder.setText(settings.get("shortcut", config.DEFAULT_LAUNCH_SHORTCUT))
self.color = QColor(settings.get("color", "#1E1E1E"))
self.transparency_slider.setValue(int(settings.get("transparency", 90)))

Expand Down Expand Up @@ -198,15 +204,17 @@ def get_settings(self):
"hey_llama_mic": self.hey_llama_mic_checkbox.isChecked(),
}

def save_settings(self):
home_dir = Path.home()
settings_dir = home_dir / "llama_assistant"
settings_file = settings_dir / "settings.json"
def save_settings(self, settings=None):
if settings is None:
home_dir = Path.home()
settings_dir = home_dir / "llama_assistant"
settings_file = settings_dir / "settings.json"

if not settings_dir.exists():
settings_dir.mkdir(parents=True)

if not settings_dir.exists():
settings_dir.mkdir(parents=True)
settings = self.get_settings()

settings = self.get_settings()
with open(settings_file, "w") as f:
json.dump(settings, f)

Expand Down
33 changes: 31 additions & 2 deletions llama_assistant/shortcut_recorder.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
import sys

from PyQt6.QtWidgets import QLineEdit
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QKeyEvent, QKeySequence


def is_macos():
return sys.platform == "darwin"


class ShortcutRecorder(QLineEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setPlaceholderText("Press a key combination")
self.recorded_shortcut = None

# Set the style sheet for rounded corners
self.setStyleSheet(
"""
QLineEdit {
border: 1px solid #a0a0a0;
border-radius: 7.5px;
padding: 2px 5px;
background-color: #fefefe;
color: #333333;
font-size: 14px;
}
QLineEdit:focus {
border-color: #3498db;
}
"""
)

def keyPressEvent(self, event: QKeyEvent):
modifiers = []
if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
modifiers.append("<ctrl>")
if is_macos():
modifiers.append("<cmd>")
else:
modifiers.append("<ctrl>")
if event.modifiers() & Qt.KeyboardModifier.AltModifier:
modifiers.append("<alt>")
if event.modifiers() & Qt.KeyboardModifier.ShiftModifier:
modifiers.append("<shift>")
if event.modifiers() & Qt.KeyboardModifier.MetaModifier:
modifiers.append("<cmd>") # Using <cmd> for Meta/Command key
if is_macos():
modifiers.append("<ctrl>")
else:
modifiers.append("<cmd>")

key = event.key()
if key not in (
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "llama-assistant"
version = "0.1.22"
version = "0.1.23"
authors = [
{name = "Viet-Anh Nguyen", email = "vietanh.dev@gmail.com"},
]
Expand Down

0 comments on commit 751e0a9

Please sign in to comment.