diff --git a/README.md b/README.md index 94990db3..f879ee88 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -aw-watcher-window -================= +# aw-watcher-window Cross-platform window-Watcher for Linux (X11), macOS, Windows. @@ -9,4 +8,3 @@ Cross-platform window-Watcher for Linux (X11), macOS, Windows. To log current window title the terminal needs access to macOS accessibility API. This can be enabled in `System Preferences > Security & Privacy > Accessibility`, then add the Terminal to this list. If this is not enabled the watcher can only log current application, and not window title. - diff --git a/aw-watcher-window.spec b/aw-watcher-window.spec index be81152e..8793f0af 100644 --- a/aw-watcher-window.spec +++ b/aw-watcher-window.spec @@ -6,7 +6,7 @@ block_cipher = None a = Analysis(['aw_watcher_window/__main__.py'], pathex=[], binaries=None, - datas=[("aw_watcher_window/printAppTitle.scpt", "aw_watcher_window")], + datas=[], hiddenimports=[], hookspath=[], runtime_hooks=[], diff --git a/aw_watcher_window/lib.py b/aw_watcher_window/lib.py index 010fb802..ceaf4a68 100644 --- a/aw_watcher_window/lib.py +++ b/aw_watcher_window/lib.py @@ -1,8 +1,8 @@ import sys -from typing import Optional +from typing import Callable, Dict, Optional -def get_current_window_linux() -> Optional[dict]: +def get_current_window_linux() -> Dict[str, str]: from . import xlib window = xlib.get_current_window() @@ -16,16 +16,15 @@ def get_current_window_linux() -> Optional[dict]: return {"appname": cls, "title": name} -def get_current_window_macos() -> Optional[dict]: +def initialize_get_macos_window() -> Callable[[], Dict[str, str]]: from . import macos - info = macos.getInfo() - app = macos.getApp(info) - title = macos.getTitle(info) + def get_current_window_macos() -> Dict[str, str]: + app = macos.get_current_app() + print ("appname" + macos.get_app_name(app) + ", title" + macos.get_app_title(app)) + return {"appname": macos.get_app_name(app), "title": macos.get_app_title(app)} + return get_current_window_macos - return {"title": title, "appname": app} - - -def get_current_window_windows() -> Optional[dict]: +def get_current_window_windows() -> Dict[str, str]: from . import windows window_handle = windows.get_active_window_handle() app = windows.get_app_name(window_handle) @@ -39,12 +38,12 @@ def get_current_window_windows() -> Optional[dict]: return {"appname": app, "title": title} -def get_current_window() -> Optional[dict]: +def get_current_window() -> Optional[Callable[[], Dict[str, str]]]: if sys.platform.startswith("linux"): - return get_current_window_linux() + return get_current_window_linux elif sys.platform == "darwin": - return get_current_window_macos() + return initialize_get_macos_window() elif sys.platform in ["win32", "cygwin"]: - return get_current_window_windows() + return get_current_window_windows else: raise Exception("Unknown platform: {}".format(sys.platform)) diff --git a/aw_watcher_window/macos.py b/aw_watcher_window/macos.py index 9cc04d9a..e1406ae1 100644 --- a/aw_watcher_window/macos.py +++ b/aw_watcher_window/macos.py @@ -1,17 +1,50 @@ -import subprocess -from subprocess import PIPE -import os +from threading import Thread +from typing import Dict, Optional, NoReturn +from AppKit import NSObject, NSNotification, NSWorkspace, NSRunningApplication, NSWorkspaceDidActivateApplicationNotification +from Quartz import ( + CGWindowListCopyWindowInfo, + kCGWindowListOptionOnScreenOnly, + kCGNullWindowID +) +from PyObjCTools import AppHelper -def getInfo() -> str: - cmd = ["osascript", os.path.join(os.path.dirname(os.path.realpath(__file__)), "printAppTitle.scpt")] - p = subprocess.run(cmd, stdout=PIPE) - return str(p.stdout, "utf8").strip() +class Observer(NSObject): + app = NSWorkspace.sharedWorkspace().frontmostApplication() + def get_front_app(self) -> NSRunningApplication: + return self.app -def getApp(info) -> str: - return info.split('","')[0][1:] + def handle_(self, noti: NSNotification) -> None: + self._set_front_app() + def _set_front_app(self) -> None: + self.app = NSWorkspace.sharedWorkspace().frontmostApplication() +observer = Observer.new() +NSWorkspace.sharedWorkspace().notificationCenter().addObserver_selector_name_object_( + observer, + "handle:", + NSWorkspaceDidActivateApplicationNotification, + None) +AppHelper.runConsoleEventLoop() + +def get_current_app() -> NSRunningApplication: + return observer.get_front_app() + + +def get_app_name(app: NSRunningApplication) -> str: + return app.localizedName() + + +def get_app_title(app: NSRunningApplication) -> str: + pid = app.processIdentifier() + options = kCGWindowListOptionOnScreenOnly + windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) + + for window in windowList: + lookupPid = window['kCGWindowOwnerPID'] + if (lookupPid == pid): + return window.get('kCGWindowName', 'Non-detected window title') + + return "Couldn't find title by pid" -def getTitle(info) -> str: - return info.split('","')[1][:-1] diff --git a/aw_watcher_window/main.py b/aw_watcher_window/main.py index f22ce497..10839f1e 100644 --- a/aw_watcher_window/main.py +++ b/aw_watcher_window/main.py @@ -54,13 +54,14 @@ def parse_args(default_poll_time: float, default_exclude_title: bool): def heartbeat_loop(client, bucket_id, poll_time, exclude_title=False): + current_window_fn = get_current_window() while True: if os.getppid() == 1: logger.info("window-watcher stopped because parent process died") break try: - current_window = get_current_window() + current_window = current_window_fn() logger.debug(current_window) except Exception as e: logger.error("Exception thrown while trying to get active window: {}".format(e)) diff --git a/aw_watcher_window/printAppTitle.scpt b/aw_watcher_window/printAppTitle.scpt deleted file mode 100644 index d004b036..00000000 Binary files a/aw_watcher_window/printAppTitle.scpt and /dev/null differ