hammerspoon icon indicating copy to clipboard operation
hammerspoon copied to clipboard

Screen rotation does not work in Monterey (12.1) on an M1 Max

Open bkudria opened this issue 3 years ago • 5 comments

Hammerspoon Version 0.9.93 (6148)

> hs.screen.allScreens()[2]:rotate()
90

> hs.screen.allScreens()[2]:rotate(0)
false -- and the screen does not rotate

> hs.screen.allScreens()[2]:rotate()
90

Rotating using macOS display settings works without issue.

bkudria avatar Dec 20 '21 20:12 bkudria

I am experiencing the same issue unfortunately. I'm on an M1 Pro 14" Macbook Pro running macOS 12.0.1 (Monterey).

> hs.screen.allScreens()[2]:rotate()
270

> hs.screen.allScreens()[2]:rotate(0)
false

> hs.screen.allScreens()[2]:rotate(90)
false

> hs.screen.allScreens()[2]:rotate(180)
false

> hs.screen.allScreens()[2]:rotate(270)
false

Of course, I can rotate in macOS settings just fine.

AaronBeaudoin avatar Feb 14 '22 16:02 AaronBeaudoin

For reference, I'm able to use these apps to rotate screens:

  • https://magesw.com/displayrotation/ (1.5 Beta)
  • https://github.com/alin23/Lunar

bkudria avatar Feb 14 '22 20:02 bkudria

Looking at the code for Lunar, it seems like it's doing the rotation using DDC, which I didn't expect.

The basic issue here is that the ARM version of macOS uses a different set of underlying display code than the Intel version does, so the various APIs (particularly the private API) we use for things like screen rotation, no longer work.

I don't have a great solution for this yet - pulling a complete DDC implementation in just for display rotation is quite a big step, although it would allow us more control of other aspects of external monitors.

cmsj avatar Mar 04 '22 18:03 cmsj

@cmsj - This is a little bit above my pay grade, but it looks like it's possible using IORegistryEntryCreateCFProperty and IOFBTransform, if this means anything to you?

https://opensource.apple.com/source/IOGraphics/IOGraphics-330/IOGraphicsFamily/IOKit/graphics/IOGraphicsTypesPrivate.h.auto.html

latenitefilms avatar Mar 13 '22 10:03 latenitefilms

SLSSetDisplayRotation seems to be what magesw's imagerotation uses. Here's the decompiled pseudocode

-(void)rotateDisplay:(long long)arg2 to:(unsigned int)arg3 {
    rcx = arg3;
    rdx = arg2;
    rdi = self;
    if (*qword_100008dd8 != 0x0) { // SLSSetDisplayRotation
            r12 = rcx;
            r14 = rdx;
            r15 = rdi;
            rbx = 0x1;
            if (*(int8_t *)&rdi->_shouldForceRotation == 0x0) {
                    rbx = [NSEvent modifierFlags] >> 0x13 & 0x1;
            }
            var_54 = 0x8;
            rax = CGGetActiveDisplayList(0x8, &var_50, &var_54);
            if (rax == 0x0) {
                    rax = var_54;
                    if (rax >= 0x9) {
                            rax = 0x8;
                    }
                    if (rax > r14) {
                            r13 = *(int32_t *)(rbp + (r14 * 0x4 - 0x50));
                            rax = [r15 displayIDSupportsRotation:r13]; 
                            r12 = (ROL(r12, 0x1c)) - 0x3;
                            if (rax != 0x0) {
                                    (*qword_100008dd8)(r13); // SLSSetDisplayRotation
                            }
                            else {
                                    if (rbx != 0x0) {
                                            (*qword_100008dd8)(r13);
                                            [r15 confirmRotationForDisplay:r14];
                                    }
                            }
                    }
            }
    }
    if (**___stack_chk_guard != **___stack_chk_guard) {
            __stack_chk_fail();
    }
    return;
}

And here's how current rotation info is retrieved

-(long long)rotationofDisplayID:(unsigned int)arg2 {
    r14 = self;
    r15 = CGDisplayIOServicePort(arg2);
    rax = IORegistryEntryCreateCFProperty(r15, @"IOFBProbeOptions", **_kCFAllocatorDefault, 0x0);
    if (rax != 0x0) {
            var_2C = 0x0;
            CFNumberGetValue(rax, 0x3, &var_2C);
            CFRelease(rax);
            if ((*(int8_t *)(&var_2C + 0x1) & 0x4) == 0x0) {
                    COND = *(int8_t *)&r14->_shouldForceRotation == 0x0;
                    if (!COND) {
                            rax = 0x0;
                    }
                    else {
                            [NSEvent modifierFlags];
                            rax = 0x7fffffffffffffff;
                            if (COND) {
                                    rax = 0x0;
                            }
                    }
            }
            else {
                    rax = IORegistryEntryCreateCFProperty(r15, @"IOFBTransform", **_kCFAllocatorDefault, 0x0);
                    if (rax != 0x0) {
                            var_2C = 0x0;
                            CFNumberGetValue(rax, 0x3, &var_2C);
                            CFRelease(rax);
                            rbx = 0xf0 & var_2C;
                    }
                    else {
                            rbx = 0x0;
                    }
                    rax = rbx;
            }
    }
    else {
            COND = *(int8_t *)&r14->_shouldForceRotation == 0x0;
            if (!COND) {
                    rax = 0x0;
            }
            else {
                    [NSEvent modifierFlags];
                    rax = 0x7fffffffffffffff;
                    if (COND) {
                            rax = 0x0;
                    }
            }
    }
    return rax;
}

krackers avatar Jul 17 '22 20:07 krackers