The usual method of using Quartz Debug doesn't seem to work anymore.
4 Answers
Running
sudo defaults write /Library/Preferences/com.apple.windowserver.plist DisplayResolutionEnabled -bool true
nd restarting worked for me:
I only tested it with the internal display of an iMac though.
-
1
-
Doesn't work for me either. Trying to get an SEIKI 39UY04 be be readable. Commented Feb 18, 2014 at 18:40
-
It's works for me , macbook pro early 2008 and mavericks– user91306Commented Sep 19, 2014 at 17:10
-
Worked on 10.9.5 MacBook Pro (late 2011) HD3000 with Dell P2815Q 1920x1080 @2x (3840x2160 native). Used "Display Menu" from app store to switch to mode. Commented Jan 22, 2015 at 22:17
-
Not working for an external Acer Predator connected over HDMI on Mojave– Andy RayCommented Nov 15, 2018 at 17:00
actually it works on external displays such as TVs, but the list in System Preferences is limited. you can try two things:
- either hold "alt" when clicking "Scaled" button, or
- install one of the menu utilities such as http://www.resolutiontab.com/
the first one didn't work for me as it kept only showing "tv friendly" resolutions (720p, 1080p and 1080i). the second did indeed show 960x540 HiDPI i was after for my tv.
-
1I have a Dell P2415Q 4K monitor connected via DisplayPort/Thunderbolt, and holding alt revealed a previously hidden option called "1920 x 1080" that is actually 3840 x 2160 in HiDPI mode. Commented Dec 12, 2014 at 22:16
-
It seems to me that OSX is preventing TVs (or the other way around) from using HiDPI on non-scaling mode. For example if your TV is 1080p, the 960 X 540 HiDPI won't work, but 1280 X 720 HiDPI mode will (but it will be blurry because it is really running 2560 X 1440 on a 1920 X 1080 display).
Alright, here we go: reversing this from the top-down, and explaining why it doesn't work for some people. All reversing done on 10.9, but the gist should hold for later versions as well.
Let's start at the end. In system preferences - displays
, all core logic is handled by MonitorPanel.framework
private framework.
Within it, we have the core logic to determine what name to assign to a given resolution. Note this is not what determines what resolutions are available, this is simply the user-facing string, but we start here because it explains the behavior some people mentioned before:
void * -[MPDisplayMode resolutionFormat](void * self, void * _cmd) {
rbx = self;
rax = [self tvMode];
if (LODWORD(rax) <= 0x6) goto loc_2883;
goto loc_283b;
loc_2883:
LODWORD(rcx) = LODWORD(rax);
rax = @"480iString";
rcx = sign_extend_64(*(int32_t *)(0x2a24 + rcx * 0x4));
switch (rax) {
case 0:
goto loc_289c
case 1:
goto loc_2a19
case 2:
goto loc_2914
case 3:
goto loc_2920
case 4:
goto loc_292c
case 5:
goto loc_2938
case 6:
goto loc_2944
}
loc_289c:
rax = [rbx->_display is4K];
if (LOBYTE(rax) != 0x0) {
rax = [rbx->_display isModeNative:rbx];
if (LOBYTE(rax) == 0x0) {
r15 = *objc_msgSend;
LOBYTE(r14) = LOBYTE([rbx isHiDPI]);
LOBYTE(rcx) = LOBYTE([rbx isInterlaced]);
if (LOBYTE(r14) != 0x0) {
if (LOBYTE(rcx) != 0x0) {
rax = @"InterlacedString";
}
else {
rax = @"ResolutionString";
}
}
else {
rdx = @"LoDPIInterlacedString";
rax = @"LoDPIString";
if (LOBYTE(rcx) != 0x0) {
rax = rdx;
}
}
}
else {
rax = [rbx isStretched];
if (LOBYTE(rax) != 0x0) {
rax = @"StretchedString";
}
else {
rax = [rbx isSimulscan];
if (LOBYTE(rax) != 0x0) {
rax = @"SimulscanString";
}
else {
rax = [rbx isInterlaced];
if (LOBYTE(rax) != 0x0) {
rax = @"InterlacedString";
}
else {
rax = @"ResolutionString";
}
}
}
}
}
else {
rax = [rbx isHiDPI];
if (LOBYTE(rax) != 0x0) {
rax = [rbx->_display isHiDPI];
if (LOBYTE(rax) == 0x0) {
LOBYTE(rcx) = LOBYTE([rbx isInterlaced]);
rdx = @"HiDPIString";
rax = @"HiDPIInterlacedString";
if (LOBYTE(rcx) == 0x0) {
rax = rdx;
}
}
else {
rax = @"ResolutionString";
}
}
else {
rax = [rbx isStretched];
if (LOBYTE(rax) != 0x0) {
rax = @"StretchedString";
}
else {
rax = [rbx isSimulscan];
if (LOBYTE(rax) != 0x0) {
rax = @"SimulscanString";
}
else {
rax = [rbx isInterlaced];
if (LOBYTE(rax) != 0x0) {
rax = @"InterlacedString";
}
else {
rax = @"ResolutionString";
}
}
}
}
}
loc_2a19:
return rax;
loc_2914:
rax = @"480pString";
goto loc_2a19;
loc_2920:
rax = @"720iString";
goto loc_2a19;
loc_292c:
rax = @"720pString";
goto loc_2a19;
loc_2938:
rax = @"1080iString";
goto loc_2a19;
loc_2944:
rax = @"1080pString";
goto loc_2a19;
loc_283b:
rax = [rbx->_display is4K];
if (LOBYTE(rax) != 0x0) {
rax = [rbx->_display isModeNative:rbx];
if (LOBYTE(rax) == 0x0) {
rax = @"LoDPIString";
}
else {
rax = @"ResolutionString";
}
}
else {
rax = @"ResolutionString";
}
goto loc_2a19;
}
First off, note that TV and non-TV cases are handled separately. If the display is detected as a tv mode (can check in system profiler), no hidpi suffix will be shown (it's also filtered out by default, more on that in a bit). Note that if your display is erroneously detected as a tv, then you can fix it with an edid override. Search online about how to force rgb mode.
Also note that if it's detected as a display that natively supports hiDPI (which fyi is determined by checking if the "native resolution" of a display is a hiDPI resolution), then it will not show the (hiDPI)
suffix in the string even though when selected the computer will still use hidpi rendering.
Now we get to the question of what resolutions are shown. This part is mostly handled by CGS, but syspref doesn't present all of them to you. When you hold option, it chooses between displaying userModes vs. trimmedModes. userModes basically filters the display modes list given by CGS based on a bit in the flag field of CGSDisplayModeDescription struct that tells it whether this mode is user-visible or not. trimmedModes goes further and eliminates more things, based on logic that I don't care to reverse.
This is why, for instance, you see that when using an app like RDM it presents more resolutions than what you see even when holding option in system preferences.
Also note that the list of "raw resolutions" can be influenced by setting a display override. This site is great for generating the display override file, and is basically necessary if you want to use a hidpi mode with a non-standard resolution. E.g. if you have a 1440p display, and want to use hidpi with an effectively scale of 1300p or something, then you will need to use a display override. (This is also how things like BetterDisplay work). SwitchResX is also another tool for creating display overrides.
As mentioned before though, even though a raw resolution is offered doesn't mean it's visible in system preferences. You can either use RDM to work around this, or patch -[MPDisplayMode isUserVisible]
in MonitorPanel.framework
to always return true.
Below is some code you can use to play around with MonitorPanel.framework:
#import <Foundation/Foundation.h>
#include <ApplicationServices/ApplicationServices.h>
const int kMaxDisplays = 16;
typedef struct {
uint32_t mode; // 0
uint32_t flags; // 4
uint32_t width; // 8
uint32_t height; // 12
uint32_t depth; // 16
uint32_t dc2[42];
uint16_t dc3;
uint16_t freq;
uint8_t dc4[16];
CGFloat scale;
} CGSDisplayModeDescription;
void print_binary(unsigned int number)
{
if (number >> 1) {
print_binary(number >> 1);
}
putc((number & 1) ? '1' : '0', stdout);
}
@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];
[mpdisplay setValue:0 forKey:@"trimmedModes"];
print_binary(0x3fffffff);
printf("\n");
printf("%d", (((CGSDisplayModeDescription *) [[mpdisplay modeWithNumber:500] modeDescription])->flags) & kDisplayModeNeverShowFlag);
//NSLog(@"%@",[(MPDisplayMode*)[mpdisplay modeWithNumber:500] valueForKey:@"desc"]);
//printf("%x", (long) ((CGSDisplayModeDescription *) [[mpdisplay modeWithNumber:500] modeDescription])->flags);
//[[mpdisplay modeWithNumber:500] isUserVisible];
break;
}
}
To summarize, to get hidpi working in addition to setting DisplayResolutionEnabled
, you'll want to use either RDM or a patched MonitorPanel.framework
to check the list of resolutions seen by the system, and use a display override to add the 2x density resolution if needed.