'How to zoom the screen programmatically with private coregraphic methods (how to find function arguments)
as you may know Mac OS X has a pretty nice screen zooming functionality built in, which can be enabled in System Preferences => Universal Access.
Now I'm trying to trigger this screen zooming programatically. I can do it by sending keyboard shortcuts, but it's not as fluid as it could be. So I did some research on how the system does this.
There seem to be a few private core graphics methods that can do what I want but I'm unable to find what arguments they take.
These are the functions I have found:
- CGSZoomPoint
- CGSUnzoomPoint
- CGSGetZoomParameters
- CGSSetZoomParameters
- CGSIsZoomed
Some people already reverse engineered some of the private methods of the core graphics framework and created headers for them. (see e.g. http://pwproject.googlecode.com/svn/trunk/Mac%20OS%20X/Vector%20Grab/ ) Unfortunately the methods I seem to need are missing. (CGSIsZoomed, is actually included
CG_EXTERN CGError CGSIsZoomed(CGSConnectionID cid, bool *outIsZoomed);
)
I have also found an app which seems to be able to zoom the screen and this seems to be the relevant disassembly, but I don't know much about assembly, thus I can't read it ;-(
0000000100015c21 callq 0x10002c212 ; symbol stub for: _CGEventCreate
0000000100015c26 movq %rax,%rbx
0000000100015c29 movq %rbx,%rdi
0000000100015c2c callq 0x10002c22a ; symbol stub for: _CGEventGetLocation
0000000100015c31 movsd %xmm0,0xffffff48(%rbp)
0000000100015c39 movsd %xmm1,0xffffff58(%rbp)
0000000100015c41 movsd %xmm0,0xa8(%rbp)
0000000100015c46 movsd %xmm1,0xb0(%rbp)
0000000100015c4b movq %rbx,%rdi
0000000100015c4e callq 0x10002c362 ; symbol stub for: _CFRelease
0000000100015c5a jle 0x100015d01
0000000100015c60 movq 0x00098a71(%rip),%rax
0000000100015c67 movq 0x18(%rax),%rcx
0000000100015c6b movq %rcx,0x18(%rsp)
0000000100015c70 movq 0x10(%rax),%rcx
0000000100015c74 movq %rcx,0x10(%rsp)
0000000100015c79 movq (%rax),%rcx
0000000100015c7c movq 0x08(%rax),%rax
0000000100015c80 movq %rax,0x08(%rsp)
0000000100015c85 movq %rcx,(%rsp)
0000000100015c89 movsd 0xffffff48(%rbp),%xmm0
0000000100015c91 movsd 0xffffff58(%rbp),%xmm1
0000000100015c99 callq 0x10002c266 ; symbol stub for: _CGRectContainsPoint
0000000100015c9e cmpl $0x02,0x00098a43(%rip)
0000000100015ca5 jl 0x100015d01
0000000100015ca7 movl $0x 00000001,%r15d
0000000100015cad movl $0x00000020,%ebx
0000000100015cb2 movsd 0xa8(%rbp),%xmm0
0000000100015cb7 movsd 0xb0(%rbp),%xmm1
0000000100015cbc movq 0x00098a15(%rip),%rax
0000000100015cc3 movq 0x18(%rax,%rbx),%rcx
0000000100015cc8 movq %rcx,0x18(%rsp)
0000000100015ccd movq 0x10(%rax,%rbx),%rcx
0000000100015cd2 movq %rcx,0x10(%rsp)
0000000100015cd7 movq (%rax,%rbx),%rcx
0000000100015cdb movq 0x08(%rax,%rbx),%rax
0000000100015ce0 movq %rax,0x08(%rsp)
0000000100015ce5 movq %rcx,(%rsp)
0000000100015ce9 callq 0x10002c266 ; symbol stub for: _CGRectContainsPoint
0000000100015cee addq $0x20,%rbx
0000000100015cf2 incq %r15
0000000100015cf5 movslq 0x000989ec(%rip),%rax
0000000100015cfc cmpq %rax,%r15
0000000100015cff jl 0x100015cb2
0000000100015d01 movsd 0xc0(%rbp),%xmm0
0000000100015d06 movsd %xmm0,0xffffff58(%rbp)
0000000100015d0e leaq 0xa8(%rbp),%rsi
0000000100015d12 leaq 0x98(%rbp),%rdx
0000000100015d16 movl %r14d,%edi
0000000100015d19 callq 0x10002c296 ; symbol stub for: _CGSZoomPoint
0000000100015d1e movsd 0xffffff58(%rbp),%xmm2
0000000100015d26 testl %eax,%eax
0000000100015d28 jne 0x100015d99
0000000100015d2a movapd %xmm2,%xmm0
0000000100015d2e mulsd 0xffffff60(%rbp),%xmm0
0000000100015d36 addsd 0x98(%rbp),%xmm0
0000000100015d3b movsd 0x00017255(%rip),%xmm1
0000000100015d43 ucomisd %xmm0,%xmm1
0000000100015d47 ja 0x100015d78
0000000100015d49 ucomisd 0x0001749f(%rip),%xmm0
0000000100015d51 ja 0x100015d78
0000000100015d53 mulsd 0xffffff50(%rbp),%xmm2
0000000100015d5b addsd 0xa0(%rbp),%xmm2
0000000100015d60 movsd 0x00017230(%rip),%xmm0
0000000100015d68 ucomisd %xmm2,%xmm0
0000000100015d6c ja 0x100015d78
0000000100015d6e ucomisd 0x00017482(%rip),%xmm2
0000000100015d76 jbe 0x100015d99
0000000100015d78 movzbl 0xbf(%rbp),%edx
0000000100015d7c andl $0x01,%edx
0000000100015d7f movsd 0xc0(%rbp),%xmm0
0000000100015d84 leaq 0xc8(%rbp),%rsi
0000000100015d88 movl %r14d,%edi
0000000100015d8b movl $0x 00000001,%ecx
0000000100015d90 xorpd %xmm1,%xmm1
0000000100015d94 callq 0x10002c290 ; symbol stub for: _CGSSetZoomParameters
0000000100015d99 movsd 0xffffff70(%rbp),%xmm1
0000000100015da1 addsd 0xffffff50(%rbp),%xmm1
0000000100015da9 movsd 0xffffff68(%rbp),%xmm0
0000000100015db1 addsd 0xffffff60(%rbp),%xmm0
0000000100015db9 movb 0x000988f1(%rip),%al
0000000100015dbf xorl %edi,%edi
0000000100015dc1 cmpb $0x01,%al
0000000100015dc3 jne 0x100015dde
0000000100015dc5 movl $0x 00000006,%esi
0000000100015dca xorl %edx,%edx
If anyone has any idea how to use those private functions you'd make my week :-)
Best, Thomas
Solution 1:[1]
Well this question has been a long time without an answer, but it so happens I just figured out how to use CGSGetZoomParameters
and CGSSetZoomParameters
for my own work, and they both can be used to control and retrieve information about the zoomed state of the screen. The function definitions are:
CG_EXTERN CGError CGSGetZoomParameters(CGSConnectionID cid, CGPoint *origin, double * zoomFactor, int8_t *smoothed);
CG_EXTERN CGError CGSSetZoomParameters(CGSConnectionID cid, CGPoint *origin, double zoomFactor, int8_t smoothed);
where origin
determines what section of the screen is zoomed using the following method:
For zooming a region as far left as possible, set origin.x
to: (desktopWidth / 2) / zoomFactor
For zooming a region as far right as possible, set origin.x
to: desktopWidth - ((desktopWidth / 2) / zoomFactor)
For zooming a region as far up as possible, set origin.y
to: (desktopHeight / 2) / zoomFactor
For zooming a region as far right as possible, set origin.y
to: desktopHeight - ((desktopHeight / 2) / zoomFactor)
For desktopWidth
and desktopHeight
is the total width / height of the bounding rectangle of the current desktop, including all displays in their current arrangement. So if you have two 1080p displays arranged exactly horizontally, then the total desktop width and height is 3840x1080.
For zooming an intermediate area, use a value for origin
that is somewhere between the two extremums. The math works out so that the number of integer points between them corresponds exactly to the number of positions a zoomed screen can have for any given zoomFactor
.
These functions are low level and will interfere with the standard macOS accessibility zooming features. For example, if your screen is unzoomed, you the zoom it in using CGSSetZoomParameters
, and then use one the accessibility features like holding Control and scrolling the mouse wheel to change the zoom, they will work as though the screen is unzoomed. Furthermore, when the screen is zoomed, it cannot be panned using the mouse regardless of how the accessibility zoom features are configured, so be careful! You could cause the screen to zoom in and leave the user with no good way to zoom out again.
I'm not sure how CGSZoomPoint
or CGSUnzoomPoint
are used -- I couldn't get them to have any effect. But as said, low level control of screen zooming can be accomplished with the other two functions.
As requested, here is an example of how to use this function:
typedef int CGSConnectionID;
CG_EXTERN CGSConnectionID CGSMainConnectionID(void);
CG_EXTERN CGError CGSGetZoomParameters(CGSConnectionID cid, CGPoint *origin, double *zoomFactor, int8_t *smoothed);
CG_EXTERN CGError CGSSetZoomParameters(CGSConnectionID cid, CGPoint *origin, double zoomFactor, int8_t smoothed);
CGRect boundingBoxOfAllDisplays() {
CGDirectDisplayID displays[50]; // Adjust if use case somehow could involve more than 50 displays
uint32_t displayCount;
CGRect result = {0,0,0,0};
CGGetActiveDisplayList(50, displays, &displayCount);
for(uint32_t i = 0; i < displayCount; ++i) {
result = CGRectUnion(result, CGDisplayBounds(displays[i]));
}
return result;
}
CGFloat lerp(CGFloat a, CGFloat b, CGFloat u) {
return (b - a) * u + a;
}
void zoomPoint(CGPoint p, double factor) {
CGRect bounds = boundingBoxOfAllDisplays();
if (bounds.size.width == 0 || bounds.size.height == 0) { // Being paranoid
return;
}
CGFloat ux = (p.x - bounds.origin.x) / bounds.size.width;
CGFloat uy = (p.y - bounds.origin.y) / bounds.size.height;
CGPoint origin = CGPointMake(lerp(bounds.size.width / (2 * factor),
bounds.size.width - bounds.size.width / (2 * factor),
ux),
lerp(bounds.size.height / (2 * factor),
bounds.size.height - bounds.size.height / (2 * factor),
uy));
CGSSetZoomParameters(CGSMainConnectionID(), &origin, factor, 1);
}
zoomPoint()
will zoom the screen around the given point such that it will remain in the same physical point on the screen after zooming, much like how the mouse cursor remains in the same place when zooming the screen by holding control and using the mouse wheel.
Note that because this is a Core Graphics function, the coordinates must be relative to the upper-left corner of the main display. The coordinate system used in Cocoa functions are relative to the lower-left corner of the main display, so functions like [NSEvent mouseLocation]
will return a point in the wrong coordinate system and must be flipped first.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |