6
6
import AppKit
7
7
import ApplicationServices
8
8
import Quartz
9
+ import Foundation
10
+ import re
11
+ import plistlib
9
12
10
13
11
14
def get_active_window_state ():
@@ -14,8 +17,8 @@ def get_active_window_state():
14
17
meta = get_active_window_meta ()
15
18
data = get_window_data (meta )
16
19
title_parts = [
17
- meta [' kCGWindowOwnerName' ],
18
- meta [' kCGWindowName' ],
20
+ meta [" kCGWindowOwnerName" ],
21
+ meta [" kCGWindowName" ],
19
22
]
20
23
title_parts = [part for part in title_parts if part ]
21
24
title = " " .join (title_parts )
@@ -47,14 +50,12 @@ def get_active_window_state():
47
50
def get_active_window_meta ():
48
51
windows = Quartz .CGWindowListCopyWindowInfo (
49
52
(
50
- Quartz .kCGWindowListExcludeDesktopElements |
51
- Quartz .kCGWindowListOptionOnScreenOnly
53
+ Quartz .kCGWindowListExcludeDesktopElements
54
+ | Quartz .kCGWindowListOptionOnScreenOnly
52
55
),
53
56
Quartz .kCGNullWindowID ,
54
57
)
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" ]
58
59
active_window_info = active_windows_info [0 ]
59
60
return active_window_info
60
61
@@ -63,7 +64,7 @@ def get_active_window(window_meta):
63
64
pid = window_meta ["kCGWindowOwnerPID" ]
64
65
app_ref = ApplicationServices .AXUIElementCreateApplication (pid )
65
66
error_code , window = ApplicationServices .AXUIElementCopyAttributeValue (
66
- app_ref , ' AXFocusedWindow' , None
67
+ app_ref , " AXFocusedWindow" , None
67
68
)
68
69
if error_code :
69
70
logger .error ("Error getting focused window" )
@@ -98,13 +99,14 @@ def dump_state(element, elements=None):
98
99
state [k ] = _state
99
100
return state
100
101
else :
101
- error_code , attr_names = (
102
- ApplicationServices . AXUIElementCopyAttributeNames ( element , None )
102
+ error_code , attr_names = ApplicationServices . AXUIElementCopyAttributeNames (
103
+ element , None
103
104
)
104
105
if attr_names :
105
106
state = {}
106
107
for attr_name in attr_names :
107
-
108
+ if attr_name is None :
109
+ continue
108
110
# don't traverse back up
109
111
# for WindowEvents:
110
112
if "parent" in attr_name .lower ():
@@ -113,14 +115,19 @@ def dump_state(element, elements=None):
113
115
if attr_name in ("AXTopLevelUIElement" , "AXWindow" ):
114
116
continue
115
117
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 ,
120
125
)
121
126
122
127
# 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
+ ):
124
131
continue
125
132
126
133
_state = dump_state (attr_val , elements )
@@ -135,14 +142,59 @@ def dump_state(element, elements=None):
135
142
def deepconvert_objc (object ):
136
143
"""Convert all contents of an ObjC object to Python primitives."""
137
144
value = object
145
+ strings = (
146
+ str ,
147
+ AppKit .NSString ,
148
+ ApplicationServices .AXTextMarkerRangeRef ,
149
+ ApplicationServices .AXUIElementRef ,
150
+ ApplicationServices .AXTextMarkerRef ,
151
+ Quartz .CGPathRef ,
152
+ )
153
+
138
154
if isinstance (object , AppKit .NSNumber ):
139
155
value = int (object )
140
156
elif isinstance (object , AppKit .NSArray ) or isinstance (object , list ):
141
157
value = [deepconvert_objc (x ) for x in object ]
142
158
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 = } " )
146
198
if value :
147
199
value = atomacos ._converter .Converter ().convert_value (value )
148
200
return value
0 commit comments