@@ -17,9 +17,9 @@ import { makeComponentLogger } from "./DesktopObservability.ts";
1717// Electron's app.setAsDefaultProtocolClient resolves the desktop id from
1818// setDesktopName, which cannot match those files — so the browser keeps
1919// prompting "Choose an application" for every OAuth callback. Instead, write
20- // our own handler entry pointing at the current AppImage and claim the
21- // scheme default via xdg-mime, exactly what the file manager's "set as
22- // default" checkbox would record in mimeapps.list.
20+ // our own handler entry pointing at the current AppImage, refresh the desktop
21+ // MIME cache so desktop environments recognize that entry as a handler, and
22+ // use xdg-mime to record it as the scheme default in mimeapps.list.
2323export const URL_HANDLER_DESKTOP_ENTRY_NAME = "t3code-url-handler.desktop" ;
2424
2525const { logInfo, logWarning } = makeComponentLogger ( "desktop-linux-url-handler" ) ;
@@ -42,6 +42,23 @@ export class DesktopLinuxUrlHandlerRegistrationError extends Schema.TaggedErrorC
4242
4343const isRegistrationError = Schema . is ( DesktopLinuxUrlHandlerRegistrationError ) ;
4444
45+ export class DesktopLinuxUrlHandlerCacheRefreshError extends Schema . TaggedErrorClass < DesktopLinuxUrlHandlerCacheRefreshError > ( ) (
46+ "DesktopLinuxUrlHandlerCacheRefreshError" ,
47+ {
48+ applicationsDir : Schema . String ,
49+ exitCode : Schema . optionalKey ( Schema . Number ) ,
50+ cause : Schema . optionalKey ( Schema . Defect ( ) ) ,
51+ } ,
52+ ) {
53+ override get message ( ) : string {
54+ const exitCode =
55+ this . exitCode === undefined ? "" : `, update-desktop-database exit code ${ this . exitCode } ` ;
56+ return `Failed to refresh the desktop MIME cache at ${ this . applicationsDir } ${ exitCode } .` ;
57+ }
58+ }
59+
60+ const isCacheRefreshError = Schema . is ( DesktopLinuxUrlHandlerCacheRefreshError ) ;
61+
4562const escapeDesktopEntryString = ( value : string ) : string =>
4663 value
4764 . replaceAll ( "\\" , "\\\\" )
@@ -128,6 +145,37 @@ export const make = Effect.gen(function* () {
128145 ) ,
129146 ) ;
130147
148+ const updateDesktopDatabase = Effect . scoped (
149+ Effect . gen ( function * ( ) {
150+ const command = ChildProcess . make (
151+ "update-desktop-database" ,
152+ [ environment . linuxApplicationsDir ] ,
153+ {
154+ stdin : "ignore" ,
155+ stdout : "ignore" ,
156+ stderr : "ignore" ,
157+ } ,
158+ ) ;
159+ const handle = yield * spawner . spawn ( command ) ;
160+ const exitCode = yield * handle . exitCode ;
161+ if ( exitCode !== 0 ) {
162+ return yield * new DesktopLinuxUrlHandlerCacheRefreshError ( {
163+ applicationsDir : environment . linuxApplicationsDir ,
164+ exitCode,
165+ } ) ;
166+ }
167+ } ) ,
168+ ) . pipe (
169+ Effect . mapError ( ( error ) =>
170+ isCacheRefreshError ( error )
171+ ? error
172+ : new DesktopLinuxUrlHandlerCacheRefreshError ( {
173+ applicationsDir : environment . linuxApplicationsDir ,
174+ cause : error ,
175+ } ) ,
176+ ) ,
177+ ) ;
178+
131179 const setDefaultHandler = Effect . scoped (
132180 Effect . gen ( function * ( ) {
133181 const command = ChildProcess . make (
@@ -166,6 +214,21 @@ export const make = Effect.gen(function* () {
166214 return ;
167215 }
168216 yield * writeDesktopEntry ;
217+
218+ yield * updateDesktopDatabase . pipe (
219+ // Some MIME implementations, including GIO, use mimeinfo.cache to verify
220+ // that a desktop entry is associated with a scheme. Cache refresh is
221+ // independently best-effort so a missing update-desktop-database executable
222+ // does not prevent xdg-mime from recording the requested default.
223+ Effect . catch ( ( error ) =>
224+ logWarning ( "desktop MIME cache refresh failed" , {
225+ applicationsDir : environment . linuxApplicationsDir ,
226+ message : error . message ,
227+ ...( error . exitCode === undefined ? { } : { exitCode : error . exitCode } ) ,
228+ } ) ,
229+ ) ,
230+ ) ;
231+
169232 yield * setDefaultHandler ;
170233 yield * logInfo ( "registered URL scheme handler" , { scheme } ) ;
171234 } ) . pipe (
0 commit comments