displayplacer: error rotating screen on M1 with Ventura

I’m on an M1 MacBook Pro running Ventura 13.0.1, with two external displays. Got the same error when I ran it from the brew install, and when i made the latest source (except for the extra enabled:true field).

I first tried displayplacer list after setting my display to 0" and 90" using System Settings. The last line of the output included this for 0:

"id:8E291BE3-E0C5-4282-9EFB-57B0F5311B8F res:1920x1080 hz:60 color_depth:8 enabled:true scaling:off origin:(-1080,840) degree:0"

and this for 90:

"id:8E291BE3-E0C5-4282-9EFB-57B0F5311B8F res:1080x1920 hz:60 color_depth:8 enabled:true scaling:off origin:(-1080,840) degree:90"

If I try to run displayplacer with either of those values, when the screen is rotated to the other value, I get an error like this:

Error rotating screen 8E291BE3-E0C5-4282-9EFB-57B0F5311B8F: (ipc/send) invalid destination port, code: 0x10000003
Screen ID 8E291BE3-E0C5-4282-9EFB-57B0F5311B8F: could not find res:1080x1920 hz:60 color_depth:8 scaling:off

and the screen doesn’t rotate. It does mess with the arrangement of the displays when I run this, so something is working…just not the rotation.

Possibly related: I also tried display_manager, which looks like it uses the same IOServiceRequestProbe call, and that also failed, with error code 268435459.

The only non-Apple solutions I’ve found that still work are Display Rotation Menu and BetterDisplay, but I don’t know what they’re calling.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 2
  • Comments: 15 (4 by maintainers)

Most upvoted comments

Here, this should do it I think. And it’s not M1 only, it should work as far back as 10.6. Amazing for a private api to remain so stable.

#import <Foundation/Foundation.h>
#include <ApplicationServices/ApplicationServices.h>

const int kMaxDisplays = 16;

@interface MPDisplayMode : NSObject

+ (id)modeWithDescription:(struct _CGSDisplayModeDescription *)arg1 forDisplay:(id)arg2;
@property(readonly) NSString *refreshString;
@property(readonly) NSString *resolutionString;
- (id)resolutionFormat;
@property(readonly) BOOL isSafeMode;
- (id)description;
- (void)getModeDescription:(struct _CGSDisplayModeDescription *)arg1;
- (BOOL)modeResolutionMatches:(id)arg1;
- (BOOL)resolutionMatches:(struct _CGSDisplayModeDescription *)arg1;
@property(readonly) unsigned int tvModeEquiv;
@property(readonly) unsigned int tvMode;
@property(readonly) BOOL isTVMode;
@property(readonly) BOOL isSimulscan;
@property(readonly) BOOL isInterlaced;
@property(readonly) BOOL isNativeMode;
@property(readonly) BOOL isDefaultMode;
@property(readonly) BOOL isStretched;
@property(readonly) BOOL isUserVisible;
@property(readonly) BOOL isHiDPI;
@property(readonly) BOOL isRetina;
@property(readonly) NSNumber *scanRate;
@property(readonly) int roundedScanRate;
@property(readonly) float scale;
@property(readonly) float aspectRatio;
@property(readonly) int fixPtRefreshRate;
@property(readonly) int refreshRate;
@property(readonly) int dotsPerInch;
@property(readonly) int vertDPI;
@property(readonly) int horizDPI;
@property(readonly) int pixelsHigh;
@property(readonly) int pixelsWide;
@property(readonly) int height;
@property(readonly) int width;
@property(readonly) int modeNumber;
@property(readonly) struct _CGSDisplayModeDescription *modeDescription;
- (void)dealloc;
- (id)initWithModeDescription:(struct _CGSDisplayModeDescription *)arg1 forDisplay:(id)arg2;

@end

@interface MPDisplay : NSObject

@property(readonly) BOOL hasRotationSensor; // @synthesize hasRotationSensor=_hasRotationSensor;
@property(retain) MPDisplayMode *defaultMode; // @synthesize defaultMode=_defaultMode;
@property(retain) MPDisplayMode *nativeMode; // @synthesize nativeMode=_nativeMode;
@property(retain) MPDisplayMode *currentMode; // @synthesize currentMode=_currentMode;
@property(readonly) BOOL hasZeroRate; // @synthesize hasZeroRate=_hasZeroRate;
@property(readonly) BOOL hasMultipleRates; // @synthesize hasMultipleRates=_hasMultipleRates;
@property(readonly) BOOL isSidecarDisplay; // @synthesize isSidecarDisplay=_isSidecarDisplay;
@property(readonly) BOOL isAirPlayDisplay; // @synthesize isAirPlayDisplay=_isAirPlayDisplay;
@property(readonly) BOOL isProjector; // @synthesize isProjector=_isProjector;
@property(readonly) BOOL is4K; // @synthesize is4K=_is4K;
@property(readonly) BOOL isTV; // @synthesize isTV=_isTV;
@property(readonly) BOOL isMirrorMaster; // @synthesize isMirrorMaster=_isMirrorMaster;
@property(readonly) BOOL isMirrored; // @synthesize isMirrored=_isMirrored;
@property(readonly) BOOL isBuiltIn; // @synthesize isBuiltIn=_isBuiltIn;
@property(readonly) BOOL isHiDPI; // @synthesize isHiDPI=_isHiDPI;
@property(readonly) BOOL hasTVModes; // @synthesize hasTVModes=_hasTVModes;
@property(readonly) BOOL hasSimulscan; // @synthesize hasSimulscan=_hasSimulscan;
@property(readonly) BOOL hasSafeMode; // @synthesize hasSafeMode=_hasSafeMode;
@property(readonly) BOOL isSmartDisplay; // @synthesize isSmartDisplay=_isSmartDisplay;
@property(nonatomic) int orientation; // @synthesize orientation=_orientation;
@property unsigned int userFlags; // @synthesize userFlags=_userFlags;
@property(readonly) int aliasID; // @synthesize aliasID=_aliasID;
@property(readonly) int displayID; // @synthesize displayID=_displayID;
- (BOOL)setActivePreset:(id)arg1;
@property(readonly) NSArray *presets;
@property(readonly) BOOL hasPresets;
- (void)buildPresetsList;
- (void)_loadPreviewIconFromServiceDictionary:(id)arg1;
- (id)_imageAndRect:(struct CGRect *)arg1 fromDictionary:(id)arg2 forOrientation:(long long)arg3;
- (id)_iconAtPath:(id)arg1;
@property(readonly) struct CGRect displayResolutionPreviewRect;
@property(readonly) NSImage *displayResolutionPreviewIcon;
@property(readonly) NSImage *displayIcon;
@property(readonly) NSUUID *uuid;
@property(readonly) struct CGRect hardwareBounds;
@property(readonly) int mirrorMasterDisplayID;
- (void)setPreferHDRModes:(BOOL)arg1;
@property(readonly) BOOL preferHDRModes;
@property(readonly) BOOL hasHDRModes;
@property(readonly) BOOL isForcedToMirror;
- (void)setMirrorMaster:(BOOL)arg1;
- (void)setMirrorMode:(id)arg1;
- (void)setMirrorModeNumber:(int)arg1;
- (int)setMode:(id)arg1;
- (int)setModeNumber:(int)arg1;
- (BOOL)isModeNative:(id)arg1;
- (BOOL)inDefaultMode;
- (id)modesForResolution:(id)arg1;
@property(readonly) NSArray *scanRateStrings;
@property(readonly) NSArray *scanRates;
- (id)allModes;
- (id)userModes;
@property BOOL bestForVideoMode;
- (BOOL)supportsBestForVideoMode;
@property int underscan;
@property(readonly) int maxUnderscan;
@property(readonly) int minUnderscan;
- (BOOL)supportsUnderscan;
@property BOOL overscanEnabled;
- (BOOL)supportsOverscan;
- (BOOL)canChangeOrientation;
- (BOOL)hasMultipleScanRates;
@property(readonly) struct CGRect displayBounds;
- (id)modeWithNumber:(int)arg1;
- (id)modeMatchingResolutionOfMode:(id)arg1 withScanRate:(id)arg2;
- (id)modesMatchingResolutionOfMode:(id)arg1;
- (id)modesOfType:(unsigned long long)arg1;
- (id)resolutionsOfType:(unsigned long long)arg1;
- (void)refreshModes;
- (void)refreshResolutions;
- (void)refreshResolutions:(id)arg1 usingModeList:(id)arg2;
- (id)scanRateForString:(id)arg1;
- (id)stringForScanRate:(id)arg1;
- (void)refreshScanRates;
- (void)determineTrimmedModeList;
- (void)bucketizeDisplayModes;
- (void)addMatchingModesToTrimmed;
- (void)addTVModesToPreferred;
- (BOOL)isAlias:(int)arg1;
- (id)multiscanModesForMode:(id)arg1;
@property(readonly) BOOL hasMenuBar;
@property(readonly) BOOL isAppleProDisplay;
@property(readonly) BOOL isBuiltInRetina;
@property(readonly) NSString *titleName;
@property(retain, nonatomic) NSString *displayName;
- (void)dealloc;
- (id)initWithCGSDisplayID:(int)arg1;

@end

int main(int argc, char *argv[]) {
    CGDirectDisplayID display[kMaxDisplays];
    CGDisplayCount numDisplays;
    CGDisplayErr err;
    err = CGGetOnlineDisplayList(kMaxDisplays, display, &numDisplays);
    if (err != CGDisplayNoErr) {
        printf("cannot get list of displays (error %d)\n", err);
        return -1;
    }
    
    for (CGDisplayCount i = 0; i < numDisplays; ++i) {
        CGDirectDisplayID dspy = display[i];
        CGDisplayModeRef mode = CGDisplayCopyDisplayMode(dspy);
        if (mode == NULL)
            continue;
    
            printf("display %d (id %d): ", i, dspy);
            if (CGDisplayIsMain(dspy))
                printf("main, ");
            printf("%sactive, %s, %sline, %s%s",
                CGDisplayIsActive(dspy) ? "" : "in",
                CGDisplayIsAsleep(dspy) ? "asleep" : "awake",
                CGDisplayIsOnline(dspy) ? "on" : "off",
                CGDisplayIsBuiltin(dspy) ? "built-in" : "external",
                CGDisplayIsStereo(dspy) ? ", stereo" : "");
            printf(", ID 0x%x\n", (unsigned int)dspy);
        
                CGRect bounds = CGDisplayBounds(dspy);
                printf("\tresolution %.0f x %.0f pt",
                    bounds.size.width, bounds.size.height);
                printf(" (%zu x %zu px)",
                    CGDisplayModeGetPixelWidth(mode),
                    CGDisplayModeGetPixelHeight(mode));
                double refreshRate = CGDisplayModeGetRefreshRate(mode);
                if (refreshRate != 0)
                    printf(" @ %.1f Hz", refreshRate);
                printf(", origin (%.0f, %.0f)\n",
                    bounds.origin.x, bounds.origin.y);
                CGSize size = CGDisplayScreenSize(dspy);
                printf("\tphysical size %.0f x %.0f mm",
                    size.width, size.height);
                double rotation = CGDisplayRotation(dspy);
                if (rotation)
                    printf(", rotated %.0f°", rotation);
                printf("\n\tIOKit flags 0x%x",
                    CGDisplayModeGetIOFlags(mode));
                printf("; IOKit display mode ID 0x%x\n",
                    CGDisplayModeGetIODisplayModeID(mode));
                if (CGDisplayIsInMirrorSet(dspy)) {
                    CGDirectDisplayID mirrorsDisplay = CGDisplayMirrorsDisplay(dspy);
                    if (mirrorsDisplay == kCGNullDirectDisplay)
                        printf("\tmirrored\n");
                    else
                        printf("\tmirrors display ID 0x%x\n", mirrorsDisplay);
                }
                
                printf("\t%susable for desktop GUI%s\n",
                    CGDisplayModeIsUsableForDesktopGUI(mode) ? "" : "not ",
                    CGDisplayUsesOpenGLAcceleration(dspy) ?
                    ", uses OpenGL acceleration" : "");
        CGDisplayModeRelease(mode);
        
        MPDisplay* mpdisplay = [[MPDisplay alloc] initWithCGSDisplayID:dspy];
        printf("Rotation: %d", [mpdisplay orientation]);
        [mpdisplay setOrientation: 90];
        break;
        
        
    }
}

compile with -fobjc-arc -framework MonitorPanel -F /System/Library/PrivateFrameworks

Probably to make it cleaner you’d want to use the proper dumped structs as in https://github.com/alin23/mac-utils/blob/main/Headers/MonitorPanel.framework/Headers/CDStructures.h

In fact a lot of the above methods can be used in displayplacer to do things at a higher level than CGS calls. setMode is really interesting, it allows you to control resolution, refresh rate, with, height, etc.

One benefit of doing it via SLSSetDisplayRotation though is it can probably force orientation even when MonitorPanel frameworks thinks canChangeOrientation is false. Even MBP internal screen has canChangeOrientation = true so I’m not sure what sort of displays would have it false. Maybe virtual displays like sidecar?

Now I can replace two really messy AppleScript automations I had hacked together! (One for when running a single external display, the other when running dual displays) WOOOPP

MONITOR=`displayplacer list | grep -o "id.*1920" | cut -d' ' -f1 | cut -d':' -f2`

if displayplacer list | grep -c "res:1920x1200.*current"
then
	displayplacer "id:$MONITOR res:1200x1920 degree:90"
else
	displayplacer "id:$MONITOR res:1920x1200 degree:0"
fi

Latest works a treat - amazing! Thanks @jakehilborn and @krackers!

I had an old version at that was taking priority on my PATH

/usr/local/bin/displayplacer --version
displayplacer v1.3.0-dev

Developer: Jake Hilborn
GitHub: https://github.com/jakehilborn/displayplacer
LinkedIn: https://www.linkedin.com/in/jakehilborn
Email: jakehilborn@gmail
❯ displayplacer "id:F56E02CD-5383-4A80-A127-34DDEFC68E97 res:1200x1920 hz:60 color_depth:8 enabled:true scaling:off origin:(0,0) degree:90"
❯ displayplacer "id:F56E02CD-5383-4A80-A127-34DDEFC68E97 res:1200x1920 hz:60 color_depth:8 enabled:true scaling:off origin:(0,0) degree:0"
Error rotating screen F56E02CD-5383-4A80-A127-34DDEFC68E97: (ipc/send) invalid destination port, code: 0x10000003