1// AppKit that winit, AccessKit and the app call and 10.6 lacks, added at load time with
2// the neutral answer: scale 1, no precise scrolling, no appearances, no tabbing, no full
3// screen; window drags are done here. `remote.sh selectors` lists what a binary names
4// that 10.6 lacks. Linked from the archive by the appearance name winit imports, so only
5// AppKit binaries carry it.
6// class_addMethod adds nothing where the system already has the method.
7#include <ApplicationServices/ApplicationServices.h>
8#include <objc/message.h>
9#include <objc/runtime.h>
10
11// 10.9's appearance name; it names the stub appearance below.
12const CFStringRef NSAppearanceNameAqua = CFSTR("NSAppearanceNameAqua");
13
14// Accessibility names from 10.7 to 10.13, which AccessKit imports. 10.6's VoiceOver
15// asks through the older attribute protocol, which never sees them.
16const CFStringRef NSAccessibilityAnnouncementKey = CFSTR("AXAnnouncementKey");
17const CFStringRef NSAccessibilityAnnouncementRequestedNotification = CFSTR("AXAnnouncementRequested");
18const CFStringRef NSAccessibilityLanguageTextAttribute = CFSTR("AXLanguageText");
19const CFStringRef NSAccessibilityPriorityKey = CFSTR("AXPriorityKey");
20const CFStringRef NSAccessibilitySwitchSubrole = CFSTR("AXSwitch");
21const CFStringRef NSAccessibilityTabButtonSubrole = CFSTR("AXTabButton");
22const CFStringRef NSAccessibilityTextAlignmentAttribute = CFSTR("AXTextAlignment");
23const CFStringRef NSAccessibilityToggleSubrole = CFSTR("AXToggle");
24
25// NSURL resource keys from 10.7 to 10.9 for iCloud files, which the app asks about; 10.6
26// knows none of them, so a file there reads as not in iCloud.
27const CFStringRef NSURLIsUbiquitousItemKey = CFSTR("NSURLIsUbiquitousItemKey");
28const CFStringRef NSURLUbiquitousItemDownloadingStatusKey = CFSTR("NSURLUbiquitousItemDownloadingStatusKey");
29const CFStringRef NSURLUbiquitousItemDownloadingStatusNotDownloaded =
30 CFSTR("NSURLUbiquitousItemDownloadingStatusNotDownloaded");
31
32extern void NSAccessibilityPostNotification(id element, CFStringRef notification);
33
34void NSAccessibilityPostNotificationWithUserInfo(id element, CFStringRef notification, id info) {
35 NSAccessibilityPostNotification(element, notification);
36}
37
38typedef id (*Send)(id, SEL);
39#define SEND(receiver, selector) ((Send)objc_msgSend)((id)(receiver), sel_registerName(selector))
40typedef double (*SendDouble)(id, SEL);
41
42static double one(id self, SEL _cmd) { return 1.0; }
43static void ignore(id self, SEL _cmd, id value) {}
44static BOOL no(id self, SEL _cmd) { return NO; }
45static long zero(id self, SEL _cmd) { return 0; }
46static id nothing(id self, SEL _cmd) { return nil; }
47
48static double delta_x(id self, SEL _cmd) { return ((SendDouble)objc_msgSend)(self, sel_registerName("deltaX")); }
49static double delta_y(id self, SEL _cmd) { return ((SendDouble)objc_msgSend)(self, sel_registerName("deltaY")); }
50
51// 10.7: the window's frame origin is where its base coordinates start.
52static CGRect rect_to_screen(id self, SEL _cmd, CGRect rect) {
53 CGRect frame = ((CGRect(*)(id, SEL))objc_msgSend_stret)(self, sel_registerName("frame"));
54 rect.origin.x += frame.origin.x;
55 rect.origin.y += frame.origin.y;
56 return rect;
57}
58
59static CGPoint point_from_screen(id self, SEL _cmd, CGPoint point) {
60 CGRect frame = ((CGRect(*)(id, SEL))objc_msgSend_stret)(self, sel_registerName("frame"));
61 return CGPointMake(point.x - frame.origin.x, point.y - frame.origin.y);
62}
63
64// 10.11: the window follows the pointer until the button comes up, as a press on its
65// title does.
66static void window_drag(id self, SEL _cmd, id event) {
67 typedef CGPoint (*SendPoint)(id, SEL);
68 typedef CGRect (*SendRect)(id, SEL);
69 id event_class = (id)objc_getClass("NSEvent");
70 CGPoint start = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation"));
71 CGRect frame = ((SendRect)objc_msgSend_stret)(self, sel_registerName("frame"));
72 id app = SEND(objc_getClass("NSApplication"), "sharedApplication");
73 id forever = SEND(objc_getClass("NSDate"), "distantFuture");
74 for (;;) {
75 // NSLeftMouseDraggedMask | NSLeftMouseUpMask.
76 id next = ((id (*)(id, SEL, unsigned long long, id, CFStringRef, BOOL))objc_msgSend)(
77 app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"),
78 1 << 6 | 1 << 2, forever, CFSTR("NSEventTrackingRunLoopMode"), YES);
79 // NSLeftMouseUp.
80 if (((unsigned long (*)(id, SEL))objc_msgSend)(next, sel_registerName("type")) == 2) {
81 // The button's release stays in the queue for whoever asked for the drag.
82 ((void (*)(id, SEL, id, BOOL))objc_msgSend)(app, sel_registerName("postEvent:atStart:"), next, YES);
83 return;
84 }
85 CGPoint now = ((SendPoint)objc_msgSend)(event_class, sel_registerName("mouseLocation"));
86 CGRect moved = frame;
87 moved.origin.x += now.x - start.x;
88 moved.origin.y += now.y - start.y;
89 moved = ((CGRect (*)(id, SEL, CGRect, id))objc_msgSend_stret)(
90 self, sel_registerName("constrainFrameRect:toScreen:"), moved, SEND(self, "screen"));
91 ((void (*)(id, SEL, CGPoint))objc_msgSend)(self, sel_registerName("setFrameOrigin:"), moved.origin);
92 }
93}
94
95static id srgb_color(id self, SEL _cmd, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) {
96 CGFloat components[] = {red, green, blue, alpha};
97 return ((id (*)(id, SEL, id, CGFloat *, long))objc_msgSend)(
98 self, sel_registerName("colorWithColorSpace:components:count:"),
99 SEND(objc_getClass("NSColorSpace"), "sRGBColorSpace"), components, 4);
100}
101
102// 10.10's label is what 10.6 calls the description attribute.
103static void set_accessibility_label(id self, SEL _cmd, id label) {
104 ((void (*)(id, SEL, id, CFStringRef))objc_msgSend)(
105 self, sel_registerName("accessibilitySetOverrideValue:forAttribute:"), label, CFSTR("AXDescription"));
106}
107
108static id appearance_class;
109
110static id stub_appearance(id self, SEL _cmd) {
111 static id shared;
112 if (!shared) shared = SEND(SEND(appearance_class, "alloc"), "init");
113 return shared;
114}
115
116static id aqua(id self, SEL _cmd) { return (id)NSAppearanceNameAqua; }
117static id best_match(id self, SEL _cmd, id names) { return (id)NSAppearanceNameAqua; }
118
119// 10.6 answers component accessors only for the named RGB spaces; later systems answer
120// them for any RGB colour space, such as the sRGB the app converts system colours to.
121static CGFloat component(id self, int index) {
122 CGFloat components[4] = {0};
123 ((void (*)(id, SEL, CGFloat *))objc_msgSend)(self, sel_registerName("getComponents:"), components);
124 return components[index];
125}
126static CGFloat red(id self, SEL _cmd) { return component(self, 0); }
127static CGFloat green(id self, SEL _cmd) { return component(self, 1); }
128static CGFloat blue(id self, SEL _cmd) { return component(self, 2); }
129
130#define RECT "{CGRect={CGPoint=dd}{CGSize=dd}}"
131
132static void add(const char *class_name, int meta, const char *selector, void *imp, const char *types) {
133 Class cls = (Class)objc_getClass(class_name);
134 if (meta) cls = object_getClass((id)cls);
135 class_addMethod(cls, sel_registerName(selector), (IMP)imp, types);
136}
137
138__attribute__((constructor)) static void polyfill(void) {
139 add("NSScreen", 0, "backingScaleFactor", one, "d@:");
140 add("NSWindow", 0, "backingScaleFactor", one, "d@:");
141 add("NSWindow", 0, "convertRectToScreen:", rect_to_screen, RECT "@:" RECT);
142 add("NSWindow", 0, "convertPointFromScreen:", point_from_screen, "{CGPoint=dd}@:{CGPoint=dd}");
143 add("NSWindow", 0, "performWindowDragWithEvent:", window_drag, "v@:@");
144 add("NSWindow", 0, "toggleFullScreen:", ignore, "v@:@");
145 add("NSWindow", 0, "setTabbingMode:", ignore, "v@:q");
146 add("NSWindow", 0, "setTabbingIdentifier:", ignore, "v@:@");
147 add("NSWindow", 1, "setAllowsAutomaticWindowTabbing:", ignore, "v@:c");
148 add("NSWindow", 0, "setTitlebarAppearsTransparent:", ignore, "v@:c");
149 add("NSView", 0, "setWantsBestResolutionOpenGLSurface:", ignore, "v@:c");
150 add("NSView", 0, "setAccessibilityLabel:", set_accessibility_label, "v@:@");
151 add("NSEvent", 0, "hasPreciseScrollingDeltas", no, "c@:");
152 add("NSEvent", 0, "scrollingDeltaX", delta_x, "d@:");
153 add("NSEvent", 0, "scrollingDeltaY", delta_y, "d@:");
154 add("NSEvent", 0, "momentumPhase", zero, "Q@:");
155 add("NSEvent", 0, "phase", zero, "Q@:");
156 add("NSColorSpaceColor", 0, "redComponent", red, "d@:");
157 add("NSColorSpaceColor", 0, "greenComponent", green, "d@:");
158 add("NSColorSpaceColor", 0, "blueComponent", blue, "d@:");
159 add("NSColor", 1, "colorWithSRGBRed:green:blue:alpha:", srgb_color, "@@:dddd");
160
161 // One appearance, Aqua, which windows have and nothing changes.
162 appearance_class = (id)objc_getClass("NSAppearance");
163 if (!appearance_class) {
164 Class cls = objc_allocateClassPair((Class)objc_getClass("NSObject"), "NSAppearance", 0);
165 objc_registerClassPair(cls);
166 appearance_class = (id)cls;
167 }
168 add("NSAppearance", 0, "name", aqua, "@@:");
169 add("NSAppearance", 0, "bestMatchFromAppearancesWithNames:", best_match, "@@:@");
170 add("NSAppearance", 1, "appearanceNamed:", nothing, "@@:@");
171 add("NSAppearance", 1, "currentAppearance", nothing, "@@:");
172 add("NSAppearance", 1, "setCurrentAppearance:", ignore, "v@:@");
173 add("NSWindow", 0, "appearance", nothing, "@@:");
174 add("NSWindow", 0, "setAppearance:", ignore, "v@:@");
175 add("NSWindow", 0, "effectiveAppearance", stub_appearance, "@@:");
176}