| 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. |
| 12 | const 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. |
| 16 | const CFStringRef NSAccessibilityAnnouncementKey = CFSTR("AXAnnouncementKey"); |
| 17 | const CFStringRef NSAccessibilityAnnouncementRequestedNotification = CFSTR("AXAnnouncementRequested"); |
| 18 | const CFStringRef NSAccessibilityLanguageTextAttribute = CFSTR("AXLanguageText"); |
| 19 | const CFStringRef NSAccessibilityPriorityKey = CFSTR("AXPriorityKey"); |
| 20 | const CFStringRef NSAccessibilitySwitchSubrole = CFSTR("AXSwitch"); |
| 21 | const CFStringRef NSAccessibilityTabButtonSubrole = CFSTR("AXTabButton"); |
| 22 | const CFStringRef NSAccessibilityTextAlignmentAttribute = CFSTR("AXTextAlignment"); |
| 23 | const 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. |
| 27 | const CFStringRef NSURLIsUbiquitousItemKey = CFSTR("NSURLIsUbiquitousItemKey"); |
| 28 | const CFStringRef NSURLUbiquitousItemDownloadingStatusKey = CFSTR("NSURLUbiquitousItemDownloadingStatusKey"); |
| 29 | const CFStringRef NSURLUbiquitousItemDownloadingStatusNotDownloaded = |
| 30 | CFSTR("NSURLUbiquitousItemDownloadingStatusNotDownloaded"); |
| 31 | |
| 32 | extern void NSAccessibilityPostNotification(id element, CFStringRef notification); |
| 33 | |
| 34 | void NSAccessibilityPostNotificationWithUserInfo(id element, CFStringRef notification, id info) { |
| 35 | NSAccessibilityPostNotification(element, notification); |
| 36 | } |
| 37 | |
| 38 | typedef id (*Send)(id, SEL); |
| 39 | #define SEND(receiver, selector) ((Send)objc_msgSend)((id)(receiver), sel_registerName(selector)) |
| 40 | typedef double (*SendDouble)(id, SEL); |
| 41 | |
| 42 | static double one(id self, SEL _cmd) { return 1.0; } |
| 43 | static void ignore(id self, SEL _cmd, id value) {} |
| 44 | static BOOL no(id self, SEL _cmd) { return NO; } |
| 45 | static long zero(id self, SEL _cmd) { return 0; } |
| 46 | static id nothing(id self, SEL _cmd) { return nil; } |
| 47 | |
| 48 | static double delta_x(id self, SEL _cmd) { return ((SendDouble)objc_msgSend)(self, sel_registerName("deltaX")); } |
| 49 | static 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. |
| 52 | static 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 | |
| 59 | static 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. |
| 66 | static 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 | |
| 95 | static 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. |
| 103 | static 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 | |
| 108 | static id appearance_class; |
| 109 | |
| 110 | static 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 | |
| 116 | static id aqua(id self, SEL _cmd) { return (id)NSAppearanceNameAqua; } |
| 117 | static 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. |
| 121 | static 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 | } |
| 126 | static CGFloat red(id self, SEL _cmd) { return component(self, 0); } |
| 127 | static CGFloat green(id self, SEL _cmd) { return component(self, 1); } |
| 128 | static CGFloat blue(id self, SEL _cmd) { return component(self, 2); } |
| 129 | |
| 130 | #define RECT "{CGRect={CGPoint=dd}{CGSize=dd}}" |
| 131 | |
| 132 | static 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 | } |