Skip to content
Merged

Beta #36

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func main() {

port := os.Getenv("LOCAL_HOST_PORT_DEV")
if port == "" {
port = "8765"
port = "8766"
}

ip := os.Getenv("LOCAL_HOST_IP")
Expand Down
15 changes: 13 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ go 1.24.0
require github.com/gorilla/websocket v1.5.3

require (
github.com/godbus/dbus/v5 v5.2.0
github.com/godbus/dbus/v5 v5.2.2
github.com/joho/godotenv v1.5.1
github.com/mattn/go-sqlite3 v1.14.32
github.com/shirou/gopsutil/v3 v3.24.5
golang.org/x/crypto v0.44.0
)

require golang.org/x/sys v0.38.0 // indirect
require (
github.com/NVIDIA/go-nvml v0.13.0-1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.38.0 // indirect
)
39 changes: 39 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
github.com/NVIDIA/go-nvml v0.13.0-1 h1:OLX8Jq3dONuPOQPC7rndB6+iDmDakw0XTYgzMxObkEw=
github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4hrK3LmPt4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 changes: 2 additions & 18 deletions internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import (
spotifyAuth "Quazaar/internal/spotify/auth"
spotifyTrack "Quazaar/internal/spotify/track"
"Quazaar/internal/system"
systemBattery "Quazaar/internal/system/battery"
systemBluetooth "Quazaar/internal/system/bluetooth"
systemBrightness "Quazaar/internal/system/brightness"
systemSound "Quazaar/internal/system/sound"
systemVolume "Quazaar/internal/system/volume"
systemWakaTime "Quazaar/internal/wakatime"
"Quazaar/internal/websocket"
"bytes"
"embed"
Expand Down Expand Up @@ -78,16 +72,9 @@ func SetupRoutes(embedFS embed.FS) {
http.HandleFunc("/api/v0.1/player/pause", middleware.AuthenticationMiddleware(player.HandlePause))
http.HandleFunc("/api/v0.1/player/next", middleware.AuthenticationMiddleware(player.HandleNext))
http.HandleFunc("/api/v0.1/player/previous", middleware.AuthenticationMiddleware(player.HandlePrevious))
// API v0.1 - System Info
http.HandleFunc("/api/v0.1/system/wifi", middleware.AuthenticationMiddleware(system.HandleGetWiFiInfo))
http.HandleFunc("/api/v0.1/system/bluetooth", middleware.AuthenticationMiddleware(systemBluetooth.HandleGetBluetoothDevices))
http.HandleFunc("/api/v0.1/system/volume", middleware.AuthenticationMiddleware(systemVolume.HandleGetVolume))
http.HandleFunc("/api/v0.1/system/brightness", middleware.AuthenticationMiddleware(systemBrightness.HandleGetBrightness))
http.HandleFunc("/api/v0.1/system/sound/devices", middleware.AuthenticationMiddleware(systemSound.HandleListDevices))
http.HandleFunc("/api/v0.1/system/sound/device", middleware.AuthenticationMiddleware(systemSound.HandleSetDevice))

// API v0.1 - WakaTime
http.HandleFunc("/api/v0.1/system/wakatime", middleware.AuthenticationMiddleware(systemWakaTime.HandleGetWakaTimeStats))
// API v0.1 - System Routes
system.SetupRoutes()

// API v0.1 - Spotify
http.HandleFunc("/api/v0.1/spotify/track/currently-playing", middleware.AuthenticationMiddleware(spotifyTrack.CurrentlyPlayingTrackApi))
Expand All @@ -99,9 +86,6 @@ func SetupRoutes(embedFS embed.FS) {
// API v0.1 - File Share
http.HandleFunc("/api/v0.1/fileshare/create-accept-uri", fileShare.RequestTempFileShareAccept)
http.HandleFunc("/api/v0.1/fileshare/acceptfile", fileShare.HandleTempFileShareAccept)

// API v0.1 - System TODO: Move to systemBattery package
http.HandleFunc("/api/v0.1/system/battery", systemBattery.GetBatteryInfoApi)
}

func serveHome(w http.ResponseWriter, r *http.Request) {
Expand Down
43 changes: 35 additions & 8 deletions internal/player/provider_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ func next() (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.Next", 0).Store()
return err == nil, err
Expand All @@ -169,7 +173,11 @@ func previous() (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.Previous", 0).Store()
return err == nil, err
Expand All @@ -182,7 +190,11 @@ func playPause() (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.PlayPause", 0).Store()
return err == nil, err
Expand All @@ -195,7 +207,12 @@ func seekForward() (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}

obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.Seek", 0, int64(5000000)).Store()
return err == nil, err
Expand All @@ -208,7 +225,11 @@ func seekBackward() (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.Seek", 0, int64(-5000000)).Store()
return err == nil, err
Expand All @@ -220,7 +241,11 @@ func seekToPosition(position int64) (success bool, err error) {
fmt.Println("Error connecting to D-Bus:", err)
return false, err
}
player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
fmt.Println("No active player found:", err)
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")
err = obj.Call("org.mpris.MediaPlayer2.Player.SetPosition", 0, dbus.ObjectPath("/org/mpris/MediaPlayer2/TrackList/NoTrack"), position).Store()
return err == nil, err
Expand All @@ -232,7 +257,10 @@ func setVolume(volume int) (success bool, err error) {
return false, err
}

player := "org.mpris.MediaPlayer2.spotify"
player, err := findActivePlayer(dbusConn)
if err != nil {
return false, err
}
obj := dbusConn.Object(player, "/org/mpris/MediaPlayer2")

// FIX: Volume is a Property, so we use SetProperty, not Call
Expand All @@ -251,7 +279,6 @@ var LinuxDBusPlayer = models.PlayerFunctions{
SeekTo: seekToPosition,
SetVolume: setVolume,
GetCurrentPlayerMetadata: getMetadata,
// for now just focus on spotify buddy ..........
GetAllPlayers: func() ([]string, error) {
conn, err := GetDBusConnection()
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions internal/system/info/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package systemInfo

import (
"encoding/json"
"net/http"
)

func GetSystemInfoApi(w http.ResponseWriter, r *http.Request) {
info := getUser()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(info)

}
149 changes: 149 additions & 0 deletions internal/system/info/cpu/get_cpu_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package cpu

import (
"log"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/godbus/dbus/v5"
"github.com/shirou/gopsutil/v3/cpu"
)

type CPUInfo struct {
CPUUsage float64 `json:"cpu_usage"`
CPUTemp float64 `json:"cpu_temp"`
CPUWattage float64 `json:"cpu_wattage"`
}

var (
dbusConn *dbus.Conn
dbusOnce sync.Once
pathCache = make(map[string]string)
pathMutex sync.RWMutex
)

func InitDBus() {
dbusOnce.Do(func() {
var err error
dbusConn, err = dbus.SystemBus()
if err != nil {
log.Printf("Warning: Failed to connect to DBus: %v", err)
}
})
}

func GetCPUInfo() CPUInfo {
InitDBus()
return CPUInfo{
CPUUsage: getCPUUsage(),
CPUTemp: getCPUTemp(),
CPUWattage: getCPUWattage(),
}

}

func getCPUUsage() float64 {
cpuPercent, err := cpu.Percent(0, false)
if err == nil && len(cpuPercent) > 0 {
return math.Round(cpuPercent[0]*100) / 100
}
return -1
}

func getCPUTemp() float64 {
if t, err := getCPUTemperature(); err == nil {
return t
}
return -1
}

func getCPUWattage() float64 {
if w, err := getCPUWattageValue(); err == nil {
return w
}
return 0
}

func getCPUTemperature() (float64, error) {
if path, ok := getCachedPath("cpu_temp"); ok {
if val, err := readFileAsFloat(path); err == nil {
return math.Round((val/1000.0)*100) / 100, nil
}
}

matches, _ := filepath.Glob("/sys/class/hwmon/hwmon*")
for _, hwmonPath := range matches {
nameBytes, _ := os.ReadFile(filepath.Join(hwmonPath, "name"))
name := strings.TrimSpace(string(nameBytes))

if strings.Contains(name, "k10temp") || strings.Contains(name, "coretemp") || strings.Contains(name, "zenpower") {
target := filepath.Join(hwmonPath, "temp1_input")
if val, err := readFileAsFloat(target); err == nil && val > 0 {
setCachedPath("cpu_temp", target)
return math.Round((val/1000.0)*100) / 100, nil
}
}
}
return 0, os.ErrNotExist
}

func getCPUWattageValue() (float64, error) {
if dbusConn != nil {
obj := dbusConn.Object("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice")
var energyRate dbus.Variant
err := obj.Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.UPower.Device", "EnergyRate").Store(&energyRate)
if err == nil {
if watts, ok := energyRate.Value().(float64); ok && watts > 0 {
return watts, nil
}
}
}

if path, ok := getCachedPath("cpu_power"); ok {
if val, err := readFileAsFloat(path); err == nil {
return val / 1_000_000.0, nil
}
}

matches, _ := filepath.Glob("/sys/class/hwmon/hwmon*")
for _, hwmonPath := range matches {
nameBytes, _ := os.ReadFile(filepath.Join(hwmonPath, "name"))
name := strings.TrimSpace(string(nameBytes))
if strings.Contains(name, "amdgpu") || strings.Contains(name, "nvidia") {
continue
}
target := filepath.Join(hwmonPath, "power1_average")
if val, err := readFileAsFloat(target); err == nil && val > 0 {
setCachedPath("cpu_power", target)
return val / 1_000_000.0, nil
}
}
return 0, os.ErrNotExist
}

// helper functions for caching file paths

func readFileAsFloat(path string) (float64, error) {
content, err := os.ReadFile(path)
if err != nil {
return 0, err
}
return strconv.ParseFloat(strings.TrimSpace(string(content)), 64)
}

func getCachedPath(key string) (string, bool) {
pathMutex.RLock()
defer pathMutex.RUnlock()
path, exists := pathCache[key]
return path, exists
}

func setCachedPath(key, path string) {
pathMutex.Lock()
defer pathMutex.Unlock()
pathCache[key] = path
}
Loading