22import * as Electron from "electron" ;
33import { SNAP_SHOT_PERMISSION_HELPER_CHANNEL } from "../ipc/channels.ts" ;
44
5+ import {
6+ settingsHelperBounds ,
7+ watchMacSettingsWindow ,
8+ type SettingsWindow ,
9+ } from "./MacSettingsWindow.ts" ;
10+
511type Permission = "screen-recording" | "accessibility" ;
612
713const permissionGranted = ( permission : Permission ) =>
@@ -34,24 +40,26 @@ function helperHtml(permission: Permission, icon: string) {
3440 const title = permission === "screen-recording" ? "Screen Recording" : "Accessibility" ;
3541 return `<!doctype html><html lang="en"><head><meta charset="utf-8">
3642<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'">
37- <title>Set up snapshots </title><style>
43+ <title>Set up ${ title } </title><style>
3844:root { color-scheme: light dark; --base: #fff; --row: #e7e7e7; --text: #292929; --line: #e3e3e3; }
3945@media (prefers-color-scheme: dark) { :root { --base: #242424; --row: #383838; --text: #f5f5f5; --line: #484848; } }
4046* { box-sizing: border-box; }
41- body { margin: 0; padding: 20px; height: 136px; border: 1px solid var(--line); border-radius: 24px; background: var(--base); color: var(--text); font: 15px/22px -apple-system, BlinkMacSystemFont, sans-serif; user-select: none; }
42- header { -webkit-app-region: drag; font-weight: 600; white-space: nowrap; }
47+ html { background: transparent; }
48+ body { margin: 0; background: transparent; color: var(--text); font: 15px/22px -apple-system, BlinkMacSystemFont, sans-serif; user-select: none; }
49+ #panel { position: relative; margin: 2px; padding: 20px; height: 136px; border: 1px solid var(--line); border-radius: 24px; background: var(--base); }
50+ header { font-weight: 600; white-space: nowrap; }
4351button { font: inherit; color: inherit; }
4452button:focus-visible { outline: 2px solid #007aff; outline-offset: 3px; }
4553#close { position: absolute; right: 8px; top: 6px; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%; background: var(--base); font-size: 18px; cursor: pointer; opacity: 0; }
46- body :hover #close, #close:focus-visible { opacity: 1; }
54+ #panel :hover #close, #close:focus-visible { opacity: 1; }
4755#app { display: flex; align-items: center; gap: 12px; width: 100%; height: 52px; margin-top: 20px; padding: 8px 12px; border: 0; border-radius: 10px; background: var(--row); cursor: grab; text-align: left; font-size: 16px; font-weight: 600; }
4856#app:active { cursor: grabbing; }
4957img { width: 32px; height: 32px; pointer-events: none; }
50- </style></head><body>
58+ </style></head><body><main id="panel">
5159<button id="close" aria-label="Close permission helper">×</button>
52- <header>↑ Drag T3 Code into the ${ title } list above</header>
60+ <header>↑ Drag T3 Code into the list above</header>
5361<button id="app" draggable="true" aria-label="Drag T3 Code to System Settings, or click to reveal in Finder"><img src="${ escapeHtml ( icon ) } " alt="" draggable="false">T3 Code</button>
54- </body></html>` ;
62+ </main></ body></html>` ;
5563}
5664
5765/** Owns one temporary panel and its IPC listener. Closing it releases all resources. */
@@ -81,20 +89,13 @@ export class MacPermissionHelper {
8189 . find ( ( image ) => ! image . isEmpty ( ) ) ;
8290 if ( ! appIcon ) throw new Error ( "The packaged T3 Code icon is missing." ) ;
8391 const icon = appIcon . resize ( { width : 64 , height : 64 } ) ;
84- const display = owner
85- ? Electron . screen . getDisplayMatching ( owner . getBounds ( ) )
86- : Electron . screen . getDisplayNearestPoint ( Electron . screen . getCursorScreenPoint ( ) ) ;
87- const area = display . workArea ;
88- const width = Math . min ( 560 , area . width ) ;
89- const height = 136 ;
9092 const window = new Electron . BrowserWindow ( {
91- width,
92- height,
93- x : Math . round ( area . x + ( area . width - width ) / 2 ) ,
94- y : Math . max ( area . y , area . y + area . height - height - 28 ) ,
93+ width : 560 ,
94+ height : 140 ,
9595 show : false ,
9696 frame : false ,
9797 transparent : true ,
98+ roundedCorners : false ,
9899 backgroundColor : "#00000000" ,
99100 hasShadow : false ,
100101 resizable : false ,
@@ -136,12 +137,39 @@ export class MacPermissionHelper {
136137 finish ( ) ;
137138 }
138139 } ;
140+ let settingsWindow : SettingsWindow = null ;
141+ let foundSettings = false ;
142+ const syncPosition = ( ) => {
143+ if ( window . isDestroyed ( ) ) return ;
144+ if ( ! settingsWindow && foundSettings ) {
145+ window . close ( ) ;
146+ return ;
147+ }
148+ if ( ! settingsWindow || ( ! settingsWindow . frontmost && ! window . isFocused ( ) ) ) {
149+ window . hide ( ) ;
150+ return ;
151+ }
152+ const bounds = settingsHelperBounds ( settingsWindow ) ;
153+ const current = window . getBounds ( ) ;
154+ if (
155+ current . x !== bounds . x ||
156+ current . y !== bounds . y ||
157+ current . width !== bounds . width ||
158+ current . height !== bounds . height
159+ ) {
160+ window . setBounds ( bounds , false ) ;
161+ }
162+ if ( ! window . isVisible ( ) ) window . showInactive ( ) ;
163+ } ;
164+ let stopTracking = ( ) => { } ;
165+ window . on ( "blur" , syncPosition ) ;
139166 const onOwnerClosed = ( ) => window . destroy ( ) ;
140167 Electron . ipcMain . on ( SNAP_SHOT_PERMISSION_HELPER_CHANNEL , onMessage ) ;
141168 const timer = setInterval ( check , 1_000 ) ;
142169 owner ?. once ( "closed" , onOwnerClosed ) ;
143170 window . once ( "closed" , ( ) => {
144171 clearInterval ( timer ) ;
172+ stopTracking ( ) ;
145173 Electron . ipcMain . removeListener ( SNAP_SHOT_PERMISSION_HELPER_CHANNEL , onMessage ) ;
146174 owner ?. removeListener ( "closed" , onOwnerClosed ) ;
147175 if ( this . window === window ) this . window = undefined ;
@@ -152,7 +180,13 @@ export class MacPermissionHelper {
152180 await window . loadURL (
153181 `data:text/html;charset=utf-8,${ encodeURIComponent ( helperHtml ( permission , icon . toDataURL ( ) ) ) } ` ,
154182 ) ;
155- if ( ! window . isDestroyed ( ) ) window . showInactive ( ) ;
183+ if ( ! window . isDestroyed ( ) ) {
184+ stopTracking = watchMacSettingsWindow ( ( current ) => {
185+ settingsWindow = current ;
186+ if ( current ) foundSettings = true ;
187+ syncPosition ( ) ;
188+ } ) ;
189+ }
156190 } catch ( error ) {
157191 if ( ! window . isDestroyed ( ) ) window . destroy ( ) ;
158192 throw error ;
0 commit comments