'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