diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation')
4 files changed, 391 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client.go new file mode 100644 index 0000000..5b44121 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client.go @@ -0,0 +1,42 @@ +package geolocation + +import "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + +func NewClient() Client { + geoclueClient, err := newGeoClueClient() + if err != nil { + 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") + 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/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_geoclue.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_geoclue.go new file mode 100644 index 0000000..a06aadf --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_geoclue.go @@ -0,0 +1,243 @@ +package geolocation + +import ( + "fmt" + "sync" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/godbus/dbus/v5" +) + +const ( + dbusGeoClueService = "org.freedesktop.GeoClue2" + dbusGeoCluePath = "/org/freedesktop/GeoClue2" + dbusGeoClueInterface = dbusGeoClueService + + dbusGeoClueManagerPath = dbusGeoCluePath + "/Manager" + dbusGeoClueManagerInterface = dbusGeoClueInterface + ".Manager" + dbusGeoClueManagerGetClient = dbusGeoClueManagerInterface + ".GetClient" + + dbusGeoClueClientInterface = dbusGeoClueInterface + ".Client" + dbusGeoClueClientDesktopId = dbusGeoClueClientInterface + ".DesktopId" + dbusGeoClueClientTimeThreshold = dbusGeoClueClientInterface + ".TimeThreshold" + dbusGeoClueClientTimeStart = dbusGeoClueClientInterface + ".Start" + dbusGeoClueClientTimeStop = dbusGeoClueClientInterface + ".Stop" + dbusGeoClueClientLocationUpdated = dbusGeoClueClientInterface + ".LocationUpdated" + + dbusGeoClueLocationInterface = dbusGeoClueInterface + ".Location" + dbusGeoClueLocationLatitude = dbusGeoClueLocationInterface + ".Latitude" + dbusGeoClueLocationLongitude = dbusGeoClueLocationInterface + ".Longitude" +) + +type GeoClueClient struct { + currLocation *Location + locationMutex sync.RWMutex + + dbusConn *dbus.Conn + clientPath dbus.ObjectPath + signals chan *dbus.Signal + + stopChan chan struct{} + sigWG sync.WaitGroup + + subscribers syncmap.Map[string, chan Location] +} + +func newGeoClueClient() (*GeoClueClient, error) { + dbusConn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, fmt.Errorf("system bus connection failed: %w", err) + } + + c := &GeoClueClient{ + dbusConn: dbusConn, + stopChan: make(chan struct{}), + signals: make(chan *dbus.Signal, 256), + + currLocation: &Location{ + Latitude: 0.0, + Longitude: 0.0, + }, + } + + if err := c.setupClient(); err != nil { + dbusConn.Close() + return nil, err + } + + if err := c.startSignalPump(); err != nil { + return nil, err + } + + return c, nil +} + +func (c *GeoClueClient) Close() { + close(c.stopChan) + + c.sigWG.Wait() + + if c.signals != nil { + c.dbusConn.RemoveSignal(c.signals) + close(c.signals) + } + + c.subscribers.Range(func(key string, ch chan Location) bool { + close(ch) + c.subscribers.Delete(key) + return true + }) + + if c.dbusConn != nil { + c.dbusConn.Close() + } +} + +func (c *GeoClueClient) Subscribe(id string) chan Location { + ch := make(chan Location, 64) + c.subscribers.Store(id, ch) + return ch +} + +func (c *GeoClueClient) Unsubscribe(id string) { + if ch, ok := c.subscribers.LoadAndDelete(id); ok { + close(ch) + } +} + +func (c *GeoClueClient) setupClient() error { + managerObj := c.dbusConn.Object(dbusGeoClueService, dbusGeoClueManagerPath) + + if err := managerObj.Call(dbusGeoClueManagerGetClient, 0).Store(&c.clientPath); err != nil { + return fmt.Errorf("failed to create GeoClue2 client: %w", err) + } + + clientObj := c.dbusConn.Object(dbusGeoClueService, c.clientPath) + if err := clientObj.SetProperty(dbusGeoClueClientDesktopId, "dms"); err != nil { + return fmt.Errorf("failed to set desktop ID: %w", err) + } + + if err := clientObj.SetProperty(dbusGeoClueClientTimeThreshold, uint(10)); err != nil { + return fmt.Errorf("failed to set time threshold: %w", err) + } + + return nil +} + +func (c *GeoClueClient) startSignalPump() error { + c.dbusConn.Signal(c.signals) + + if err := c.dbusConn.AddMatchSignal( + dbus.WithMatchObjectPath(c.clientPath), + dbus.WithMatchInterface(dbusGeoClueClientInterface), + dbus.WithMatchSender(dbusGeoClueClientLocationUpdated), + ); err != nil { + return err + } + + c.sigWG.Add(1) + go func() { + defer c.sigWG.Done() + + clientObj := c.dbusConn.Object(dbusGeoClueService, c.clientPath) + clientObj.Call(dbusGeoClueClientTimeStart, 0) + defer clientObj.Call(dbusGeoClueClientTimeStop, 0) + + for { + select { + case <-c.stopChan: + return + case sig, ok := <-c.signals: + if !ok { + return + } + if sig == nil { + continue + } + + c.handleSignal(sig) + } + } + }() + + return nil +} + +func (c *GeoClueClient) handleSignal(sig *dbus.Signal) { + switch sig.Name { + case dbusGeoClueClientLocationUpdated: + if len(sig.Body) != 2 { + return + } + + newLocationPath, ok := sig.Body[1].(dbus.ObjectPath) + if !ok { + return + } + + if err := c.handleLocationUpdated(newLocationPath); err != nil { + log.Warn("GeoClue: Failed to handle location update: %v", err) + return + } + } +} + +func (c *GeoClueClient) handleLocationUpdated(path dbus.ObjectPath) error { + locationObj := c.dbusConn.Object(dbusGeoClueService, path) + + lat, err := locationObj.GetProperty(dbusGeoClueLocationLatitude) + if err != nil { + return err + } + + long, err := locationObj.GetProperty(dbusGeoClueLocationLongitude) + if err != nil { + return err + } + + c.locationMutex.Lock() + c.currLocation.Latitude = dbusutil.AsOr(lat, 0.0) + c.currLocation.Longitude = dbusutil.AsOr(long, 0.0) + c.locationMutex.Unlock() + + c.notifySubscribers() + return nil +} + +func (c *GeoClueClient) notifySubscribers() { + currentLocation, err := c.GetLocation() + if err != nil { + return + } + + c.subscribers.Range(func(key string, ch chan Location) bool { + select { + case ch <- currentLocation: + default: + log.Warn("GeoClue: subscriber channel full, dropping update") + } + return true + }) +} + +func (c *GeoClueClient) SeedLocation(loc Location) { + c.locationMutex.Lock() + defer c.locationMutex.Unlock() + c.currLocation.Latitude = loc.Latitude + c.currLocation.Longitude = loc.Longitude +} + +func (c *GeoClueClient) GetLocation() (Location, error) { + c.locationMutex.RLock() + defer c.locationMutex.RUnlock() + if c.currLocation == nil { + return Location{ + Latitude: 0.0, + Longitude: 0.0, + }, nil + } + stateCopy := *c.currLocation + return stateCopy, nil +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_ip.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_ip.go new file mode 100644 index 0000000..0e68cae --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/client_ip.go @@ -0,0 +1,91 @@ +package geolocation + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +type IpClient struct { + currLocation *Location +} + +type ipLocationResult struct { + Location + City string +} + +type ipAPIResponse struct { + Status string `json:"status"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + City string `json:"city"` +} + +func newIpClient() *IpClient { + return &IpClient{ + currLocation: &Location{}, + } +} + +func (c *IpClient) Subscribe(id string) chan Location { + ch := make(chan Location, 1) + if location, err := c.GetLocation(); err == nil { + ch <- location + } + return ch +} + +func (c *IpClient) Unsubscribe(id string) {} + +func (c *IpClient) Close() {} + +func (c *IpClient) GetLocation() (Location, error) { + if c.currLocation.Latitude != 0 || c.currLocation.Longitude != 0 { + return *c.currLocation, nil + } + + 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 ipLocationResult{}, fmt.Errorf("failed to fetch IP location: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return ipLocationResult{}, fmt.Errorf("ip-api.com returned status %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return ipLocationResult{}, fmt.Errorf("failed to read response: %w", err) + } + + var data ipAPIResponse + if err := json.Unmarshal(body, &data); err != nil { + return ipLocationResult{}, fmt.Errorf("failed to parse response: %w", err) + } + + if data.Status == "fail" || (data.Lat == 0 && data.Lon == 0) { + return ipLocationResult{}, fmt.Errorf("ip-api.com returned no location data") + } + + return ipLocationResult{ + Location: Location{Latitude: data.Lat, Longitude: data.Lon}, + City: data.City, + }, nil +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/types.go new file mode 100644 index 0000000..bf71041 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/geolocation/types.go @@ -0,0 +1,15 @@ +package geolocation + +type Location struct { + Latitude float64 + Longitude float64 +} + +type Client interface { + GetLocation() (Location, error) + + Subscribe(id string) chan Location + Unsubscribe(id string) + + Close() +} |