@@ -130,6 +130,8 @@ CalendariumDesklet.prototype = {
130130 global . logError ( "Calendarium: _setupUI crash [" + e . message + "] stack:\n" + ( e . stack || "(no stack)" ) ) ;
131131 throw e ;
132132 }
133+ // Detect the computer's location from its system timezone (async).
134+ this . _resolveSystemLocationAsync ( ) ;
133135 // Load locale data files asynchronously; refresh again when done.
134136 this . _loadNamedayData ( ( ) => this . _refresh ( ) ) ;
135137 } ,
@@ -339,32 +341,99 @@ CalendariumDesklet.prototype = {
339341 } ,
340342
341343 /**
342- * The computer's own IANA timezone name (e.g. "Europe/Budapest"), or null
343- * if it cannot be determined .
344+ * Read a text file asynchronously; cb(text) with the contents as a string,
345+ * or cb(null) on any failure. Never blocks the main loop .
344346 */
345- _systemTimezoneName : function ( ) {
346- try {
347- let id = GLib . TimeZone . new_local ( ) . get_identifier ( ) ;
348- if ( id && id . indexOf ( "/" ) !== - 1 ) return id ;
349- } catch ( e ) { }
350- try {
351- let [ ok , contents ] = GLib . file_get_contents ( "/etc/timezone" ) ;
352- if ( ok ) {
353- let s = ( ( contents instanceof Uint8Array )
347+ _readTextFileAsync : function ( path , cb ) {
348+ let f = Gio . File . new_for_path ( path ) ;
349+ f . load_contents_async ( null , function ( obj , res ) {
350+ try {
351+ let [ ok , contents ] = f . load_contents_finish ( res ) ;
352+ if ( ! ok ) { cb ( null ) ; return ; }
353+ cb ( ( contents instanceof Uint8Array )
354354 ? new TextDecoder ( ) . decode ( contents )
355- : imports . byteArray . toString ( contents ) ) . trim ( ) ;
356- if ( s . indexOf ( "/" ) !== - 1 ) return s ;
355+ : imports . byteArray . toString ( contents ) ) ;
356+ } catch ( e ) {
357+ cb ( null ) ;
357358 }
358- } catch ( e ) { }
359+ } ) ;
360+ } ,
361+
362+ /**
363+ * Resolve the computer's approximate location from its system IANA
364+ * timezone, fully asynchronously, and cache it in this._sysLoc as
365+ * { lat, lon, tz }. Triggers a redraw when done if the primary location
366+ * is auto-detected. Falls back to DEFAULT_LAT/DEFAULT_LON (Budapest).
367+ */
368+ _resolveSystemLocationAsync : function ( ) {
369+ if ( this . _sysLoc ) return ;
370+ let self = this ;
371+
372+ let finish = function ( tzName ) {
373+ let loc = { lat : DEFAULT_LAT , lon : DEFAULT_LON , tz : tzName || null } ;
374+ let apply = function ( coords ) {
375+ if ( coords ) { loc . lat = coords . lat ; loc . lon = coords . lon ; }
376+ self . _sysLoc = loc ;
377+ if ( ! self . _isDestroyed && ! self . use_manual_location ) {
378+ self . _onSettingChanged ( ) ;
379+ }
380+ } ;
381+ if ( tzName ) self . _tzTabCoordsAsync ( tzName , apply ) ;
382+ else apply ( null ) ;
383+ } ;
384+
385+ // 1. GLib.TimeZone (no blocking file read).
359386 try {
360- let target = Gio . File . new_for_path ( "/etc/localtime" )
361- . query_info ( "standard::symlink-target" ,
362- Gio . FileQueryInfoFlags . NOFOLLOW_SYMLINKS , null )
363- . get_symlink_target ( ) ;
364- let m = target && target . match ( / z o n e i n f o \/ ( .+ ) $ / ) ;
365- if ( m ) return m [ 1 ] ;
387+ let id = GLib . TimeZone . new_local ( ) . get_identifier ( ) ;
388+ if ( id && id . indexOf ( "/" ) !== - 1 ) { finish ( id ) ; return ; }
366389 } catch ( e ) { }
367- return null ;
390+
391+ // 2. /etc/timezone (Debian/Ubuntu/Mint).
392+ this . _readTextFileAsync ( "/etc/timezone" , function ( text ) {
393+ let s = text && text . trim ( ) ;
394+ if ( s && s . indexOf ( "/" ) !== - 1 ) { finish ( s ) ; return ; }
395+
396+ // 3. /etc/localtime symlink target.
397+ let f = Gio . File . new_for_path ( "/etc/localtime" ) ;
398+ f . query_info_async ( "standard::symlink-target" ,
399+ Gio . FileQueryInfoFlags . NOFOLLOW_SYMLINKS ,
400+ GLib . PRIORITY_DEFAULT , null , function ( obj , res ) {
401+ let tzName = null ;
402+ try {
403+ let target = f . query_info_finish ( res ) . get_symlink_target ( ) ;
404+ let m = target && target . match ( / z o n e i n f o \/ ( .+ ) $ / ) ;
405+ if ( m ) tzName = m [ 1 ] ;
406+ } catch ( e ) { }
407+ finish ( tzName ) ;
408+ } ) ;
409+ } ) ;
410+ } ,
411+
412+ /**
413+ * Look up approximate coordinates for an IANA timezone name in the tzdata
414+ * zone1970.tab / zone.tab table, asynchronously. cb({lat, lon}) or cb(null).
415+ */
416+ _tzTabCoordsAsync : function ( tzName , cb ) {
417+ let self = this ;
418+ let paths = [ "/usr/share/zoneinfo/zone1970.tab" ,
419+ "/usr/share/zoneinfo/zone.tab" ] ;
420+ let tryPath = function ( idx ) {
421+ if ( idx >= paths . length ) { cb ( null ) ; return ; }
422+ self . _readTextFileAsync ( paths [ idx ] , function ( text ) {
423+ if ( ! text ) { tryPath ( idx + 1 ) ; return ; }
424+ let lines = text . split ( "\n" ) ;
425+ for ( let i = 0 ; i < lines . length ; i ++ ) {
426+ if ( ! lines [ i ] || lines [ i ] [ 0 ] === "#" ) continue ;
427+ let cols = lines [ i ] . split ( "\t" ) ;
428+ if ( cols . length < 3 ) continue ;
429+ if ( cols [ 2 ] . trim ( ) !== tzName ) continue ;
430+ cb ( self . _parseIso6709 ( cols [ 1 ] . trim ( ) ) ) ;
431+ return ;
432+ }
433+ tryPath ( idx + 1 ) ;
434+ } ) ;
435+ } ;
436+ tryPath ( 0 ) ;
368437 } ,
369438
370439 /**
@@ -384,51 +453,13 @@ CalendariumDesklet.prototype = {
384453 lon : Math . round ( lon * 10000 ) / 10000 } ;
385454 } ,
386455
387- /**
388- * Approximate coordinates of a system IANA timezone, looked up in the
389- * tzdata zone1970.tab / zone.tab table. Returns { lat, lon } or null.
390- */
391- _tzTabCoords : function ( tzName ) {
392- let paths = [ "/usr/share/zoneinfo/zone1970.tab" ,
393- "/usr/share/zoneinfo/zone.tab" ] ;
394- for ( let p = 0 ; p < paths . length ; p ++ ) {
395- let text ;
396- try {
397- let [ ok , contents ] = GLib . file_get_contents ( paths [ p ] ) ;
398- if ( ! ok ) continue ;
399- text = ( contents instanceof Uint8Array )
400- ? new TextDecoder ( ) . decode ( contents )
401- : imports . byteArray . toString ( contents ) ;
402- } catch ( e ) { continue ; }
403- let lines = text . split ( "\n" ) ;
404- for ( let i = 0 ; i < lines . length ; i ++ ) {
405- if ( ! lines [ i ] || lines [ i ] [ 0 ] === "#" ) continue ;
406- let cols = lines [ i ] . split ( "\t" ) ;
407- if ( cols . length < 3 ) continue ;
408- if ( cols [ 2 ] . trim ( ) !== tzName ) continue ;
409- return this . _parseIso6709 ( cols [ 1 ] . trim ( ) ) ;
410- }
411- }
412- return null ;
413- } ,
414-
415456 /**
416457 * Approximate location of the computer, derived from its system IANA
417- * timezone. Result is { lat, lon, tz }; cached for the desklet's lifetime.
418- * Falls back to DEFAULT_LAT/DEFAULT_LON (Budapest) when the timezone
419- * cannot be resolved to coordinates.
458+ * timezone by _resolveSystemLocationAsync(). Returns the cached
459+ * { lat, lon, tz }, or the Budapest default until resolution completes.
420460 */
421461 _systemLocation : function ( ) {
422- if ( this . _sysLoc ) return this . _sysLoc ;
423- let loc = { lat : DEFAULT_LAT , lon : DEFAULT_LON , tz : null } ;
424- let tzName = this . _systemTimezoneName ( ) ;
425- loc . tz = tzName ;
426- if ( tzName ) {
427- let coords = this . _tzTabCoords ( tzName ) ;
428- if ( coords ) { loc . lat = coords . lat ; loc . lon = coords . lon ; }
429- }
430- this . _sysLoc = loc ;
431- return loc ;
462+ return this . _sysLoc || { lat : DEFAULT_LAT , lon : DEFAULT_LON , tz : null } ;
432463 } ,
433464
434465 /**
0 commit comments