-
Notifications
You must be signed in to change notification settings - Fork 2
Fix/dock orientation #3
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
base: main
Are you sure you want to change the base?
Conversation
mshamrai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the PR! We really appreciate the collaboration.
Can you please elaborate on the change in scaling factor calculation?
|
|
||
|
|
||
| def crop_screenshot(image_path, window_coords, output_path): | ||
| backing_scale_factor = AppKit.NSScreen.mainScreen().backingScaleFactor() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you like this function? It should return the scaling factor, there is no need to compute it manually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I avoided backingScaleFactor because it introduces a critical bug in multi-monitor setups. NSScreen.mainScreen() returns the properties of the primary display (e.g., the MacBook), regardless of where the window is actually located.
I verified this with a physical test case using a Retina MacBook (Main, 2.0x) and an LG External Monitor (Extended, 1.0x).
- I moved the Calculator app to the external monitor (1.0x).
mainScreen().backingScaleFactor()still returned2.0because the Mac is the primary display.- The script applied 2x math to the 1.0x image, resulting in a broken/blank screenshot because the crop area was doubled and shifted out of bounds.
By calculating the scale manually, we ensure the math matches the actual monitor the window is on.
macapptree/window_tools.py
Outdated
| if scale_factor is None: | ||
| # Heuristic: Compare image width vs window element width (points) to detect 1x vs 2x. | ||
| # If explicit detection fails, fallback to the screen's backing scale factor. | ||
| current_scaling_factor = _screen_scaling_factor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why don't you like the backing scale factor? Did you have problems with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the problem is that backingScaleFactor is environment-dependent, whereas measuring the image is data-dependent (and therefore reliable).
Since macOS creates screenshots based on the target display's resolution, the image file itself is the source of truth. My implementation divides the Image Width (px) by the Window Width (pts) to derive the exact multiplier used for that specific capture.
- Window on Retina: 460px / 230pts = 2.0
- Window on External: 230px / 230pts = 1.0
This makes the logic display-agnostic. It works correctly even in mixed-DPI setups, Sidecar, or potentially headless CI environments where mainScreen might report default values that don't match the virtual framebuffer.
mshamrai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure, that your implementation will return needed value in the case you mention. Unless you find the screen where the window is.
And can you please create a function for the scale_factor calculation in order to reduce code duplicates? You may put it in window_tools.py file.
Much appreciated!
macapptree/screenshot_app_window.py
Outdated
|
|
||
|
|
||
| # Get main screen width (Points) to determine ratio | ||
| screen = AppKit.NSScreen.mainScreen() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But here you also use AppKit.NSScreen.mainScreen(), which returns the main screen. It looks like in this case your implementation would return the same value of the scale_factor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback. You're right. Comparing the image width to mainScreen in crop_screenshot was incorrect because it re-introduced the same fragility as backingScaleFactor (comparing a window object to a screen reference).
I pushed a refactor that addresses both points:
- Shared Helper: moved the scale calculation logic to a reusable function
get_image_scale_factorinwindow_tools.pyto remove the code duplication - Logic Fix: In
screenshot_app_window.py, updated the logic to pass the window's logical width as the reference target instead of the screen width
# New logic
scale_factor = get_image_scale_factor(img_width, window_width)Ensures the ratio is calculated strictly as Window Pixels / Window Points which guarantees the correct scale (1.0 vs 2.0) regardless of which monitor the window is on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good! I think we almost done here. Only some refactoring remains
macapptree/screenshot_app_window.py
Outdated
| frame = screen.frame() | ||
| screen_width_points = frame.size.width | ||
| # Local import to avoid circular dependency | ||
| from macapptree.window_tools import get_image_scale_factor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the local import is a good practice. Can you please refactor it to avoid local imports and circular dependencies? Maybe move it to a new file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Created scale_utils.py as a standalone module with no dependencies on window_tools or screenshot_app_window. Both files now import from it at the top level.
macapptree/screenshot_app_window.py
Outdated
| # But get_image_scale_factor handles logic. | ||
| # Let's check get_image_scale_factor implementation again. | ||
| # I changed it to return None instead of global _screen_scaling_factor because _screen_scaling_factor is not available here. | ||
| # So I need to import AppKit here to get fallback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments look totally unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
macapptree/screenshot_app_window.py
Outdated
| # Let's check get_image_scale_factor implementation again. | ||
| # I changed it to return None instead of global _screen_scaling_factor because _screen_scaling_factor is not available here. | ||
| # So I need to import AppKit here to get fallback. | ||
| scale_factor = AppKit.NSScreen.mainScreen().backingScaleFactor() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this logic is already incapsulated in the function, so I think you should not have it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed. The fallback is now handled inside get_image_scale_factor() so callers don't need to check for None.
macapptree/window_tools.py
Outdated
| """ | ||
| if target_width_points > 0: | ||
| ratio = image_width_px / target_width_points | ||
| if abs(ratio - 1.0) < 0.1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please introduce new eps input variable with the default 0.1 instead of using constant as is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done. Added eps: float = 0.1 as a parameter.
macapptree/window_tools.py
Outdated
| elif abs(ratio - 2.0) < 0.1: | ||
| current_scaling_factor = 2.0 | ||
| target_width = bx2 - bx | ||
| except Exception: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exceptions are you expecting here? It looks like it is unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed. Replaced with a simple if hasattr() check.
crawlmacos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good enough. Thank you
- Create scale_utils.py with get_image_scale_factor function - Add eps parameter for ratio tolerance (default 0.1) - Move fallback logic into the function - Remove local imports and unnecessary try/except - Remove debug comments - Fix unused variable warning (img_height -> _)
d635d20 to
1b3c966
Compare
Summary
This PR addresses several issues preventing reliable Dock screenshots on macOS, specifically on Retina displays and systems with non-standard Dock configurations.
1. Hardcoded Dock Configuration
Problem: The previous implementation assumed the Dock was always on the
bottomand set toautohide=True. This caused failures for users with the Dock on the left/right or always visible.Solution:
get_dock_orientation(): Executesdefaults read com.apple.dock orientationto detect if the Dock isleft,right, orbottom.get_dock_autohide(): Executesdefaults read com.apple.dock autohide.DockCaptureclass now defaults to these system settings instead of hardcoded values.2. Unreliable Dock Reveal
Problem: The mouse movement logic used a small offset (e.g.,
screen_width // 2, screen_height - 2), which often failed to trigger the Dock reveal animation reliably.Solution:
0orscreen_height - 1).try/finallyblock to ensure the mouse cursor is restored to its original position even if capture fails.3. Low-Resolution & Misaligned Screenshots
Problem:
capture_full_screenused abboxargument which inadvertently causedImageGrabto return a downscaled 1x image on Retina displays (logical points instead of physical pixels).crop_screenshotcaused "double-cropping" (capturing empty space) when applied to these 1x images.Solution:
bboxargument to enable native Retina (2x) resolution (e.g., 3024x1964).crop_screenshotandsegment_imageto dynamically calculate the scale factor (by comparing image pixels to screen points). This ensures correct cropping and bounding box alignment regardless of whether the source image is 1x or 2x.