From 9c628e28ec186445cf129b9ab7000e0c22e66020 Mon Sep 17 00:00:00 2001 From: Sho Date: Sun, 2 Aug 2026 02:13:33 +0900 Subject: [PATCH] feat: add Linux inotify real-time kernel watching for instant app install/uninstall detection --- quickshell/modules/launcher/AppLauncher.qml | 1 + .../services/python/app_launcher_service.py | 51 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/quickshell/modules/launcher/AppLauncher.qml b/quickshell/modules/launcher/AppLauncher.qml index 568b962..86c32eb 100644 --- a/quickshell/modules/launcher/AppLauncher.qml +++ b/quickshell/modules/launcher/AppLauncher.qml @@ -51,6 +51,7 @@ PanelWindow { openProgress = 0.0 openAnim.restart() AppLauncherService.reset() + AppLauncherService.reload() searchField.text = "" root.selectedIndex = 0 Qt.callLater(() => { diff --git a/quickshell/services/python/app_launcher_service.py b/quickshell/services/python/app_launcher_service.py index cecbae9..7e536db 100755 --- a/quickshell/services/python/app_launcher_service.py +++ b/quickshell/services/python/app_launcher_service.py @@ -231,6 +231,8 @@ def parse_desktop_apps(): def watch_directory_changes(): import select + import ctypes + dirs = [ '/usr/share/applications', '/usr/local/share/applications', @@ -239,6 +241,27 @@ def watch_directory_changes(): os.path.expanduser('~/.local/share/flatpak/exports/share/applications') ] + # Initialize Linux kernel inotify for 0ms instant file creation/deletion notifications + inotify_fd = -1 + try: + libc = ctypes.CDLL("libc.so.6") + inotify_init = libc.inotify_init + inotify_add_watch = libc.inotify_add_watch + inotify_init.restype = ctypes.c_int + inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32] + inotify_add_watch.restype = ctypes.c_int + + fd = inotify_init() + if fd >= 0: + # IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB + mask = 0x00000100 | 0x00000200 | 0x00000002 | 0x00000040 | 0x00000080 | 0x00000004 + for d in dirs: + if os.path.exists(d): + inotify_add_watch(fd, d.encode('utf-8'), mask) + inotify_fd = fd + except Exception: + inotify_fd = -1 + def get_state_snapshot(): snapshot = {} for d in dirs: @@ -260,21 +283,33 @@ def watch_directory_changes(): while True: try: - # Check for non-blocking stdin input (reload requests from QML) - rlist, _, _ = select.select([sys.stdin], [], [], 0.5) + read_fds = [sys.stdin] + if inotify_fd >= 0: + read_fds.append(inotify_fd) + + rlist, _, _ = select.select(read_fds, [], [], 0.4) if rlist: - line = sys.stdin.readline() - if line: - parse_desktop_apps() - last_snapshot = get_state_snapshot() - continue + for fd in rlist: + if fd == sys.stdin: + line = sys.stdin.readline() + if line: + parse_desktop_apps() + last_snapshot = get_state_snapshot() + elif fd == inotify_fd: + # Flush inotify events and trigger re-parse + try: + os.read(inotify_fd, 4096) + except Exception: pass + parse_desktop_apps() + last_snapshot = get_state_snapshot() + continue current_snapshot = get_state_snapshot() if current_snapshot != last_snapshot: last_snapshot = current_snapshot parse_desktop_apps() except Exception: - time.sleep(0.5) + time.sleep(0.4) if __name__ == '__main__': watch_directory_changes()