Skip to content

Commit c68ee43

Browse files
authored
Merge pull request #3 from MrJarnould/fix/dock-orientation
Fix/dock orientation
2 parents 6c08819 + 1b3c966 commit c68ee43

5 files changed

Lines changed: 221 additions & 110 deletions

File tree

macapptree/dock_utils.py

Lines changed: 105 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
11
import os
2+
import subprocess
23
import time
4+
35
import AppKit
46
import Quartz
5-
import ApplicationServices
67

78
import macapptree.apps as apps
8-
from macapptree.uielement import UIElement
99
from macapptree.extractor import extract_window
10-
from macapptree.window_tools import store_screen_scaling_factor, segment_window_components
1110
from macapptree.screenshot_app_window import capture_full_screen
12-
from macapptree.window_tools import propagate_screen_rect
11+
from macapptree.uielement import UIElement
12+
from macapptree.window_tools import (
13+
propagate_screen_rect,
14+
segment_window_components,
15+
store_screen_scaling_factor,
16+
)
17+
18+
19+
def get_dock_orientation() -> str:
20+
try:
21+
result = subprocess.run(
22+
['defaults', 'read', 'com.apple.dock', 'orientation'],
23+
capture_output=True, text=True
24+
)
25+
val = result.stdout.strip()
26+
if val in ["left", "bottom", "right"]:
27+
return val
28+
except Exception:
29+
pass
30+
return "bottom"
31+
32+
def get_dock_autohide() -> bool:
33+
try:
34+
result = subprocess.run(
35+
['defaults', 'read', 'com.apple.dock', 'autohide'],
36+
capture_output=True, text=True
37+
)
38+
return result.stdout.strip() == "1"
39+
except Exception:
40+
pass
41+
return True # Default to True to be safe (reveal if unsure)
1342

1443
DOCK_THICKNESS_PT = 96
1544

@@ -30,74 +59,97 @@ def _propagate_screen_rect_local(ui_element, screen_rect_tl):
3059
_propagate_screen_rect_local(child, screen_rect_tl)
3160

3261
# if the dock is set to autohide, we can move the mouse to reveal it temporarily
33-
def _reveal_dock_temporarily(orientation: str = "bottom", dwell: float = 0.8):
62+
# if the dock is set to autohide, we can move the mouse to reveal it temporarily
63+
def _move_mouse_revealing_dock(orientation: str = "bottom", dwell: float = 0.8):
3464
try:
65+
# Save current mouse position
66+
loc = Quartz.CGEventGetLocation(Quartz.CGEventCreate(None))
67+
current_x, current_y = loc.x, loc.y
68+
3569
screen = AppKit.NSScreen.mainScreen().frame()
3670
sw, sh = int(screen.size.width), int(screen.size.height)
3771

3872
if orientation.lower() == "left":
39-
x, y = 2, sh // 2
73+
x, y = 0, sh // 2
4074
elif orientation.lower() == "right":
41-
x, y = sw - 2, sh // 2
75+
x, y = sw - 1, sh // 2
4276
else:
4377
# bottom
44-
x, y = sw // 2, sh - 2
78+
x, y = sw // 2, sh - 1
4579

4680
evt = Quartz.CGEventCreateMouseEvent(None, Quartz.kCGEventMouseMoved, (x, y), Quartz.kCGMouseButtonLeft)
4781
Quartz.CGEventPost(Quartz.kCGHIDEventTap, evt)
4882
time.sleep(dwell)
83+
84+
return (current_x, current_y)
85+
except Exception:
86+
return None
87+
88+
def _restore_mouse_position(pos):
89+
if not pos:
90+
return
91+
try:
92+
current_x, current_y = pos
93+
# Restore mouse position
94+
restore_evt = Quartz.CGEventCreateMouseEvent(None, Quartz.kCGEventMouseMoved, (current_x, current_y), Quartz.kCGMouseButtonLeft)
95+
Quartz.CGEventPost(Quartz.kCGHIDEventTap, restore_evt)
4996
except Exception:
5097
pass
5198

5299
class DockCapture:
53-
def __init__(self, orientation: str = "bottom", reveal: bool = True, dwell: float = 0.8):
54-
self.orientation = orientation
55-
self.reveal = reveal
100+
def __init__(self, orientation: str = None, reveal: bool = None, dwell: float = 0.8):
101+
self.orientation = orientation or get_dock_orientation()
102+
self.reveal = reveal if reveal is not None else get_dock_autohide()
56103
self.dwell = dwell
57104

58105
def capture(self, max_depth=None, output_screenshot_dir=None):
59106
store_screen_scaling_factor()
60107

108+
original_mouse_pos = None
61109
if self.reveal:
62-
_reveal_dock_temporarily(self.orientation, dwell=self.dwell)
63-
64-
dock_ax = apps.dock_ax_application()
65-
66-
x_tl, y_tl, w, h = _dock_tl_rect_fixed(self.orientation)
67-
68-
dock_root = UIElement(
69-
dock_ax,
70-
offset_x=x_tl,
71-
offset_y=y_tl,
72-
max_depth=max_depth,
73-
parents_visible_bbox=[0, 0, w, h],
74-
)
75-
dock_root.app_name = "Dock"
76-
dock_root.window_screen_rect = [x_tl, y_tl, x_tl + w, y_tl + h]
77-
78-
extract_window(
79-
dock_root, "com.apple.dock", None,
80-
perform_hit_test=False, print_nodes=False, max_depth=max_depth
81-
)
82-
propagate_screen_rect(dock_root, dock_root.window_screen_rect)
83-
84-
screenshot_info = None
85-
if output_screenshot_dir:
86-
os.makedirs(output_screenshot_dir, exist_ok=True)
87-
88-
full_path = os.path.join(output_screenshot_dir, "dock_full.png")
89-
capture_full_screen(full_path)
90-
91-
crop_path = full_path.replace(".png", "_cropped.png")
92-
from macapptree.screenshot_app_window import crop_screenshot
93-
_ = crop_screenshot(full_path, (x_tl, y_tl, w, h), crop_path)
94-
95-
segmented_path = segment_window_components(dock_root, crop_path) or crop_path
96-
screenshot_info = {
97-
"app": "com.apple.dock",
98-
"window_name": "Dock",
99-
"cropped_screenshot_path": crop_path,
100-
"segmented_screenshot_path": segmented_path,
101-
}
102-
103-
return dock_root, screenshot_info
110+
original_mouse_pos = _move_mouse_revealing_dock(self.orientation, dwell=self.dwell)
111+
112+
try:
113+
dock_ax = apps.dock_ax_application()
114+
115+
x_tl, y_tl, w, h = _dock_tl_rect_fixed(self.orientation)
116+
117+
dock_root = UIElement(
118+
dock_ax,
119+
offset_x=x_tl,
120+
offset_y=y_tl,
121+
max_depth=max_depth,
122+
parents_visible_bbox=[0, 0, w, h],
123+
)
124+
dock_root.app_name = "Dock"
125+
dock_root.window_screen_rect = [x_tl, y_tl, x_tl + w, y_tl + h]
126+
127+
extract_window(
128+
dock_root, "com.apple.dock", None,
129+
perform_hit_test=False, print_nodes=False, max_depth=max_depth
130+
)
131+
propagate_screen_rect(dock_root, dock_root.window_screen_rect)
132+
133+
screenshot_info = None
134+
if output_screenshot_dir:
135+
os.makedirs(output_screenshot_dir, exist_ok=True)
136+
137+
full_path = os.path.join(output_screenshot_dir, "dock_full.png")
138+
capture_full_screen(full_path)
139+
140+
crop_path = full_path.replace(".png", "_cropped.png")
141+
from macapptree.screenshot_app_window import crop_screenshot
142+
_ = crop_screenshot(full_path, (x_tl, y_tl, w, h), crop_path)
143+
144+
segmented_path = segment_window_components(dock_root, crop_path) or crop_path
145+
screenshot_info = {
146+
"app": "com.apple.dock",
147+
"window_name": "Dock",
148+
"cropped_screenshot_path": crop_path,
149+
"segmented_screenshot_path": segmented_path,
150+
}
151+
152+
return dock_root, screenshot_info
153+
finally:
154+
if original_mouse_pos:
155+
_restore_mouse_position(original_mouse_pos)

macapptree/main.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1-
import AppKit
2-
import ApplicationServices
31
import argparse
42
import json
53
import os
64
import time
7-
from PIL import ImageGrab, Image, ImageDraw, ImageFont
85

9-
from macapptree.apps import get_visible_windows_for_bundles
6+
import AppKit
7+
import ApplicationServices
8+
from PIL import Image, ImageDraw, ImageFont
9+
1010
import macapptree.apps as apps
11-
from macapptree.window_tools import store_screen_scaling_factor, segment_window_components
12-
from macapptree.uielement import UIElement, element_attribute, element_value
11+
from macapptree.dock_utils import DockCapture
1312
from macapptree.extractor import extract_window
14-
from macapptree.screenshot_app_window import screenshot_window_to_file, capture_full_screen, rect_subtract
15-
from macapptree.uielement import _flatten_ui_elements
16-
from macapptree.window_tools import propagate_screen_rect, _iou, _build_global_visible_index
17-
1813
from macapptree.menu_bar_utils import MenuBarCapture
19-
from macapptree.dock_utils import DockCapture
20-
21-
14+
from macapptree.screenshot_app_window import (
15+
capture_full_screen,
16+
screenshot_window_to_file,
17+
)
18+
from macapptree.uielement import (
19+
UIElement,
20+
_flatten_ui_elements,
21+
element_attribute,
22+
element_value,
23+
)
24+
from macapptree.window_tools import (
25+
_build_global_visible_index,
26+
_iou,
27+
propagate_screen_rect,
28+
segment_window_components,
29+
store_screen_scaling_factor,
30+
)
2231

2332

2433
def get_window_rect(window):
@@ -226,7 +235,7 @@ def main(app_bundles, output_accessibility_file, output_screenshot_dir, max_dept
226235

227236
if include_dock:
228237
print("Processing Dock…")
229-
dock = DockCapture(orientation="bottom", reveal=True, dwell=0.8)
238+
dock = DockCapture(dwell=0.8)
230239

231240
dock_root, dock_shots = dock.capture(max_depth, output_screenshot_dir)
232241
if dock_root:

macapptree/scale_utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Utility functions for image scaling calculations.
3+
4+
This module is intentionally dependency-free (no imports from window_tools
5+
or screenshot_app_window) to avoid circular import issues.
6+
"""
7+
import AppKit
8+
9+
10+
def get_image_scale_factor(image_width_px: int, target_width_points: float, eps: float = 0.1) -> float:
11+
"""
12+
Detects if an image is 1x or 2x (Retina) by comparing its pixel width
13+
to the target object's logical point width.
14+
15+
Args:
16+
image_width_px: The width of the image in pixels.
17+
target_width_points: The expected width of the target in logical points.
18+
eps: Tolerance for ratio comparison (default 0.1).
19+
20+
Returns:
21+
The detected scale factor (1.0 or 2.0), or the system's
22+
backingScaleFactor as a fallback.
23+
"""
24+
if target_width_points > 0:
25+
ratio = image_width_px / target_width_points
26+
if abs(ratio - 1.0) < eps:
27+
return 1.0
28+
elif abs(ratio - 2.0) < eps:
29+
return 2.0
30+
31+
# Fallback to system scale factor if target dimensions are invalid
32+
return AppKit.NSScreen.mainScreen().backingScaleFactor()

0 commit comments

Comments
 (0)