diff --git a/README.md b/README.md index 8cfd46d9..b495e2e4 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ To install the latest git version directly from github without cloning, run `pip install git+https://github.com/ActivityWatch/aw-watcher-window.git` To install from a cloned version, cd into the directory and run -`poetry install` to install inside an virtualenv. If you want to install it -system-wide it can be installed with `pip install .`, but that has the issue -that it might not get the exact version of the dependencies due to not reading -the poetry.lock file. +`poetry install` to install inside an virtualenv. You can run the binary via `aw-watcher-window`. + +If you want to install it system-wide it can be installed with `pip install .`, but that has the issue +that it might not get the exact version of the dependencies due to not reading the poetry.lock file. ## Note to macOS users diff --git a/aw_watcher_window/config.py b/aw_watcher_window/config.py index 372a680f..332d1ff7 100644 --- a/aw_watcher_window/config.py +++ b/aw_watcher_window/config.py @@ -1,18 +1,31 @@ from configparser import ConfigParser +import argparse from aw_core.config import load_config as _load_config - def load_config(): default_client_config = ConfigParser() - default_client_config["aw-watcher-window"] = { - "exclude_title": False, - "poll_time": "1.0" - } - default_client_config["aw-watcher-window-testing"] = { + default_client_config["aw-watcher-window"] = default_client_config["aw-watcher-window-testing"] = { "exclude_title": False, - "poll_time": "1.0" + "poll_time": "1.0", + "strategy_macos": "jxa" } # TODO: Handle so aw-watcher-window testing gets loaded instead of testing is on return _load_config("aw-watcher-window", default_client_config)["aw-watcher-window"] + +def parse_args(): + config = load_config() + + default_poll_time = config.getfloat("poll_time") + default_exclude_title = config.getboolean("exclude_title") + default_strategy_macos = config.get("strategy_macos") + + parser = argparse.ArgumentParser("A cross platform window watcher for Activitywatch.\nSupported on: Linux (X11), macOS and Windows.") + parser.add_argument("--testing", dest="testing", action="store_true") + parser.add_argument("--exclude-title", dest="exclude_title", action="store_true", default=default_exclude_title) + parser.add_argument("--verbose", dest="verbose", action="store_true") + parser.add_argument("--poll-time", dest="poll_time", type=float, default=default_poll_time) + parser.add_argument("--strategy", dest="strategy", default=default_strategy_macos, choices=["jxa", "applescript"], help="(macOS only) strategy to use for retrieving the active window") + parsed_args = parser.parse_args() + return parsed_args diff --git a/aw_watcher_window/lib.py b/aw_watcher_window/lib.py index 010fb802..f2743b07 100644 --- a/aw_watcher_window/lib.py +++ b/aw_watcher_window/lib.py @@ -4,6 +4,7 @@ def get_current_window_linux() -> Optional[dict]: from . import xlib + window = xlib.get_current_window() if window is None: @@ -13,20 +14,28 @@ def get_current_window_linux() -> Optional[dict]: cls = xlib.get_window_class(window) name = xlib.get_window_name(window) - return {"appname": cls, "title": name} + return {"app": cls, "title": name} + +def get_current_window_macos(strategy: str) -> Optional[dict]: + # TODO should we use unknown when the title is blank like the other platforms? -def get_current_window_macos() -> Optional[dict]: - from . import macos - info = macos.getInfo() - app = macos.getApp(info) - title = macos.getTitle(info) + # `jxa` is the default & preferred strategy. It includes the url + incognito status + if strategy == "jxa": + from . import macos_jxa - return {"title": title, "appname": app} + return macos_jxa.getInfo() + elif strategy == "applescript": + from . import macos_applescript + + return macos_applescript.getInfo() + else: + raise ValueError(f"invalid strategy '{strategy}'") def get_current_window_windows() -> Optional[dict]: from . import windows + window_handle = windows.get_active_window_handle() app = windows.get_app_name(window_handle) title = windows.get_window_title(window_handle) @@ -36,14 +45,14 @@ def get_current_window_windows() -> Optional[dict]: if title is None: title = "unknown" - return {"appname": app, "title": title} + return {"app": app, "title": title} -def get_current_window() -> Optional[dict]: +def get_current_window(strategy: str = None) -> Optional[dict]: if sys.platform.startswith("linux"): return get_current_window_linux() elif sys.platform == "darwin": - return get_current_window_macos() + return get_current_window_macos(strategy) elif sys.platform in ["win32", "cygwin"]: return get_current_window_windows() else: diff --git a/aw_watcher_window/macos.py b/aw_watcher_window/macos.py deleted file mode 100644 index 968d4920..00000000 --- a/aw_watcher_window/macos.py +++ /dev/null @@ -1,45 +0,0 @@ -import subprocess -from subprocess import PIPE -import os - - -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() - - -def getApp(info: str) -> str: - return info.split('","')[0][1:] - - -def getTitle(info: str) -> str: - return info.split('","')[1][:-1] - - -def background_ensure_permissions() -> None: - from multiprocessing import Process - permission_process = Process(target=ensure_permissions, args=(())) - permission_process.start() - return - - -def ensure_permissions() -> None: - from ApplicationServices import AXIsProcessTrusted - from AppKit import NSAlert, NSAlertFirstButtonReturn, NSWorkspace, NSURL - accessibility_permissions = AXIsProcessTrusted() - if not accessibility_permissions: - title = "Missing accessibility permissions" - info = "To let ActivityWatch capture window titles grant it accessibility permissions. \n If you've already given ActivityWatch accessibility permissions and are still seeing this dialog, try removing and re-adding them." - - alert = NSAlert.new() - alert.setMessageText_(title) - alert.setInformativeText_(info) - - ok_button = alert.addButtonWithTitle_("Open accessibility settings") - - alert.addButtonWithTitle_("Close") - choice = alert.runModal() - print(choice) - if choice == NSAlertFirstButtonReturn: - NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")) diff --git a/aw_watcher_window/macos_applescript.py b/aw_watcher_window/macos_applescript.py new file mode 100644 index 00000000..bc94b646 --- /dev/null +++ b/aw_watcher_window/macos_applescript.py @@ -0,0 +1,31 @@ +import os +import subprocess +from subprocess import PIPE +from typing import Dict + + +# the applescript version of the macos strategy is kept here until the jxa +# approach is proven out in production environments +# https://github.com/ActivityWatch/aw-watcher-window/pull/52 + + +def getInfo() -> Dict[str, str]: + cmd = [ + "osascript", + os.path.join(os.path.dirname(os.path.realpath(__file__)), "printAppTitle.scpt"), + ] + p = subprocess.run(cmd, stdout=PIPE) + info = str(p.stdout, "utf8").strip() + + app = getApp(info) + title = getTitle(info) + + return {"app": app, "title": title} + + +def getApp(info: str) -> str: + return info.split('","')[0][1:] + + +def getTitle(info: str) -> str: + return info.split('","')[1][:-1] diff --git a/aw_watcher_window/macos_jxa.py b/aw_watcher_window/macos_jxa.py new file mode 100644 index 00000000..a5eb2f56 --- /dev/null +++ b/aw_watcher_window/macos_jxa.py @@ -0,0 +1,97 @@ +import os +import json +import logging +from typing import Dict + +logger = logging.getLogger(__name__) +script = None + + +def compileScript(): + # https://stackoverflow.com/questions/44209057/how-can-i-run-jxa-from-swift + # https://stackoverflow.com/questions/16065162/calling-applescript-from-python-without-using-osascript-or-appscript + from OSAKit import OSAScript, OSALanguage + + scriptPath = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "printAppStatus.jxa" + ) + scriptContents = open(scriptPath, mode="r").read() + javascriptLanguage = OSALanguage.languageForName_("JavaScript") + + script = OSAScript.alloc().initWithSource_language_( + scriptContents, javascriptLanguage + ) + (success, err) = script.compileAndReturnError_(None) + + # should only occur if jxa was modified incorrectly + if not success: + raise Exception("error compiling jxa script") + + return script + + +def getInfo() -> Dict[str, str]: + # use a global variable to cache the compiled script for performance + global script + if not script: + script = compileScript() + + (result, err) = script.executeAndReturnError_(None) + + if err: + # error structure: + # { + # NSLocalizedDescription = "Error: Error: Can't get object."; + # NSLocalizedFailureReason = "Error: Error: Can't get object."; + # OSAScriptErrorBriefMessageKey = "Error: Error: Can't get object."; + # OSAScriptErrorMessageKey = "Error: Error: Can't get object."; + # OSAScriptErrorNumberKey = "-1728"; + # OSAScriptErrorRangeKey = "NSRange: {0, 0}"; + # } + + raise Exception("jxa error: {}".format(err["NSLocalizedDescription"])) + + return json.loads(result.stringValue()) + + +def background_ensure_permissions() -> None: + from multiprocessing import Process + + permission_process = Process(target=ensure_permissions, args=(())) + permission_process.start() + return + + +def ensure_permissions() -> None: + from ApplicationServices import AXIsProcessTrusted + from AppKit import NSAlert, NSAlertFirstButtonReturn, NSWorkspace, NSURL + + accessibility_permissions = AXIsProcessTrusted() + if not accessibility_permissions: + title = "Missing accessibility permissions" + info = "To let ActivityWatch capture window titles grant it accessibility permissions. \n If you've already given ActivityWatch accessibility permissions and are still seeing this dialog, try removing and re-adding them." + + alert = NSAlert.new() + alert.setMessageText_(title) + alert.setInformativeText_(info) + + ok_button = alert.addButtonWithTitle_("Open accessibility settings") + + alert.addButtonWithTitle_("Close") + choice = alert.runModal() + print(choice) + if choice == NSAlertFirstButtonReturn: + NSWorkspace.sharedWorkspace().openURL_( + NSURL.URLWithString_( + "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" + ) + ) + + +if __name__ == "__main__": + print(getInfo()) + print("Waiting 5 seconds...") + import time + + time.sleep(5) + print(getInfo()) diff --git a/aw_watcher_window/main.py b/aw_watcher_window/main.py index af365e6f..ead2e0f6 100644 --- a/aw_watcher_window/main.py +++ b/aw_watcher_window/main.py @@ -1,4 +1,3 @@ -import argparse import logging import traceback import sys @@ -11,18 +10,17 @@ from aw_client import ActivityWatchClient from .lib import get_current_window -from .config import load_config +from .config import parse_args logger = logging.getLogger(__name__) +# run with LOG_LEVEL=DEBUG +log_level = os.environ.get('LOG_LEVEL') +if log_level: + logger.setLevel(logging.__getattribute__(log_level.upper())) def main(): - # Read settings from config - config = load_config() - args = parse_args( - default_poll_time=config.getfloat("poll_time"), - default_exclude_title=config.getboolean("exclude_title"), - ) + args = parse_args() if sys.platform.startswith("linux") and ("DISPLAY" not in os.environ or not os.environ["DISPLAY"]): raise Exception("DISPLAY environment variable not set") @@ -45,43 +43,30 @@ def main(): sleep(1) # wait for server to start with client: - heartbeat_loop(client, bucket_id, poll_time=args.poll_time, exclude_title=args.exclude_title) + heartbeat_loop(client, bucket_id, poll_time=args.poll_time, strategy=args.strategy, exclude_title=args.exclude_title) - -def parse_args(default_poll_time: float, default_exclude_title: bool): - """config contains defaults loaded from the config file""" - parser = argparse.ArgumentParser("A cross platform window watcher for Activitywatch.\nSupported on: Linux (X11), macOS and Windows.") - parser.add_argument("--testing", dest="testing", action="store_true") - parser.add_argument("--exclude-title", dest="exclude_title", action="store_true", default=default_exclude_title) - parser.add_argument("--verbose", dest="verbose", action="store_true") - parser.add_argument("--poll-time", dest="poll_time", type=float, default=default_poll_time) - return parser.parse_args() - - -def heartbeat_loop(client, bucket_id, poll_time, exclude_title=False): +def heartbeat_loop(client, bucket_id, poll_time, strategy, exclude_title=False): while True: if os.getppid() == 1: logger.info("window-watcher stopped because parent process died") break try: - current_window = get_current_window() + current_window = get_current_window(strategy) logger.debug(current_window) except Exception as e: logger.error("Exception thrown while trying to get active window: {}".format(e)) traceback.print_exc() - current_window = {"appname": "unknown", "title": "unknown"} + current_window = {"app": "unknown", "title": "unknown"} now = datetime.now(timezone.utc) if current_window is None: logger.debug('Unable to fetch window, trying again on next poll') else: - # Create current_window event - data = { - "app": current_window["appname"], - "title": current_window["title"] if not exclude_title else "excluded" - } - current_window_event = Event(timestamp=now, data=data) + if exclude_title: + current_window["title"] = "excluded" + + current_window_event = Event(timestamp=now, data=current_window) # Set pulsetime to 1 second more than the poll_time # This since the loop takes more time than poll_time diff --git a/aw_watcher_window/printAppStatus.jxa b/aw_watcher_window/printAppStatus.jxa new file mode 100755 index 00000000..e1d3dc1f --- /dev/null +++ b/aw_watcher_window/printAppStatus.jxa @@ -0,0 +1,61 @@ +#!/usr/bin/osascript -l JavaScript + +// adapted from: +// https://gist.github.com/EvanLovely/cb01eafb0d61515c835ecd56f6ac199a + +// new to jxa? +// - https://apple-dev.groups.io/g/jxa/wiki/3202 +// - interactive repl: `osascript -il JavaScript` +// - API reference: Script Editor -> File -> Open Dictionary + +var seApp = Application("System Events"); +var oProcess = seApp.processes.whose({frontmost: true})[0]; +var appName = oProcess.displayedName(); + +// as of 05/01/21 incognio & url are not actively used in AW +// variables must be set to `undefined` since this script is re-run via osascript +// and the previously set values will be cached otherwise +var url = undefined, incognito = undefined, title = undefined; + +// it's not possible to get the URL from firefox +// https://stackoverflow.com/questions/17846948/does-firefox-offer-applescript-support-to-get-url-of-windows + +switch(appName) { + case "Safari": + // incognito is not available via safari applescript + url = Application(appName).documents[0].url(); + title = Application(appName).documents[0].name(); + break; + case "Google Chrome": + case "Google Chrome Canary": + case "Chromium": + case "Brave Browser": + const activeWindow = Application(appName).windows[0]; + const activeTab = activeWindow.activeTab(); + + url = activeTab.url(); + title = activeTab.name(); + incognito = activeWindow.mode() === 'incognito'; + break; + default: + mainWindow = oProcess. + windows(). + find(w => w.attributes.byName("AXMain").value() === true) + + // in some cases, the primary window of an application may not be found + // this occurs rarely and seems to be triggered by switching to a different application + if(mainWindow) { + title = mainWindow. + attributes. + byName("AXTitle"). + value() + } +} + +// key names must match expected names in lib.py +JSON.stringify({ + app: appName, + url, + title, + incognito +}); \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 148b0679..14ff2e5f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,29 +24,29 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "aw-client" -version = "0.5.0" +version = "0.5.1" description = "Client library for ActivityWatch" category = "main" optional = false -python-versions = "^3.6" +python-versions = "^3.7" develop = false [package.dependencies] -aw-core = "^0.5" +aw-core = "^0.5.1" click = "^7.1.1" persist-queue = "^0.6.0" requests = "^2.22.0" @@ -55,15 +55,15 @@ requests = "^2.22.0" type = "git" url = "https://github.com/ActivityWatch/aw-client.git" reference = "master" -resolved_reference = "6ba8eb0f09ca2da2d6fd4e99ceb0b02c481a051b" +resolved_reference = "6b08d438c5143149d1e60cc789a4da426771126c" [[package]] name = "aw-core" -version = "0.5.0" +version = "0.5.1" description = "Core library for ActivityWatch" category = "main" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.7,<4.0" [package.dependencies] appdirs = ">=1.4.3,<2.0.0" @@ -257,7 +257,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyobjc-core" -version = "7.1" +version = "7.2" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -265,52 +265,64 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "6.2.2" +version = "7.2" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=6.2.2" -pyobjc-framework-Cocoa = ">=6.2.2" -pyobjc-framework-Quartz = ">=6.2.2" +pyobjc-core = ">=7.2" +pyobjc-framework-Cocoa = ">=7.2" +pyobjc-framework-Quartz = ">=7.2" [[package]] name = "pyobjc-framework-cocoa" -version = "7.1" +version = "7.2" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=7.1" +pyobjc-core = ">=7.2" [[package]] name = "pyobjc-framework-coretext" -version = "6.2.2" +version = "7.2" description = "Wrappers for the framework CoreText on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=6.2.2" -pyobjc-framework-Cocoa = ">=6.2.2" -pyobjc-framework-Quartz = ">=6.2.2" +pyobjc-core = ">=7.2" +pyobjc-framework-Cocoa = ">=7.2" +pyobjc-framework-Quartz = ">=7.2" + +[[package]] +name = "pyobjc-framework-osakit" +version = "7.2" +description = "Wrappers for the framework OSAKit on macOS" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyobjc-core = ">=7.2" +pyobjc-framework-Cocoa = ">=7.2" [[package]] name = "pyobjc-framework-quartz" -version = "7.1" +version = "7.2" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=7.1" -pyobjc-framework-Cocoa = ">=7.1" +pyobjc-core = ">=7.2" +pyobjc-framework-Cocoa = ">=7.2" [[package]] name = "pyparsing" @@ -341,7 +353,7 @@ python-versions = ">=3.5" [[package]] name = "pytest" -version = "6.2.3" +version = "6.2.4" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -408,7 +420,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false @@ -456,7 +468,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -508,7 +520,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "cb68b2a767d7ac4442d2d3024d543053a3318ea5a4dca06a1661c0d6bce5d0ee" +content-hash = "93c4037f6b3763d989be6e204b699fb2e3338a9429a62a5c4ffc859eb335566e" [metadata.files] altgraph = [ @@ -524,13 +536,13 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] aw-client = [] aw-core = [ - {file = "aw-core-0.5.0.tar.gz", hash = "sha256:d74c22b4b338a20e05a3e8a345a1bc427a2579be669c5b9ea1fd04bb6fd18f9e"}, - {file = "aw_core-0.5.0-py3-none-any.whl", hash = "sha256:d72a56a532b447fee48cad2ba0e602713d93b803dff3283256273ca5859007e5"}, + {file = "aw-core-0.5.1.tar.gz", hash = "sha256:ec6ba1d140f76f832f8e69e4ff32ed498d994d438073bfa59ea588613ad2cff6"}, + {file = "aw_core-0.5.1-py3-none-any.whl", hash = "sha256:2ce56a9365fde822e44c1cd24c9de7a6ca52ff1f5cbad794cc39c228d9a3a345"}, ] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, @@ -620,36 +632,44 @@ py = [ {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pyobjc-core = [ - {file = "pyobjc-core-7.1.tar.gz", hash = "sha256:a0616d5d816b4471f8f782c3a9a8923d2cc85014d88ad4f7fec694be9e6ea349"}, - {file = "pyobjc_core-7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9fb45c9916f2a03ecd6b9ecde4c35d1d0f1a590ae2ea2372f9d9a360226ac1d"}, - {file = "pyobjc_core-7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff8e87358c6195a2937004f279050cce3d4c02cd77acd73c5ad367307def855"}, - {file = "pyobjc_core-7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:afb38efd3f2960eb49eb78552d465cfd025a9d6efa06cd4cd8694dafbe7c6e06"}, - {file = "pyobjc_core-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cb329c4119044fe83bcb3c5d4794d636c706ff0cb7c1c77d36ef5c373100082"}, - {file = "pyobjc_core-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7913d7b20217c294900537faf58e5cc15942ed7af277bf05db25667d18255114"}, + {file = "pyobjc-core-7.2.tar.gz", hash = "sha256:9e9ec482d80ea030cdb1613d05a247f31eedabe6666d884d42dd890cc5fb0e05"}, + {file = "pyobjc_core-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:94b4d9de9d228db52dd35012096d63bdf8c1ace58ea3be1d5f6f39313cd502f2"}, + {file = "pyobjc_core-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:971cbd7189ae1aa03ef0d16124aa5bcd053779e0e6b6011a41c3dbd5b4ea7e88"}, + {file = "pyobjc_core-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9d93b20394008373d6d2856d49aaff26f4b97ff42d924a14516c8a82313ec8c0"}, + {file = "pyobjc_core-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:860183540d1be792c26426018139ac8ba75e85f675c59ba080ccdc52d8e74c7a"}, + {file = "pyobjc_core-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ffe61d3c2a404354daf2d895e34e38c5044453353581b3c396bf5365de26250c"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-6.2.2.tar.gz", hash = "sha256:f9d74f1d72713180638fc441c072eaaa8e59ccabb04bac18b21d137e9c0cb5e6"}, - {file = "pyobjc_framework_ApplicationServices-6.2.2-py2.py3-none-any.whl", hash = "sha256:9018463ad698e6bb4f4dfb01cd3157b521b68ace447df8cceff17c2b99d0ebc9"}, + {file = "pyobjc-framework-ApplicationServices-7.2.tar.gz", hash = "sha256:938fcebda774b772b7681b5ff4d2b3c91c4bde29d6ad8b4e7cdd87d4df1f42ec"}, + {file = "pyobjc_framework_ApplicationServices-7.2-py2.py3-none-any.whl", hash = "sha256:8a941282072e8f6801b01c13648514044515ad306336498f62740a48eff383df"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-7.1.tar.gz", hash = "sha256:67966152b3d38a0225176fceca2e9f56d849c8e7445548da09a00cb13155ec3e"}, - {file = "pyobjc_framework_Cocoa-7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bef77eafaac5eaf1d91d479d5483fd02216caa3edc27e8f5adc9af0b3fecdac3"}, - {file = "pyobjc_framework_Cocoa-7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2ea3582c456827dc20e648c905fdbcf8d3dfae89434f981e9b761cd07262049"}, - {file = "pyobjc_framework_Cocoa-7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a4050f2d776f40c2409a151c6f7896420e936934b3bdbfabedf91509637ed9b"}, - {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f68f022f1f6d5985c418e10c6608c562fcf4bfe3714ec64fd10ce3dc6221bd4"}, - {file = "pyobjc_framework_Cocoa-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ecfefd4c48dae42275c18679c69f6f2fff970e711097515a0a8732fc10194018"}, + {file = "pyobjc-framework-Cocoa-7.2.tar.gz", hash = "sha256:c8b23f03dc3f4436d36c0fd006a8a084835c4f6015187df7c3aa5de8ecd5c653"}, + {file = "pyobjc_framework_Cocoa-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8e5dd5daa0096755937ec24c345a4b07c3fa131a457f99e0fdeeb01979178ec7"}, + {file = "pyobjc_framework_Cocoa-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:828d183947fc7746953fd0c9b1092cc423745ba0b49719e7b7d1e1614aaa20ec"}, + {file = "pyobjc_framework_Cocoa-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e4c6d7baa0c2ab5ea5efb8836ad0b3b3976cffcfc6195c1f195e826c6eb5744"}, + {file = "pyobjc_framework_Cocoa-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9a9d1d49cc5a810773c88d6de821e60c8cc41d01113cf1b9e7662938f5f7d66"}, + {file = "pyobjc_framework_Cocoa-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:506c2cd09f421eac92b9008a0142174c3d1d70ecd4b0e3fa2b924767995fd14e"}, ] pyobjc-framework-coretext = [ - {file = "pyobjc-framework-CoreText-6.2.2.tar.gz", hash = "sha256:944fda1bd9c2827e36907216a930fcaf429788132616bed3d687ba0e80405d34"}, - {file = "pyobjc_framework_CoreText-6.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0a5f2288b1e29d93d00d708d0e10d8ca49af1116772d48d0623cc62ffad12918"}, + {file = "pyobjc-framework-CoreText-7.2.tar.gz", hash = "sha256:8e3e52298073bf75c33fdc0c9f19c5d5c03b32cc507a4138386ea0e9886fd5d1"}, + {file = "pyobjc_framework_CoreText-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:13ef2c684396d7d29fc12164bbf801c33879c9639edc2202a0e7b2202f4be6c5"}, + {file = "pyobjc_framework_CoreText-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6bfc35769b9f9cbfc50dc0e2d3bf50fac78c5f5b66d51130daece6817a2a6d26"}, + {file = "pyobjc_framework_CoreText-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3013c2c006df857adcf2a3fdf1c4f185f8a75d513fa9d766922a3659940e7b0c"}, + {file = "pyobjc_framework_CoreText-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:047c0ac8cf2075a6ee11f039df4ea53f7b87003f37a91ee2fd731a7656df9ccf"}, + {file = "pyobjc_framework_CoreText-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b519e462db1747c1b6702d7abc2bf036c8570dc634d59a5f02324876826b516c"}, +] +pyobjc-framework-osakit = [ + {file = "pyobjc-framework-OSAKit-7.2.tar.gz", hash = "sha256:e553c2689ffd897040f6a1e24bdef6a0b3904e09f9a37bc65e377f1f016cd86a"}, + {file = "pyobjc_framework_OSAKit-7.2-py2.py3-none-any.whl", hash = "sha256:e7d873c2cd252027b07281cd65fb14b16938e2d0ee17cca6264e23df1cfca8f8"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-7.1.tar.gz", hash = "sha256:73102c9f4dbfa13275621014785ab3b684cf03ce93a4b0b270500c795349bea9"}, - {file = "pyobjc_framework_Quartz-7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7207a26244f02d4534ebb007fa55a9dc7c1b7fbb490d1e89e0d62cfd175e20f3"}, - {file = "pyobjc_framework_Quartz-7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5bc7a4fb3ea80b5af6910cc27729a0774a96327a69583fcf28057cb2ffce33ac"}, - {file = "pyobjc_framework_Quartz-7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c0469d60d4a79fc252f74adaa8177d2c680621d858c1b8ef19c411e903e2c892"}, - {file = "pyobjc_framework_Quartz-7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:04953c031fc35020682bd4613b9b5a9688bdb9eab7ed76fd8dcf028783568b4f"}, - {file = "pyobjc_framework_Quartz-7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d8e0c086faf649f86386d0ed99194c6d0704b602576e2b258532b635b510b790"}, + {file = "pyobjc-framework-Quartz-7.2.tar.gz", hash = "sha256:ea554e5697bc6747a4ce793c0b0036da16622b44ff75196d6124603008922afa"}, + {file = "pyobjc_framework_Quartz-7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dc61fe61d26f797e4335f3ffc891bcef64624c728c2603e3307b3910580b2cb8"}, + {file = "pyobjc_framework_Quartz-7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ad8103cc38923f2708904db11a0992ea960125ce6adf7b4c7a77d8fdafd412c4"}, + {file = "pyobjc_framework_Quartz-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4549d17ca41f0bf62792d5bc4b4293ba9a6cc560014b3e18ba22c65e4a5030d2"}, + {file = "pyobjc_framework_Quartz-7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:da16e4f1e13cb7b02e30fa538cbb3a356e4a694bbc2bb26d2bd100ca12a54ff6"}, + {file = "pyobjc_framework_Quartz-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1f6471177a39535cd0358ae29b8f3d31fe778a21deb74105c448c4e726619d7"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -663,8 +683,8 @@ pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ - {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, - {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, + {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, + {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, ] python-json-logger = [ {file = "python-json-logger-0.1.11.tar.gz", hash = "sha256:b7a31162f2a01965a5efb94453ce69230ed208468b0bbc7fdfc56e6d8df2e281"}, @@ -690,8 +710,8 @@ requests = [ {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] strict-rfc3339 = [ {file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"}, @@ -740,9 +760,9 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] urllib3 = [ {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, diff --git a/pyproject.toml b/pyproject.toml index 7d8c463f..658ea92e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,9 @@ aw-client = {git = "https://github.com/ActivityWatch/aw-client.git"} python-xlib = {version = "^0.28", platform = "linux"} pypiwin32 = {version = "223", platform = "win32"} wmi = {version = "^1.4.9", platform = "win32"} -pyobjc-framework-ApplicationServices = { version = "^6.2", platform="darwin"} -pyobjc-framework-CoreText = {version = "^6.2", platform="darwin"} - +pyobjc-framework-ApplicationServices = { version = "^7.2", platform="darwin"} +pyobjc-framework-CoreText = {version = "^7.2", platform="darwin"} +pyobjc-framework-OSAKit = {version = "^7.2", platform="darwin"} [tool.poetry.dev-dependencies] pytest = "^6.0"