aboutsummaryrefslogtreecommitdiffstats
path: root/src/nsterm.m
diff options
context:
space:
mode:
authorHelmut Eller2026-04-04 20:59:46 +0200
committerHelmut Eller2026-04-04 20:59:46 +0200
commit6eec001187e8551f32b6498e6dc60cdc58c2e515 (patch)
tree13233de9f0a05ef86a51500e8b1870b75ff20c81 /src/nsterm.m
parente4ea27119e79012f9d651cb61d1115589d91ef39 (diff)
parent01a9d78a7e4c7d7fa5b799e4fdc2caf77a012734 (diff)
downloademacs-feature/igc3.tar.gz
emacs-feature/igc3.zip
Merge branch 'master' into feature/igc3feature/igc3
Diffstat (limited to 'src/nsterm.m')
-rw-r--r--src/nsterm.m197
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
77static EmacsMenu *dockMenu; 83static 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
1100static BOOL ns_is_UAZoomEnabled = NO;
1101static unsigned long ns_UAZoomEnabled_last_called_time_ns = 0;
1102static const unsigned long NS_UAZOOMENABLED_CACHE_INTERVAL_NS =
1103 (unsigned long)(500 * NSEC_PER_MSEC); /* 500ms. */
1104static NSTimeInterval NS_UAZOOMENABLED_DEFER_INTERVAL_SECS = 0.2; /* 200ms. */
1105static NSTimer *ns_deferred_UAZoomChangeFocus_timer = nil;
1106
1107static BOOL
1108ns_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
1135static inline CGRect
1136ns_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. */
1159static NSRect ns_UAZoom_cursor_rect_new;
1160static 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. */
1164static BOOL ns_update_was_UAZoomEnabled = NO;
1165
1166static void
1167ns_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
1090static void 1216static void
1091ns_update_end (struct frame *f) 1217ns_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;