--- quodlibet/util/path.py 2025-05-31 22:50:33.000000000 +0800 +++ quodlibet/util/path.py 2025-10-16 07:05:06.000000000 +0800 @@ -24,9 +24,6 @@ from .environment import is_windows from .misc import NamedTemporaryFile -if sys.platform == "darwin": - from Foundation import NSString - def mkdir(dir_, *args): """Make a directory, including all its parent directories. This does not @@ -376,7 +373,10 @@ decoded = data.decode("utf-8", "quodlibet-osx-path-decode") try: - return bytes2fsn(NSString.fileSystemRepresentation(decoded), "utf-8") + fs_rep = decoded.encode("utf-8") + if b'\x00' in fs_rep: + raise ValueError("NUL byte in path") + return bytes2fsn(fs_rep, "utf-8") except ValueError: return filename --- quodlibet/util/i18n.py 2025-05-31 22:50:33.000000000 +0800 +++ quodlibet/util/i18n.py 2025-10-16 07:17:24.000000000 +0800 @@ -50,7 +50,7 @@ def osx_locale_id_to_lang(id_): - """Converts a NSLocale identifier to something suitable for LANG""" + """Converts a locale identifier to something suitable for LANG.""" if "_" not in id_: return id_ @@ -85,16 +85,12 @@ os.environ.setdefault("LANG", langs[0]) os.environ.setdefault("LANGUAGE", ":".join(langs)) elif sys.platform == "darwin": - from AppKit import NSLocale - - locale_id = NSLocale.currentLocale().localeIdentifier() - lang = osx_locale_id_to_lang(locale_id) + lang, encoding = locale.getdefaultlocale() + if not lang: + lang = "en_US" + lang = osx_locale_id_to_lang(lang) os.environ.setdefault("LANG", lang) - - preferred_langs = NSLocale.preferredLanguages() - if preferred_langs: - languages = map(bcp47_to_language, preferred_langs) - os.environ.setdefault("LANGUAGE", ":".join(languages)) + os.environ.setdefault("LANGUAGE", lang) else: return --- quodlibet/mmkeys/osx.py 2025-05-31 22:50:33.000000000 +0800 +++ quodlibet/mmkeys/osx.py 2025-10-16 07:27:41.000000000 +0800 """ import threading +import sys from gi.repository import GLib from ._base import MMKeysBackend, MMKeysAction, MMKeysImportError -try: - from AppKit import NSKeyUp, NSSystemDefined, NSEvent - import Quartz -except ImportError as e: - raise MMKeysImportError from e - - -class OSXBackend(MMKeysBackend): - def __init__(self, name, callback): - self.__eventsapp = MacKeyEventsTap(callback) - self.__eventsapp.start() - - def cancel(self): - if self.__eventsapp is not None: - self.__eventsapp.stop() - self.__eventsapp = None +if sys.platform == "darwin": + try: + from AppKit import NSKeyUp, NSSystemDefined, NSEvent + import Quartz + HAVE_OSXMMKEYS = True + except ImportError: + HAVE_OSXMMKEYS = False +else: + HAVE_OSXMMKEYS = False + + +if HAVE_OSXMMKEYS: + class OSXBackend(MMKeysBackend): + def __init__(self, name, callback): + self.__eventsapp = MacKeyEventsTap(callback) + self.__eventsapp.start() + + def cancel(self): + if self.__eventsapp is not None: + self.__eventsapp.stop() + self.__eventsapp = None + pass +else: + class OSXBackend(MMKeysBackend): + @staticmethod + def is_active(): + return False # So it never gets selected in find_active_backend + + def __init__(self, name, callback): + pass + def cancel(self): + pass + def grab(self): + pass class MacKeyEventsTap(threading.Thread): --- quodlibet/_main.py 2025-05-31 22:50:33.000000000 +0800 +++ quodlibet/_main.py 2025-10-16 07:30:12.000000000 +0800 @@ -279,51 +279,20 @@ def _main_setup_osx(window): - from AppKit import NSObject, NSApplication - import objc - try: import gi gi.require_version("GtkosxApplication", "1.0") from gi.repository import GtkosxApplication except (ValueError, ImportError): - print_d("importing GtkosxApplication failed, no native menus") + print_d("importing GtkosxApplication failed, no native menus or macOS integration") else: osx_app = GtkosxApplication.Application() window.set_as_osx_window(osx_app) osx_app.ready() - shared_app = NSApplication.sharedApplication() - gtk_delegate = shared_app.delegate() - - # Instead of quitting when the main window gets closed just hide it. - # If the dock icon gets clicked we get - # applicationShouldHandleReopen_hasVisibleWindows_ and show everything. - class Delegate(NSObject): - @objc.signature(b"B@:#B") - def applicationShouldHandleReopen_hasVisibleWindows_(self, ns_app, flag): # noqa - print_d("osx: handle reopen") - app.present() - return True - - def applicationShouldTerminate_(self, sender): # noqa - print_d("osx: block termination") - # FIXME: figure out why idle_add is needed here - from gi.repository import GLib - - GLib.idle_add(app.quit) - return False - - def applicationDockMenu_(self, sender): # noqa - return gtk_delegate.applicationDockMenu_(sender) - - def application_openFile_(self, sender, filename): # noqa - return app.window.open_file(filename.encode("utf-8")) - - delegate = Delegate.alloc().init() - delegate.retain() - shared_app.setDelegate_(delegate) + # Native macOS window and dock integration (AppKit/objc) is not available + print_d("Skipping AppKit/objc integration: PyObjC not installed") # QL shouldn't exit on window close, EF should if window.get_is_persistent():