hammerspoon
hammerspoon copied to clipboard
Screen rotation does not work in Monterey (12.1) on an M1 Max
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.
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.
For reference, I'm able to use these apps to rotate screens:
- https://magesw.com/displayrotation/ (1.5 Beta)
- https://github.com/alin23/Lunar
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 - 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
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;
}