-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvdisplay.m
More file actions
272 lines (249 loc) · 11.4 KB
/
Copy pathvdisplay.m
File metadata and controls
272 lines (249 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// vdisplay.m — creates headless HiDPI virtual displays and auto-applies
// scaling when a monitor listed in models.conf is (dis)connected.
// Mirror a physical monitor to a virtual display to get "looks like"
// resolutions beyond the panel's native mode (the old BetterDummy trick).
//
// Build: clang -fobjc-arc -framework Foundation -framework CoreGraphics -framework IOKit -o vdisplay vdisplay.m
// Run: ./vdisplay (keeps running; the displays exist while the process lives)
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <IOKit/IOKitLib.h>
#include <mach-o/dyld.h>
// Private IOKit AV functions (same interface m1ddc/BetterDisplay use) — the
// only reliable way to see physical connection state: a monitor that is a
// hardware-mirror slave stays "online" in CoreGraphics after unplug and
// fires no reconfiguration events.
typedef CFTypeRef IOAVServiceRef;
extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator,
io_service_t service);
extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef *data);
// Private CoreGraphics classes (same API used by BetterDummy/DeskPad/FluffyDisplay)
@interface CGVirtualDisplaySettings : NSObject
@property (nonatomic, strong) NSArray *modes;
@property (nonatomic) unsigned int hiDPI;
@end
@interface CGVirtualDisplayDescriptor : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic) unsigned int maxPixelsWide;
@property (nonatomic) unsigned int maxPixelsHigh;
@property (nonatomic) CGSize sizeInMillimeters;
@property (nonatomic) unsigned int productID;
@property (nonatomic) unsigned int vendorID;
@property (nonatomic) unsigned int serialNum;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@interface CGVirtualDisplayMode : NSObject
- (instancetype)initWithWidth:(unsigned int)width
height:(unsigned int)height
refreshRate:(double)refreshRate;
@end
@interface CGVirtualDisplay : NSObject
@property (nonatomic, readonly) CGDirectDisplayID displayID;
- (instancetype)initWithDescriptor:(CGVirtualDisplayDescriptor *)descriptor;
- (BOOL)applySettings:(CGVirtualDisplaySettings *)settings;
@end
typedef struct { uint32_t vendor, model, pw, ph; } ModelEntry;
// Scale steps offered per panel: native, ~7%, 20%, 33% more space.
// Integer math here must match set-scale.sh exactly so modes line up.
static const unsigned kFactors[][2] = {{1, 1}, {16, 15}, {6, 5}, {4, 3}};
static const int kNumFactors = 4;
// Directory this binary lives in — scripts, conf, and logs sit next to it
static NSString *baseDir(void) {
static NSString *dir = nil;
if (!dir) {
char exe[PATH_MAX];
uint32_t sz = sizeof(exe);
_NSGetExecutablePath(exe, &sz);
char real[PATH_MAX];
realpath(exe, real);
dir = [[NSString stringWithUTF8String:real] stringByDeletingLastPathComponent];
}
return dir;
}
// models.conf lines: vendor:model:WxH (decimal EDID ids); # comments allowed
static int loadModels(ModelEntry *out, int max) {
NSString *path = [baseDir() stringByAppendingPathComponent:@"models.conf"];
FILE *f = fopen(path.UTF8String, "r");
int n = 0;
if (f) {
char line[256];
while (n < max && fgets(line, sizeof(line), f)) {
ModelEntry e;
if (line[0] == '#') continue;
if (sscanf(line, "%u:%u:%ux%u", &e.vendor, &e.model, &e.pw, &e.ph) == 4)
out[n++] = e;
}
fclose(f);
}
if (n == 0) out[n++] = (ModelEntry){4268, 41413, 1920, 1080}; // DELL P2422H
return n;
}
// Set of PHYSICALLY connected monitors matching models.conf, keyed by IOKit
// registry entry id. Reads live EDID per external port — ground truth that,
// unlike the CoreGraphics display list, drops unplugged mirror slaves.
static NSSet *monitoredSet(void) {
ModelEntry models[32];
int nModels = loadModels(models, 32);
NSMutableSet *set = [NSMutableSet set];
io_iterator_t iter;
if (IOServiceGetMatchingServices(kIOMainPortDefault,
IOServiceMatching("DCPAVServiceProxy"), &iter) != KERN_SUCCESS)
return set;
io_service_t svc;
while ((svc = IOIteratorNext(iter))) {
CFTypeRef loc = IORegistryEntryCreateCFProperty(svc, CFSTR("Location"),
kCFAllocatorDefault, 0);
BOOL external = loc && CFGetTypeID(loc) == CFStringGetTypeID() &&
CFStringCompare(loc, CFSTR("External"), 0) == kCFCompareEqualTo;
if (loc) CFRelease(loc);
if (external) {
IOAVServiceRef av = IOAVServiceCreateWithService(kCFAllocatorDefault, svc);
if (av) {
CFDataRef edid = NULL;
if (IOAVServiceCopyEDID(av, &edid) == KERN_SUCCESS &&
edid && CFDataGetLength(edid) >= 12) {
const UInt8 *b = CFDataGetBytePtr(edid);
uint32_t vendor = (b[8] << 8) | b[9];
uint32_t product = (b[11] << 8) | b[10];
for (int m = 0; m < nModels; m++) {
if (models[m].vendor == vendor && models[m].model == product) {
uint64_t rid = 0;
IORegistryEntryGetRegistryEntryID(svc, &rid);
[set addObject:@(rid)];
break;
}
}
}
if (edid) CFRelease(edid);
CFRelease(av);
}
}
IOObjectRelease(svc);
}
IOObjectRelease(iter);
return set;
}
static NSSet *lastApplied = nil;
static dispatch_block_t pendingApply = nil;
static void ensureVirtualCount(NSUInteger want);
static void applyIfMonitorSetChanged(void) {
NSSet *now = monitoredSet();
if ([now isEqualToSet:lastApplied]) return;
lastApplied = now;
printf("monitored set changed (%lu connected)\n", (unsigned long)now.count);
ensureVirtualCount(MIN(now.count, (NSUInteger)2));
fflush(stdout);
if (now.count == 0) return;
// sleep lets the just-created virtual displays finish registering
NSString *cmd = [NSString stringWithFormat:
@"{ sleep 2; '%@/set-scale.sh'; } >> '%@/autoscale.log' 2>&1 &",
baseDir(), baseDir()];
system(cmd.UTF8String);
}
static void reconfigCallback(CGDirectDisplayID display,
CGDisplayChangeSummaryFlags flags,
void *userInfo) {
// React to any completed reconfiguration: unplugging a hardware-mirror
// slave doesn't reliably raise Add/Remove flags. The monitored-set
// comparison in applyIfMonitorSetChanged makes spurious wakeups no-ops.
if (flags & kCGDisplayBeginConfigurationFlag) return;
// Debounce: hotplug fires a burst of callbacks; act 3s after the last one
if (pendingApply) dispatch_block_cancel(pendingApply);
pendingApply = dispatch_block_create(0, ^{ applyIfMonitorSetChanged(); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),
dispatch_get_main_queue(), pendingApply);
}
// One "looks like" mode per scale step per panel in models.conf, deduped
static NSArray *buildModes(unsigned *maxW, unsigned *maxH) {
ModelEntry models[32];
int nModels = loadModels(models, 32);
NSMutableArray *modes = [NSMutableArray array];
NSMutableSet *seen = [NSMutableSet set];
*maxW = 0; *maxH = 0;
for (int m = 0; m < nModels; m++) {
for (int fi = 0; fi < kNumFactors; fi++) {
unsigned n = kFactors[fi][0], d = kFactors[fi][1];
unsigned w = (models[m].pw * n + d / 2) / d; w -= w % 2;
unsigned h = (models[m].ph * n + d / 2) / d; h -= h % 2;
NSNumber *key = @(((uint64_t)w << 32) | h);
if ([seen containsObject:key]) continue;
[seen addObject:key];
[modes addObject:[[CGVirtualDisplayMode alloc] initWithWidth:w
height:h
refreshRate:60]];
if (w > *maxW) *maxW = w;
if (h > *maxH) *maxH = h;
}
}
return modes;
}
static NSMutableArray *displays = nil;
static CGVirtualDisplay *makeDisplay(NSString *name, unsigned int serial,
NSArray *modes, unsigned maxW, unsigned maxH);
static NSArray *buildModes(unsigned *maxW, unsigned *maxH);
// Keep exactly `want` virtual displays alive. Releasing a CGVirtualDisplay
// destroys it, so virtuals exist only while matched monitors are connected.
static void ensureVirtualCount(NSUInteger want) {
while (displays.count > want) {
[displays removeLastObject];
printf("virtual display %lu removed\n", (unsigned long)displays.count + 1);
}
if (displays.count >= want) return;
unsigned maxW, maxH;
NSArray *modes = buildModes(&maxW, &maxH);
NSArray *names = @[ @"HiDPI Scale", @"HiDPI Scale 2" ];
while (displays.count < want) {
NSUInteger i = displays.count;
CGVirtualDisplay *d = makeDisplay(names[i], (unsigned int)i + 1,
modes, maxW, maxH);
if (!d) {
fprintf(stderr, "Failed to create virtual display %s\n",
[names[i] UTF8String]);
return;
}
[displays addObject:d];
printf("Virtual display \"%s\" up, displayID=%u\n",
[names[i] UTF8String], d.displayID);
}
}
static CGVirtualDisplay *makeDisplay(NSString *name, unsigned int serial,
NSArray *modes, unsigned maxW, unsigned maxH) {
CGVirtualDisplayDescriptor *desc = [[CGVirtualDisplayDescriptor alloc] init];
desc.name = name;
desc.maxPixelsWide = maxW * 2; // HiDPI backing is 2x the largest mode
desc.maxPixelsHigh = maxH * 2;
// Physical size of a ~24" panel so DPI/menu sizing stays sane
desc.sizeInMillimeters = CGSizeMake(527, 296);
desc.productID = 0xD311; // arbitrary; must match lsmon.m
desc.vendorID = 0xF0F0;
desc.serialNum = serial;
desc.queue = dispatch_get_main_queue();
CGVirtualDisplay *display = [[CGVirtualDisplay alloc] initWithDescriptor:desc];
if (!display) return nil;
CGVirtualDisplaySettings *settings = [[CGVirtualDisplaySettings alloc] init];
settings.hiDPI = 1;
settings.modes = modes;
if (![display applySettings:settings]) return nil;
return display;
}
int main(int argc, char **argv) {
@autoreleasepool {
displays = [NSMutableArray array];
printf("hidpi-scale daemon started (virtual displays created on demand)\n");
fflush(stdout);
// Create/destroy virtuals and apply scaling on monitor (dis)connect.
// CG events catch plugs quickly...
CGDisplayRegisterReconfigurationCallback(reconfigCallback, NULL);
// ...but unplugging a hardware-mirror slave fires NO CG events, so
// poll physical connection state (live EDID) every 5 seconds too.
dispatch_source_t poll = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(poll,
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
5 * NSEC_PER_SEC, NSEC_PER_SEC);
dispatch_source_set_event_handler(poll, ^{ applyIfMonitorSetChanged(); });
dispatch_resume(poll);
dispatch_main();
}
return 0;
}