diff --git a/core/internal/geolocation/client.go b/core/internal/geolocation/client.go index b5d03077..5b441215 100644 --- a/core/internal/geolocation/client.go +++ b/core/internal/geolocation/client.go @@ -5,21 +5,38 @@ import "github.com/AvengeMedia/DankMaterialShell/core/internal/log" func NewClient() Client { geoclueClient, err := newGeoClueClient() if err != nil { - log.Warnf("Failed to initialize GeoClue2 client: %v", err) - log.Info("Falling back to IP location") - return newIpClient() + log.Warnf("GeoClue2 unavailable: %v", err) + return newSeededIpClient() } loc, _ := geoclueClient.GetLocation() if loc.Latitude != 0 || loc.Longitude != 0 { + log.Info("Using GeoClue2 location") return geoclueClient } log.Info("GeoClue2 has no fix yet, seeding with IP location") - ipClient := newIpClient() - if ipLoc, err := ipClient.GetLocation(); err == nil { - geoclueClient.SeedLocation(ipLoc) + ipLoc, err := fetchIPLocation() + if err != nil { + log.Warnf("IP location seed failed: %v", err) + return geoclueClient } + log.Info("Seeded GeoClue2 with IP location") + geoclueClient.SeedLocation(Location{Latitude: ipLoc.Latitude, Longitude: ipLoc.Longitude}) return geoclueClient } + +func newSeededIpClient() *IpClient { + client := newIpClient() + ipLoc, err := fetchIPLocation() + if err != nil { + log.Warnf("IP location also failed: %v", err) + return client + } + + log.Info("Using IP location") + client.currLocation.Latitude = ipLoc.Latitude + client.currLocation.Longitude = ipLoc.Longitude + return client +} diff --git a/core/internal/geolocation/client_ip.go b/core/internal/geolocation/client_ip.go index cc62bcd5..0e68cae7 100644 --- a/core/internal/geolocation/client_ip.go +++ b/core/internal/geolocation/client_ip.go @@ -6,26 +6,27 @@ import ( "io" "net/http" "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" ) type IpClient struct { currLocation *Location } +type ipLocationResult struct { + Location + City string +} + type ipAPIResponse struct { - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - City string `json:"city"` + Status string `json:"status"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + City string `json:"city"` } func newIpClient() *IpClient { return &IpClient{ - currLocation: &Location{ - Latitude: 0.0, - Longitude: 0.0, - }, + currLocation: &Location{}, } } @@ -34,55 +35,57 @@ func (c *IpClient) Subscribe(id string) chan Location { if location, err := c.GetLocation(); err == nil { ch <- location } - return ch } -func (c *IpClient) Unsubscribe(id string) { - // Stub -} +func (c *IpClient) Unsubscribe(id string) {} -func (c *IpClient) Close() { - // Stub -} +func (c *IpClient) Close() {} func (c *IpClient) GetLocation() (Location, error) { - client := &http.Client{ - Timeout: 10 * time.Second, + if c.currLocation.Latitude != 0 || c.currLocation.Longitude != 0 { + return *c.currLocation, nil } - result := Location{ - Latitude: 0.0, - Longitude: 0.0, + result, err := fetchIPLocation() + if err != nil { + return Location{}, err } + c.currLocation.Latitude = result.Latitude + c.currLocation.Longitude = result.Longitude + return *c.currLocation, nil +} + +func fetchIPLocation() (ipLocationResult, error) { + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Get("http://ip-api.com/json/") if err != nil { - return result, fmt.Errorf("failed to fetch IP location: %w", err) + return ipLocationResult{}, fmt.Errorf("failed to fetch IP location: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return result, fmt.Errorf("ip-api.com returned status %d", resp.StatusCode) + return ipLocationResult{}, fmt.Errorf("ip-api.com returned status %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { - return result, fmt.Errorf("failed to read response: %w", err) + return ipLocationResult{}, fmt.Errorf("failed to read response: %w", err) } var data ipAPIResponse if err := json.Unmarshal(body, &data); err != nil { - return result, fmt.Errorf("failed to parse response: %w", err) + return ipLocationResult{}, fmt.Errorf("failed to parse response: %w", err) } - if data.Lat == 0 && data.Lon == 0 { - return result, fmt.Errorf("missing location data in response") + if data.Status == "fail" || (data.Lat == 0 && data.Lon == 0) { + return ipLocationResult{}, fmt.Errorf("ip-api.com returned no location data") } - log.Infof("Fetched IP-based location: %s (%.4f, %.4f)", data.City, data.Lat, data.Lon) - result.Latitude = data.Lat - result.Longitude = data.Lon - - return result, nil + return ipLocationResult{ + Location: Location{Latitude: data.Lat, Longitude: data.Lon}, + City: data.City, + }, nil } diff --git a/quickshell/Services/LocationService.qml b/quickshell/Services/LocationService.qml index 281b9d0e..0fde5870 100644 --- a/quickshell/Services/LocationService.qml +++ b/quickshell/Services/LocationService.qml @@ -17,11 +17,9 @@ Singleton { signal locationChanged(var data) - readonly property var lowPriorityCmd: ["nice", "-n", "19", "ionice", "-c3"] - readonly property var curlBaseCmd: ["curl", "-sS", "--fail", "--connect-timeout", "3", "--max-time", "6", "--limit-rate", "100k", "--compressed"] - - Component.onCompleted: { - getState(); + onLocationAvailableChanged: { + if (locationAvailable && !valid) + getState(); } Connections { @@ -46,50 +44,12 @@ Singleton { } function getState() { - if (!locationAvailable) { - fetchIPLocation(); + if (!locationAvailable) return; - } DMSService.sendRequest("location.getState", null, response => { - if (response.result && (response.result.latitude !== 0 || response.result.longitude !== 0)) { + if (response.result) handleStateUpdate(response.result); - return; - } - fetchIPLocation(); }); } - - function fetchIPLocation() { - if (root.valid) - return; - ipLocationFetcher.running = true; - } - - Process { - id: ipLocationFetcher - command: root.lowPriorityCmd.concat(root.curlBaseCmd).concat(["http://ip-api.com/json/"]) - running: false - - stdout: StdioCollector { - onStreamFinished: { - const raw = text.trim(); - if (!raw || raw[0] !== "{") - return; - - try { - const data = JSON.parse(raw); - if (data.status === "fail") - return; - - const lat = parseFloat(data.lat); - const lon = parseFloat(data.lon); - if (isNaN(lat) || isNaN(lon) || (lat === 0 && lon === 0)) - return; - - root.handleStateUpdate({ latitude: lat, longitude: lon }); - } catch (e) {} - } - } - } }