Skip to content

Commit be0774f

Browse files
authored
fix: support more objective-c objects (#323)
* Update _macos.py * Update _macos.py * Update _macos.py * add bug report option * Update _macos.py * undo formatting * refactor * split * remove debug line * Update _macos.py * Update _macos.py * Update _macos.py * Update _macos.py * reorganize * Update _macos.py * Update _macos.py * nonetype * Update _macos.py
1 parent 0d6a155 commit be0774f

File tree

1 file changed

+71
-19
lines changed

1 file changed

+71
-19
lines changed

openadapt/window/_macos.py

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import AppKit
77
import ApplicationServices
88
import Quartz
9+
import Foundation
10+
import re
11+
import plistlib
912

1013

1114
def get_active_window_state():
@@ -14,8 +17,8 @@ def get_active_window_state():
1417
meta = get_active_window_meta()
1518
data = get_window_data(meta)
1619
title_parts = [
17-
meta['kCGWindowOwnerName'],
18-
meta['kCGWindowName'],
20+
meta["kCGWindowOwnerName"],
21+
meta["kCGWindowName"],
1922
]
2023
title_parts = [part for part in title_parts if part]
2124
title = " ".join(title_parts)
@@ -47,14 +50,12 @@ def get_active_window_state():
4750
def get_active_window_meta():
4851
windows = Quartz.CGWindowListCopyWindowInfo(
4952
(
50-
Quartz.kCGWindowListExcludeDesktopElements |
51-
Quartz.kCGWindowListOptionOnScreenOnly
53+
Quartz.kCGWindowListExcludeDesktopElements
54+
| Quartz.kCGWindowListOptionOnScreenOnly
5255
),
5356
Quartz.kCGNullWindowID,
5457
)
55-
active_windows_info = [
56-
win for win in windows if win['kCGWindowLayer'] == 0
57-
]
58+
active_windows_info = [win for win in windows if win["kCGWindowLayer"] == 0 and win["kCGWindowOwnerName"] != "Window Server"]
5859
active_window_info = active_windows_info[0]
5960
return active_window_info
6061

@@ -63,7 +64,7 @@ def get_active_window(window_meta):
6364
pid = window_meta["kCGWindowOwnerPID"]
6465
app_ref = ApplicationServices.AXUIElementCreateApplication(pid)
6566
error_code, window = ApplicationServices.AXUIElementCopyAttributeValue(
66-
app_ref, 'AXFocusedWindow', None
67+
app_ref, "AXFocusedWindow", None
6768
)
6869
if error_code:
6970
logger.error("Error getting focused window")
@@ -98,13 +99,14 @@ def dump_state(element, elements=None):
9899
state[k] = _state
99100
return state
100101
else:
101-
error_code, attr_names = (
102-
ApplicationServices.AXUIElementCopyAttributeNames(element, None)
102+
error_code, attr_names = ApplicationServices.AXUIElementCopyAttributeNames(
103+
element, None
103104
)
104105
if attr_names:
105106
state = {}
106107
for attr_name in attr_names:
107-
108+
if attr_name is None:
109+
continue
108110
# don't traverse back up
109111
# for WindowEvents:
110112
if "parent" in attr_name.lower():
@@ -113,14 +115,19 @@ def dump_state(element, elements=None):
113115
if attr_name in ("AXTopLevelUIElement", "AXWindow"):
114116
continue
115117

116-
error_code, attr_val = (
117-
ApplicationServices.AXUIElementCopyAttributeValue(
118-
element, attr_name, None,
119-
)
118+
(
119+
error_code,
120+
attr_val,
121+
) = ApplicationServices.AXUIElementCopyAttributeValue(
122+
element,
123+
attr_name,
124+
None,
120125
)
121126

122127
# for ActionEvents
123-
if attr_name == "AXRole" and "application" in attr_val.lower():
128+
if attr_val is not None and (
129+
attr_name == "AXRole" and "application" in attr_val.lower()
130+
):
124131
continue
125132

126133
_state = dump_state(attr_val, elements)
@@ -135,14 +142,59 @@ def dump_state(element, elements=None):
135142
def deepconvert_objc(object):
136143
"""Convert all contents of an ObjC object to Python primitives."""
137144
value = object
145+
strings = (
146+
str,
147+
AppKit.NSString,
148+
ApplicationServices.AXTextMarkerRangeRef,
149+
ApplicationServices.AXUIElementRef,
150+
ApplicationServices.AXTextMarkerRef,
151+
Quartz.CGPathRef,
152+
)
153+
138154
if isinstance(object, AppKit.NSNumber):
139155
value = int(object)
140156
elif isinstance(object, AppKit.NSArray) or isinstance(object, list):
141157
value = [deepconvert_objc(x) for x in object]
142158
elif isinstance(object, AppKit.NSDictionary) or isinstance(object, dict):
143-
value = dict(object)
144-
for k, v in value.items():
145-
value[k] = deepconvert_objc(v)
159+
value = {deepconvert_objc(k): deepconvert_objc(v) for k, v in object.items()}
160+
elif isinstance(object, strings):
161+
value = str(object)
162+
# handle core-foundation class AXValueRef
163+
elif isinstance(object, ApplicationServices.AXValueRef):
164+
# convert to dict - note: this object is not iterable
165+
# TODO: access directly, e.g. via ApplicationServices.AXUIElementCopyAttributeValue
166+
rep = repr(object)
167+
x_value = re.search(r"x:([\d.]+)", rep)
168+
y_value = re.search(r"y:([\d.]+)", rep)
169+
w_value = re.search(r"w:([\d.]+)", rep)
170+
h_value = re.search(r"h:([\d.]+)", rep)
171+
type_value = re.search(r"type\s?=\s?(\w+)", rep)
172+
value = {
173+
"x": float(x_value.group(1)) if x_value else None,
174+
"y": float(y_value.group(1)) if y_value else None,
175+
"w": float(w_value.group(1)) if w_value else None,
176+
"h": float(h_value.group(1)) if h_value else None,
177+
"type": type_value.group(1) if type_value else None,
178+
}
179+
elif isinstance(object, Foundation.NSURL):
180+
value = str(object.absoluteString())
181+
elif isinstance(object, Foundation.__NSCFAttributedString):
182+
value = str(object.string())
183+
elif isinstance(object, Foundation.__NSCFData):
184+
value = {
185+
deepconvert_objc(k): deepconvert_objc(v)
186+
for k, v in plistlib.loads(object).items()
187+
}
188+
elif isinstance(object, plistlib.UID):
189+
value = object.data
190+
else:
191+
if object and not (isinstance(object, bool) or isinstance(object, int)):
192+
logger.warning(
193+
f"Unknown type: {type(object)} - "
194+
f"Please report this on GitHub: "
195+
f"https://github.com/MLDSAI/OpenAdapt/issues/new?assignees=&labels=bug&projects=&template=bug_form.yml&title=%5BBug%5D%3A+"
196+
)
197+
logger.warning(f"{object=}")
146198
if value:
147199
value = atomacos._converter.Converter().convert_value(value)
148200
return value

0 commit comments

Comments
 (0)