diff options
Diffstat (limited to 'src/nsterm.m')
| -rw-r--r-- | src/nsterm.m | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/src/nsterm.m b/src/nsterm.m index 118463a13c9..e186c16e725 100644 --- a/src/nsterm.m +++ b/src/nsterm.m | |||
| @@ -72,6 +72,12 @@ GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu) | |||
| 72 | #include "macfont.h" | 72 | #include "macfont.h" |
| 73 | #include <Carbon/Carbon.h> | 73 | #include <Carbon/Carbon.h> |
| 74 | #include <IOSurface/IOSurface.h> | 74 | #include <IOSurface/IOSurface.h> |
| 75 | /* ApplicationServices provides the macOS accessibility Zoom API | ||
| 76 | UAZoomEnabled and UAZoomChangeFocus (UniversalAccess framework). | ||
| 77 | Carbon.h already pulls in ApplicationServices on most SDK versions, | ||
| 78 | but the explicit import makes the dependency visible and guards | ||
| 79 | against SDK changes. */ | ||
| 80 | #import <ApplicationServices/ApplicationServices.h> | ||
| 75 | #endif | 81 | #endif |
| 76 | 82 | ||
| 77 | static EmacsMenu *dockMenu; | 83 | static EmacsMenu *dockMenu; |
| @@ -1086,6 +1092,126 @@ ns_update_begin (struct frame *f) | |||
| 1086 | [view lockFocus]; | 1092 | [view lockFocus]; |
| 1087 | } | 1093 | } |
| 1088 | 1094 | ||
| 1095 | /* -------------------------------------------------------------------------- | ||
| 1096 | macOS Accessibility Zoom Support | ||
| 1097 | -------------------------------------------------------------------------- */ | ||
| 1098 | #ifdef NS_IMPL_COCOA | ||
| 1099 | |||
| 1100 | static BOOL ns_is_UAZoomEnabled = NO; | ||
| 1101 | static unsigned long ns_UAZoomEnabled_last_called_time_ns = 0; | ||
| 1102 | static const unsigned long NS_UAZOOMENABLED_CACHE_INTERVAL_NS = | ||
| 1103 | (unsigned long)(500 * NSEC_PER_MSEC); /* 500ms. */ | ||
| 1104 | static NSTimeInterval NS_UAZOOMENABLED_DEFER_INTERVAL_SECS = 0.2; /* 200ms. */ | ||
| 1105 | static NSTimer *ns_deferred_UAZoomChangeFocus_timer = nil; | ||
| 1106 | |||
| 1107 | static BOOL | ||
| 1108 | ns_ua_zoom_enabled_p (void) | ||
| 1109 | /* -------------------------------------------------------------------------- | ||
| 1110 | Return the cached result of UAZoomEnabled. Refresh the cache every | ||
| 1111 | NS_UAZOOMENABLED_CACHE_INTERVAL_NS nanoseconds. | ||
| 1112 | |||
| 1113 | We cache the result to avoid the macOS Mach IPC Accessibility Server | ||
| 1114 | round trip cost on every Emacs cursor update. Since enabling Zoom | ||
| 1115 | requires an explicit user UI action that takes real user time, the | ||
| 1116 | cache TTL should be invisible to the user. | ||
| 1117 | |||
| 1118 | Use clock_gettime_nsec_np not CFAbsoluteTimeGetCurrent which depends | ||
| 1119 | on the wall clock which can be reset by the user or by NTP. | ||
| 1120 | |||
| 1121 | Main-thread-only and called from ns_update_end, below. | ||
| 1122 | -------------------------------------------------------------------------- */ | ||
| 1123 | { | ||
| 1124 | /* User-space equivalent to mach_absolute_time. */ | ||
| 1125 | unsigned long now_ns = clock_gettime_nsec_np (CLOCK_UPTIME_RAW); | ||
| 1126 | if (now_ns - ns_UAZoomEnabled_last_called_time_ns | ||
| 1127 | > NS_UAZOOMENABLED_CACHE_INTERVAL_NS) | ||
| 1128 | { | ||
| 1129 | ns_is_UAZoomEnabled = UAZoomEnabled (); | ||
| 1130 | ns_UAZoomEnabled_last_called_time_ns = now_ns; | ||
| 1131 | } | ||
| 1132 | return ns_is_UAZoomEnabled; | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | static inline CGRect | ||
| 1136 | ns_cg_rect_flip_y (CGRect r) | ||
| 1137 | /* -------------------------------------------------------------------------- | ||
| 1138 | Convert a CGRect from Cocoa screen coordinates (origin at bottom-left | ||
| 1139 | of the primary display) to CoreGraphics coordinates (origin at | ||
| 1140 | top-left of the primary display). CoreGraphics defines its | ||
| 1141 | coordinate origin at the top-left corner of the primary display and | ||
| 1142 | all screens share this global coordinate space, so the flip always | ||
| 1143 | uses the primary display height regardless of which screen R is on. | ||
| 1144 | -------------------------------------------------------------------------- */ | ||
| 1145 | { | ||
| 1146 | CGDirectDisplayID mainID = CGMainDisplayID (); | ||
| 1147 | if (mainID == kCGNullDirectDisplay) | ||
| 1148 | return r; | ||
| 1149 | CGFloat primaryH = CGDisplayBounds (mainID).size.height; | ||
| 1150 | if (primaryH <= 0) | ||
| 1151 | return r; | ||
| 1152 | r.origin.y = primaryH - r.origin.y - r.size.height; | ||
| 1153 | return r; | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | /* Cache cursor rects to call UAZoomChangeFocus only when the cursor | ||
| 1157 | position has changed, not merely when the cursor is blinking. | ||
| 1158 | See ns_draw_window_cursor and ns_update_end. */ | ||
| 1159 | static NSRect ns_UAZoom_cursor_rect_new; | ||
| 1160 | static NSRect ns_UAZoom_cursor_rect_old; | ||
| 1161 | |||
| 1162 | /* Track Zoom state per display cycle. Update the macOS Zoom cursor | ||
| 1163 | position when Zoom transitions to enabled. */ | ||
| 1164 | static BOOL ns_update_was_UAZoomEnabled = NO; | ||
| 1165 | |||
| 1166 | static void | ||
| 1167 | ns_UAZoomChangeFocus (EmacsView *view, BOOL force) | ||
| 1168 | /* -------------------------------------------------------------------------- | ||
| 1169 | Advise macOS Accessibility Zoom UAZoomChangeFocus of a potentially | ||
| 1170 | new cursor position. Force an updated position when Zoom transitions | ||
| 1171 | to enabled, or when the frame gets focus. | ||
| 1172 | -------------------------------------------------------------------------- */ | ||
| 1173 | { | ||
| 1174 | if (ns_ua_zoom_enabled_p ()) | ||
| 1175 | { | ||
| 1176 | force = force || !ns_update_was_UAZoomEnabled; | ||
| 1177 | ns_update_was_UAZoomEnabled = YES; | ||
| 1178 | if (NSIsEmptyRect (ns_UAZoom_cursor_rect_new)) | ||
| 1179 | return; | ||
| 1180 | if (force || !NSEqualRects (ns_UAZoom_cursor_rect_new, | ||
| 1181 | ns_UAZoom_cursor_rect_old)) | ||
| 1182 | { | ||
| 1183 | ns_UAZoom_cursor_rect_old = ns_UAZoom_cursor_rect_new; | ||
| 1184 | NSRect windowRect = [view convertRect:ns_UAZoom_cursor_rect_new | ||
| 1185 | toView:nil]; | ||
| 1186 | NSRect screenRect = [[view window] convertRectToScreen:windowRect]; | ||
| 1187 | CGRect cgRect = ns_cg_rect_flip_y (NSRectToCGRect (screenRect)); | ||
| 1188 | /* Some versions of macOS can ignore tiny rects, so we | ||
| 1189 | slightly expand a tiny one. Since we care mostly about its | ||
| 1190 | origin, this should be innocuous. */ | ||
| 1191 | cgRect.size.width = MAX (cgRect.size.width, 6); | ||
| 1192 | cgRect.size.height = MAX (cgRect.size.height, 10); | ||
| 1193 | if (force) | ||
| 1194 | { | ||
| 1195 | /* UAZoomChangeFocus needs old and new cursor positions to | ||
| 1196 | be different, and also it sometimes needs a kick. In | ||
| 1197 | both cases, we fake a cursor move followed by the real | ||
| 1198 | cursor move. */ | ||
| 1199 | CGRect cgRectJiggle = CGRectOffset (cgRect, 1.0, 1.0); | ||
| 1200 | if (UAZoomChangeFocus (&cgRectJiggle, NULL, | ||
| 1201 | kUAZoomFocusTypeInsertionPoint)) | ||
| 1202 | NSLog (@"UAZoomChangeFocus jiggle failed"); | ||
| 1203 | } | ||
| 1204 | if (UAZoomChangeFocus (&cgRect, NULL, | ||
| 1205 | kUAZoomFocusTypeInsertionPoint)) | ||
| 1206 | NSLog (@"UAZoomChangeFocus failed"); | ||
| 1207 | NSAccessibilityPostNotification | ||
| 1208 | (view, NSAccessibilityFocusedUIElementChangedNotification); | ||
| 1209 | } | ||
| 1210 | } | ||
| 1211 | else | ||
| 1212 | ns_update_was_UAZoomEnabled = NO; | ||
| 1213 | } | ||
| 1214 | #endif /* NS_IMPL_COCOA */ | ||
| 1089 | 1215 | ||
| 1090 | static void | 1216 | static void |
| 1091 | ns_update_end (struct frame *f) | 1217 | ns_update_end (struct frame *f) |
| @@ -1108,6 +1234,10 @@ ns_update_end (struct frame *f) | |||
| 1108 | [[view window] flushWindow]; | 1234 | [[view window] flushWindow]; |
| 1109 | #endif | 1235 | #endif |
| 1110 | 1236 | ||
| 1237 | #ifdef NS_IMPL_COCOA | ||
| 1238 | ns_UAZoomChangeFocus (view, false); | ||
| 1239 | #endif | ||
| 1240 | |||
| 1111 | unblock_input (); | 1241 | unblock_input (); |
| 1112 | ns_updating_frame = NULL; | 1242 | ns_updating_frame = NULL; |
| 1113 | } | 1243 | } |
| @@ -3238,6 +3368,16 @@ ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row, | |||
| 3238 | /* Prevent the cursor from being drawn outside the text area. */ | 3368 | /* Prevent the cursor from being drawn outside the text area. */ |
| 3239 | r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); | 3369 | r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA)); |
| 3240 | 3370 | ||
| 3371 | #ifdef NS_IMPL_COCOA | ||
| 3372 | /* Cache the cursor rect for macOS Accessibility Zoom integration (see | ||
| 3373 | ns_update_end). Only store the rect for the active cursor --- | ||
| 3374 | inactive windows must not overwrite the value because redisplay may | ||
| 3375 | draw multiple windows per frame and the drawing order is not | ||
| 3376 | guaranteed. */ | ||
| 3377 | if (active_p) | ||
| 3378 | ns_UAZoom_cursor_rect_new = r; | ||
| 3379 | #endif | ||
| 3380 | |||
| 3241 | ns_focus (f, NULL, 0); | 3381 | ns_focus (f, NULL, 0); |
| 3242 | 3382 | ||
| 3243 | NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; | 3383 | NSGraphicsContext *ctx = [NSGraphicsContext currentContext]; |
| @@ -6384,6 +6524,14 @@ ns_term_shutdown (int sig) | |||
| 6384 | } | 6524 | } |
| 6385 | #endif | 6525 | #endif |
| 6386 | 6526 | ||
| 6527 | #ifdef NS_IMPL_COCOA | ||
| 6528 | /* Is accessibility enabled for this process/bundle? */ | ||
| 6529 | if (AXIsProcessTrusted()) | ||
| 6530 | NSLog (@"Emacs is macOS AXIsProcessTrusted"); | ||
| 6531 | else | ||
| 6532 | NSLog (@"Emacs is not macOS AXIsProcessTrusted"); | ||
| 6533 | #endif | ||
| 6534 | |||
| 6387 | ns_send_appdefined (-2); | 6535 | ns_send_appdefined (-2); |
| 6388 | } | 6536 | } |
| 6389 | 6537 | ||
| @@ -7300,6 +7448,12 @@ ns_create_font_panel_buttons (id target, SEL select, SEL cancel_action) | |||
| 7300 | return [self firstRectForCharacterRange: range]; | 7448 | return [self firstRectForCharacterRange: range]; |
| 7301 | } | 7449 | } |
| 7302 | 7450 | ||
| 7451 | - (NSRect)accessibilityFrame | ||
| 7452 | { | ||
| 7453 | EmacsView *view = FRAME_NS_VIEW (*emacsframe); | ||
| 7454 | return [[view window] convertRectToScreen: ns_UAZoom_cursor_rect_new]; | ||
| 7455 | } | ||
| 7456 | |||
| 7303 | #endif /* NS_IMPL_COCOA */ | 7457 | #endif /* NS_IMPL_COCOA */ |
| 7304 | 7458 | ||
| 7305 | /*********************************************************************** | 7459 | /*********************************************************************** |
| @@ -8257,12 +8411,48 @@ ns_in_echo_area (void) | |||
| 8257 | ns_frame_rehighlight (*emacsframe); | 8411 | ns_frame_rehighlight (*emacsframe); |
| 8258 | [self adjustEmacsFrameRect]; | 8412 | [self adjustEmacsFrameRect]; |
| 8259 | 8413 | ||
| 8414 | #ifdef NS_IMPL_COCOA | ||
| 8415 | EmacsView *view = FRAME_NS_VIEW (*emacsframe); | ||
| 8416 | /* Make sure we have focus and the timer isn't already scheduled. */ | ||
| 8417 | if (self.window.firstResponder == view | ||
| 8418 | && !ns_deferred_UAZoomChangeFocus_timer) | ||
| 8419 | { | ||
| 8420 | /* Calls to ns_UAZoomChangeFocus are synchronous. We defer the | ||
| 8421 | call to give macOS time to finish window compositing or the | ||
| 8422 | calls can be silently ignored by the Zoom daemon and with no | ||
| 8423 | errors reported. This also helps ensure ns_draw_window_cursor | ||
| 8424 | has populated ns_UAZoom_cursor_rect_new. The 200 ms delay was | ||
| 8425 | chosen as a balance between macOS headroom and user | ||
| 8426 | perception. */ | ||
| 8427 | ns_deferred_UAZoomChangeFocus_timer | ||
| 8428 | = [[NSTimer | ||
| 8429 | scheduledTimerWithTimeInterval: | ||
| 8430 | NS_UAZOOMENABLED_DEFER_INTERVAL_SECS | ||
| 8431 | target: self | ||
| 8432 | selector: | ||
| 8433 | @selector (deferred_UAZoomChangeFocus_handler:) | ||
| 8434 | userInfo: 0 | ||
| 8435 | repeats: NO] | ||
| 8436 | retain]; | ||
| 8437 | } | ||
| 8438 | #endif | ||
| 8439 | |||
| 8260 | event.kind = FOCUS_IN_EVENT; | 8440 | event.kind = FOCUS_IN_EVENT; |
| 8261 | XSETFRAME (event.frame_or_window, *emacsframe); | 8441 | XSETFRAME (event.frame_or_window, *emacsframe); |
| 8262 | kbd_buffer_store_event (&event); | 8442 | kbd_buffer_store_event (&event); |
| 8263 | ns_send_appdefined (-1); // Kick main loop | 8443 | ns_send_appdefined (-1); // Kick main loop |
| 8264 | } | 8444 | } |
| 8265 | 8445 | ||
| 8446 | #ifdef NS_IMPL_COCOA | ||
| 8447 | - (void)deferred_UAZoomChangeFocus_handler: (NSTimer *)timer | ||
| 8448 | { | ||
| 8449 | EmacsView *view = FRAME_NS_VIEW (*emacsframe); | ||
| 8450 | ns_UAZoomChangeFocus (view, true); | ||
| 8451 | [ns_deferred_UAZoomChangeFocus_timer invalidate]; | ||
| 8452 | [ns_deferred_UAZoomChangeFocus_timer release]; | ||
| 8453 | ns_deferred_UAZoomChangeFocus_timer = nil; | ||
| 8454 | } | ||
| 8455 | #endif | ||
| 8266 | 8456 | ||
| 8267 | - (void)windowDidResignKey: (NSNotification *)notification | 8457 | - (void)windowDidResignKey: (NSNotification *)notification |
| 8268 | /* cf. x_detect_focus_change(), x_focus_changed(), x_new_focus_frame() */ | 8458 | /* cf. x_detect_focus_change(), x_focus_changed(), x_new_focus_frame() */ |
| @@ -8365,6 +8555,13 @@ ns_in_echo_area (void) | |||
| 8365 | 8555 | ||
| 8366 | FRAME_NS_VIEW (f) = self; | 8556 | FRAME_NS_VIEW (f) = self; |
| 8367 | *emacsframe = f; | 8557 | *emacsframe = f; |
| 8558 | |||
| 8559 | #ifdef NS_IMPL_COCOA | ||
| 8560 | /* macOS Accessibility Zoom Support. */ | ||
| 8561 | ns_UAZoom_cursor_rect_new = NSZeroRect; | ||
| 8562 | ns_UAZoom_cursor_rect_old = NSZeroRect; | ||
| 8563 | #endif | ||
| 8564 | |||
| 8368 | #ifdef NS_IMPL_COCOA | 8565 | #ifdef NS_IMPL_COCOA |
| 8369 | old_title = 0; | 8566 | old_title = 0; |
| 8370 | maximizing_resize = NO; | 8567 | maximizing_resize = NO; |