--- 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():
