Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Get macOS window title through python bindings instead of running AppleScript #40

Closed
wants to merge 8 commits into from
Closed
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
aw-watcher-window
=================
# aw-watcher-window

Cross-platform window-Watcher for Linux (X11), macOS, Windows.

Expand All @@ -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.

2 changes: 1 addition & 1 deletion aw-watcher-window.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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=[],
Expand Down
27 changes: 13 additions & 14 deletions aw_watcher_window/lib.py
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -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)
Expand All @@ -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]]]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to get_current_window_fn?

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))
55 changes: 44 additions & 11 deletions aw_watcher_window/macos.py
Original file line number Diff line number Diff line change
@@ -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]
3 changes: 2 additions & 1 deletion aw_watcher_window/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Binary file removed aw_watcher_window/printAppTitle.scpt
Binary file not shown.