├── cmdline.h ├── monitor.png ├── Resources ├── StatusIcon.png ├── Icon_512x512.png ├── StatusIcon@2x.png ├── StatusIcon_sel.png ├── StatusIcon_sel@2x.png └── Credits.rtf ├── SRApplicationDelegate.h ├── utils.mm ├── ResMenuItem.h ├── utils.h ├── Info.plist ├── main.mm ├── README.md ├── Makefile ├── ResMenuItem.mm ├── SRApplicationDelegate.mm └── cmdline.mm /cmdline.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | int cmdline_main(int argc, const char * argv[]); 4 | -------------------------------------------------------------------------------- /monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/monitor.png -------------------------------------------------------------------------------- /Resources/StatusIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/Resources/StatusIcon.png -------------------------------------------------------------------------------- /Resources/Icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/Resources/Icon_512x512.png -------------------------------------------------------------------------------- /Resources/StatusIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/Resources/StatusIcon@2x.png -------------------------------------------------------------------------------- /Resources/StatusIcon_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/Resources/StatusIcon_sel.png -------------------------------------------------------------------------------- /Resources/StatusIcon_sel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avibrazil/RDM/HEAD/Resources/StatusIcon_sel@2x.png -------------------------------------------------------------------------------- /SRApplicationDelegate.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @interface SRApplicationDelegate : NSObject 5 | { 6 | NSMenu* statusMenu; 7 | NSStatusItem* statusItem; 8 | } 9 | 10 | @end 11 | 12 | -------------------------------------------------------------------------------- /utils.mm: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | #import 5 | 6 | #import "utils.h" 7 | 8 | 9 | void CopyAllDisplayModes(CGDirectDisplayID display, modes_D4** modes, int* cnt) 10 | { 11 | int nModes; 12 | CGSGetNumberOfDisplayModes(display, &nModes); 13 | 14 | if(nModes) 15 | *cnt = nModes; 16 | 17 | if(!modes) 18 | return; 19 | 20 | *modes = (modes_D4*) malloc(sizeof(modes_D4)* nModes); 21 | for(int i=0; i 2 | 3 | 4 | 5 | CFBundleIconFile 6 | monitor 7 | CFBundlePackageType 8 | APPL 9 | CFBundleSignature 10 | ???? 11 | CFBundleExecutable 12 | SetResX 13 | CFBundleGetInfoString 14 | Rapid screen resolution switching 15 | CFBundleIdentifier 16 | net.alkalay.RDM 17 | CFBundleName 18 | RDM 19 | LSUIElement 20 | 1 21 | NSHighResolutionCapable 22 | 23 | NSHumanReadableCopyright 24 | Distributed under GNU General Public License v3.0 25 | CFBundleVersion 26 | 2.2 27 | CFBundleShortVersionString 28 | 2.2 29 | 30 | 31 | -------------------------------------------------------------------------------- /main.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #import 4 | #import 5 | 6 | #import "SRApplicationDelegate.h" 7 | #import "cmdline.h" 8 | 9 | 10 | int main(int argc, const char* argv[]) 11 | { 12 | int ret = -1; 13 | if(argc > 1) 14 | { 15 | ret = cmdline_main(argc, argv); 16 | } 17 | 18 | if(ret == -1) 19 | { 20 | fprintf(stderr, "Commandline options\n" 21 | " --width (-w) Width\n" 22 | " --height (-h) Height\n" 23 | " --scale (-s) Scale (2.0 = Retina, default=current)\n" 24 | " --bits (-b) Color depth (default=current)\n" 25 | " --display (-d) Select display # (default=main)\n" 26 | " --displays (-ld) List available displays\n" 27 | " --modes (-lm) List available modes\n" 28 | "\nCurrently running GUI. Use ^C or close from menu\n"); 29 | 30 | NSAutoreleasePool* pool = [NSAutoreleasePool new]; 31 | NSApplication* app = [NSApplication sharedApplication]; 32 | 33 | [app setDelegate: [SRApplicationDelegate new]]; 34 | 35 | //NSApplication* app = [SRApplication sharedApplication]; 36 | [app performSelectorOnMainThread: @selector(run) withObject: nil waitUntilDone: YES]; 37 | 38 | [pool release]; 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /Resources/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1504 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset77 ZapfDingbatsITC;\f2\fnil\fcharset0 LucidaGrande; 3 | } 4 | {\colortbl;\red255\green255\blue255;} 5 | {\*\expandedcolortbl;\csgray\c100000;} 6 | \margl1440\margr1440\vieww13140\viewh13880\viewkind1 7 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 8 | 9 | \f0\fs24 \cf0 This program is provided for free and without any warranty or support. By using this software, you agree to not hold us liable for any loss or damage.\ 10 | \ 11 | v0.2 12 | \f1 \uc0\u10145 13 | \f0 Original version by Paul Griffin ({\field{\*\fldinst{HYPERLINK "http://www.phoenix-dev.com"}}{\fldrslt www.phoenix-dev.com}}).\ 14 | v2.1 15 | \f1 \uc0\u10145 16 | \f0 Sorting, text fixes (including emoji) by Avi 17 | \f2 \uc0\u1488 \u1489 \u1497 18 | \f0 Alkalay 19 | \f2 \uc0\u1488 \u1500 \u1511 \u1500 \u1506 \u1497 20 | \f0 ({\field{\*\fldinst{HYPERLINK "https://github.com/avibrazil"}}{\fldrslt https://github.com/avibrazil}})\ 21 | v2.2 22 | \f1 \uc0\u10145 23 | \f0 UI displaying properly on retina displays and Dark Mode by Arya McCarthy ({\field{\*\fldinst{HYPERLINK "https://github.com/aryamccarthy"}}{\fldrslt https://github.com/aryamccarthy}} merged back to avibrazil).} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a tool that lets you use MacBook Pro Retina's highest and unsupported resolutions. 2 | As an example, a Retina MacBook Pro 13" can be set to 3360×2100 maximum resolution, as 3 | opposed to Apple's max supported 1680×1050. It is accessible from the menu bar. 4 | 5 | ![rdm-screenshot](https://cloud.githubusercontent.com/assets/3484242/7100316/255a7d74-dff0-11e4-9bf9-16e726336e29.png) 6 | 7 | You should prefer resolutions marked with ⚡️ (lightning), which indicates the resolution 8 | is HiDPI or 2× or more dense in pixels. 9 | 10 | For more practical results, add RDM.app to your Login Items in **System Preferences ➡ Users & Groups ➡ Login Items**. 11 | This way RDM will run automatically on startup. 12 | 13 | This software was studied and released [here](http://garethjenkins.com/2012/07/01/investigating-a-high-resolution-retina-utility-for-macbook-pro-1x-and-2x-modes/#comment-623) 14 | and [here](http://www.reddit.com/r/apple/comments/vi9yf/set_your_retina_macbook_pros_resolution_to/) 15 | by its original authors. I just improved the build system and Makefile, fixed the icon, 16 | added support for easy installable package (PKG, DMG) and improved the way menu is 17 | displayed. I don't know what is the license by its authors because it came 100% 18 | uncommented and undocumented. But I'm sure they would enjoy you to freely use it. Me too. 19 | 20 | Want installable binary package? [It is here](http://avi.alkalay.net/software/RDM/). 21 | 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env make -f 2 | 3 | PREFIX=/usr 4 | IDENTIFIER=net.alkalay.RDM 5 | 6 | VERSION=2.2 7 | 8 | CC=llvm-g++ 9 | PACKAGE_BUILD=/usr/bin/pkgbuild 10 | ARCH_FLAGS=-arch x86_64 11 | 12 | .PHONY: build 13 | 14 | RDM.app: SetResX Resources Info.plist monitor.icns 15 | mkdir -p RDM.app/Contents/MacOS/ 16 | cp SetResX RDM.app/Contents/MacOS/ 17 | cp -r Info.plist Resources RDM.app/Contents 18 | rm RDM.app/Contents/Resources/Icon_512x512.png 19 | rm RDM.app/Contents/Resources/StatusIcon_sel.png 20 | rm RDM.app/Contents/Resources/StatusIcon_sel@2x.png 21 | mv monitor.icns RDM.app/Contents/Resources 22 | 23 | 24 | SetResX: main.o SRApplicationDelegate.o ResMenuItem.o cmdline.o utils.o 25 | $(CC) $^ -o $@ $(ARCH_FLAGS) -framework Foundation -framework ApplicationServices -framework AppKit 26 | 27 | 28 | clean: 29 | rm -f SetResX 30 | rm -f *.o 31 | rm -f *icns 32 | rm -rf RDM.app 33 | rm -rf pkgroot dmgroot 34 | rm -f *.pkg *.dmg 35 | 36 | %.o: %.mm 37 | $(CC) $(CPPFLAGS) $(CFLAGS) $(ARCH_FLAGS) $< -c -o $@ 38 | 39 | 40 | %.icns: %.png 41 | sips -s format icns $< --out $@ 42 | 43 | pkg: RDM.app 44 | mkdir -p pkgroot/Applications 45 | mv $< pkgroot/Applications/ 46 | $(PACKAGE_BUILD) --root pkgroot/ --identifier $(IDENTIFIER) \ 47 | --version $(VERSION) "RDM-$(VERSION).pkg" 48 | rm -f RDM.pkg 49 | ln -s RDM-$(VERSION).pkg RDM.pkg 50 | 51 | dmg: pkg 52 | mkdir -p dmgroot 53 | cp RDM-$(VERSION).pkg dmgroot/ 54 | rm -f RDM-$(VERSION).dmg 55 | hdiutil makehybrid -hfs -hfs-volume-name "RDM $(VERSION)" \ 56 | -o "RDM-$(VERSION).dmg" dmgroot/ 57 | rm -f RDM.dmg 58 | ln -s RDM-$(VERSION).dmg RDM.dmg 59 | 60 | .PHONY: pkg dmg install build clean 61 | -------------------------------------------------------------------------------- /ResMenuItem.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #import 5 | #import 6 | 7 | #import "utils.h" 8 | #import "ResMenuItem.h" 9 | 10 | @implementation ResMenuItem : NSMenuItem 11 | 12 | 13 | - (id) initWithDisplay: (CGDirectDisplayID) display andMode: (modes_D4*) mode 14 | { 15 | if((self = [super initWithTitle: @"" action: @selector(setMode:) keyEquivalent: @""]) && display && mode) 16 | { 17 | _display = display; 18 | 19 | modeNum = mode->derived.mode; 20 | scale = mode->derived.density; 21 | 22 | width = mode->derived.width; 23 | height = mode->derived.height; 24 | 25 | refreshRate = mode->derived.freq; 26 | 27 | colorDepth = (mode->derived.depth == 4) ? 32 : 16; 28 | 29 | 30 | NSString* title; 31 | if(scale == 2.0f) 32 | { 33 | if(refreshRate) 34 | { 35 | title = [NSString stringWithFormat: @"%d × %d ⚡️, %.0f Hz", width, height, refreshRate]; 36 | } 37 | else 38 | { 39 | title = [NSString stringWithFormat: @"%d × %d ⚡️", width, height]; 40 | } 41 | } 42 | else 43 | { 44 | if(refreshRate) 45 | { 46 | title = [NSString stringWithFormat: @"%d × %d, %.0f Hz", width, height, refreshRate]; 47 | } 48 | else 49 | { 50 | title = [NSString stringWithFormat: @"%d × %d", width, height]; 51 | } 52 | } 53 | [self setTitle: title]; 54 | 55 | return self; 56 | } 57 | else 58 | { 59 | return NULL; 60 | } 61 | } 62 | 63 | - (void) setTextFormat: (int) textFormat 64 | { 65 | NSString* title = nil; 66 | if(textFormat == 1) 67 | { 68 | if(scale == 2.0f) 69 | { 70 | title = [NSString stringWithFormat: @"%d × %d ⚡", width, height]; 71 | } 72 | else 73 | { 74 | title = [NSString stringWithFormat: @"%d × %d", width, height]; 75 | } 76 | } 77 | 78 | if(textFormat == 2) 79 | { 80 | title = [NSString stringWithFormat: @"%.0f Hz", refreshRate]; 81 | } 82 | if(title) 83 | [self setTitle: title]; 84 | 85 | 86 | 87 | } 88 | 89 | - (CGDirectDisplayID) display 90 | { 91 | return _display; 92 | } 93 | 94 | - (int) modeNum 95 | { 96 | return modeNum; 97 | } 98 | 99 | - (int) colorDepth 100 | { 101 | return colorDepth; 102 | } 103 | 104 | - (int) width 105 | { 106 | return width; 107 | } 108 | 109 | - (int) height 110 | { 111 | return height; 112 | } 113 | 114 | - (float) refreshRate 115 | { 116 | return refreshRate; 117 | } 118 | 119 | - (float) scale 120 | { 121 | return scale; 122 | } 123 | 124 | - (NSComparisonResult) compareResMenuItem: (ResMenuItem*) otherItem 125 | { 126 | { 127 | int o_width = [otherItem width]; 128 | if(width < o_width) 129 | return NSOrderedDescending; 130 | else if(width > o_width) 131 | return NSOrderedAscending; 132 | // return NSOrderedSame; 133 | } 134 | { 135 | int o_scale = [otherItem scale]; 136 | if(scale < o_scale) 137 | return NSOrderedDescending; 138 | else if(scale > o_scale) 139 | return NSOrderedAscending; 140 | // return NSOrderedSame; 141 | } 142 | { 143 | int o_height = [otherItem height]; 144 | if(height < o_height) 145 | return NSOrderedDescending; 146 | else if(height > o_height) 147 | return NSOrderedAscending; 148 | // return NSOrderedSame; 149 | } 150 | { 151 | int o_refreshRate = [otherItem refreshRate]; 152 | if(refreshRate < o_refreshRate) 153 | return NSOrderedDescending; 154 | else if(refreshRate > o_refreshRate) 155 | return NSOrderedAscending; 156 | return NSOrderedSame; 157 | } 158 | 159 | 160 | } 161 | 162 | @end -------------------------------------------------------------------------------- /SRApplicationDelegate.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | #import 4 | #import 5 | 6 | #import "SRApplicationDelegate.h" 7 | 8 | #import "utils.h" 9 | #import "ResMenuItem.h" 10 | 11 | 12 | 13 | 14 | 15 | 16 | @implementation SRApplicationDelegate 17 | 18 | - (void) showAbout 19 | { 20 | [NSApp activateIgnoringOtherApps:YES]; 21 | [NSApp orderFrontStandardAboutPanel:self]; 22 | } 23 | 24 | 25 | - (void) quit 26 | { 27 | [NSApp terminate: self]; 28 | } 29 | 30 | 31 | 32 | - (void) refreshStatusMenu 33 | { 34 | if(statusMenu) 35 | [statusMenu release]; 36 | 37 | statusMenu = [[NSMenu alloc] initWithTitle: @""]; 38 | 39 | uint32_t nDisplays; 40 | CGDirectDisplayID displays[0x10]; 41 | CGGetOnlineDisplayList(0x10, displays, &nDisplays); 42 | 43 | for(int i=0; i lastRefreshRate))) 110 | { 111 | if(lastAddedItem) 112 | { 113 | [submenu removeItem: lastAddedItem]; 114 | lastAddedItem = nil; 115 | } 116 | [submenu addItem: item]; 117 | lastAddedItem = item; 118 | } 119 | } 120 | else 121 | { 122 | [submenu addItem: item]; 123 | lastAddedItem = item; 124 | } 125 | } 126 | } 127 | 128 | NSString* title; 129 | { 130 | if([mainItem scale] == 2.0f) 131 | { 132 | title = [NSString stringWithFormat: @"%d × %d ⚡️️", [mainItem width], [mainItem height]]; 133 | } 134 | else 135 | { 136 | title = [NSString stringWithFormat: @"%d × %d", [mainItem width], [mainItem height]]; 137 | } 138 | } 139 | 140 | 141 | NSMenuItem* resolution = [[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""]; 142 | [resolution setSubmenu: submenu]; 143 | [submenu release]; 144 | [statusMenu addItem: resolution]; 145 | [resolution release]; 146 | 147 | [displayMenuItems release]; 148 | } 149 | 150 | { 151 | NSMutableArray* displayMenuItems = [NSMutableArray new]; 152 | ResMenuItem* mainItem = nil; 153 | for(int j = 0; j < nModes; j++) 154 | { 155 | ResMenuItem* item = [[ResMenuItem alloc] initWithDisplay: display andMode: &modes[j]]; 156 | [item setTextFormat: 2]; 157 | //[item autorelease]; 158 | if(mainModeNum == j) 159 | { 160 | mainItem = item; 161 | [item setState: NSOnState]; 162 | } 163 | [displayMenuItems addObject: item]; 164 | [item release]; 165 | } 166 | int idealColorDepth = 32; 167 | double idealRefreshRate = 0.0f; 168 | if(mainItem) 169 | { 170 | idealColorDepth = [mainItem colorDepth]; 171 | idealRefreshRate = [mainItem refreshRate]; 172 | } 173 | [displayMenuItems sortUsingSelector: @selector(compareResMenuItem:)]; 174 | 175 | 176 | NSMenu* submenu = [[NSMenu alloc] initWithTitle: @""]; 177 | for(int j=0; j< [displayMenuItems count]; j++) 178 | { 179 | ResMenuItem* item = [displayMenuItems objectAtIndex: j]; 180 | if([item colorDepth] == idealColorDepth) 181 | { 182 | if([mainItem width]==[item width] && [mainItem height]==[item height] && [mainItem scale]==[item scale]) 183 | { 184 | [submenu addItem: item]; 185 | } 186 | } 187 | } 188 | if(idealRefreshRate) 189 | { 190 | NSMenuItem* freq = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"%.0f Hz", [mainItem refreshRate]] action: nil keyEquivalent: @""]; 191 | 192 | if([submenu numberOfItems] > 1) 193 | { 194 | [freq setSubmenu: submenu]; 195 | } 196 | else 197 | { 198 | [freq setEnabled: NO]; 199 | } 200 | [statusMenu addItem: freq]; 201 | [freq release]; 202 | } 203 | [submenu release]; 204 | 205 | [displayMenuItems release]; 206 | 207 | } 208 | 209 | 210 | free(modes); 211 | 212 | 213 | [statusMenu addItem: [NSMenuItem separatorItem]]; 214 | } 215 | 216 | [statusMenu addItemWithTitle: @"About RDM" action: @selector(showAbout) keyEquivalent: @""]; 217 | 218 | 219 | [statusMenu addItemWithTitle: @"Quit" action: @selector(quit) keyEquivalent: @""]; 220 | [statusMenu setDelegate: self]; 221 | [statusItem setMenu: statusMenu]; 222 | } 223 | 224 | 225 | - (void) setMode: (ResMenuItem*) item 226 | { 227 | CGDirectDisplayID display = [item display]; 228 | int modeNum = [item modeNum]; 229 | 230 | SetDisplayModeNum(display, modeNum); 231 | /* 232 | 233 | CGDisplayConfigRef config; 234 | if (CGBeginDisplayConfiguration(&config) == kCGErrorSuccess) { 235 | CGConfigureDisplayWithDisplayMode(config, display, mode, NULL); 236 | CGCompleteDisplayConfiguration(config, kCGConfigureForSession); 237 | }*/ 238 | [self refreshStatusMenu]; 239 | } 240 | 241 | - (void) applicationDidFinishLaunching: (NSNotification*) notification 242 | { 243 | // NSLog(@"Finished launching"); 244 | statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]; 245 | 246 | NSImage* statusImage = [NSImage imageNamed: @"StatusIcon"]; 247 | [statusItem setImage: statusImage]; 248 | [statusItem setHighlightMode: YES]; 249 | 250 | BOOL supportsDarkMenu = !(floor(NSAppKitVersionNumber) < 1343); // NSAppKitVersionNumber10_10 251 | if (supportsDarkMenu) { 252 | [[statusItem image] setTemplate:YES]; 253 | } 254 | 255 | [self refreshStatusMenu]; 256 | 257 | } 258 | 259 | @end 260 | -------------------------------------------------------------------------------- /cmdline.mm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | 5 | #import "utils.h" 6 | 7 | 8 | 9 | 10 | int cmdline_main(int argc, const char * argv[]) 11 | { 12 | //exit(0); 13 | 14 | NSAutoreleasePool* pool = [NSAutoreleasePool new]; 15 | { 16 | int width = 0; 17 | int height = 0; 18 | CGFloat scale = 0.0f; 19 | int bitRes = 0; 20 | int displayNo = -1; 21 | double rotation = -1; 22 | 23 | bool listDisplays = 0; 24 | bool listModes = 0; 25 | 26 | for(int i=1; i 0) 140 | { 141 | if(displayNo > nDisplays -1) 142 | { 143 | fprintf(stderr, "Error: display index %d exceeds display count %d\n", displayNo, nDisplays); 144 | exit(1); 145 | } 146 | display = displays[displayNo]; 147 | } 148 | else 149 | { 150 | display = CGMainDisplayID(); 151 | } 152 | 153 | if(listDisplays) 154 | { 155 | for(int i=0; i