mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Compare commits
10 Commits
816819bf9f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80025804ab | ||
|
|
028d3b4e61 | ||
|
|
9cce5ccfe6 | ||
|
|
a260b8060e | ||
|
|
f945307232 | ||
|
|
8f44d52cb2 | ||
|
|
3413cb7b89 | ||
|
|
4e3b24ffbb | ||
|
|
03cfa55e0b | ||
|
|
a887e60f40 |
@@ -502,17 +502,17 @@ func (p *MangoWCParser) handleSource(line, baseDir string, keybinds *[]MangoWCKe
|
|||||||
p.dmsProcessed = true
|
p.dmsProcessed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := sourcePath
|
expanded, err := utils.ExpandPath(sourcePath)
|
||||||
if !filepath.IsAbs(sourcePath) {
|
|
||||||
fullPath = filepath.Join(baseDir, sourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
expanded, err := utils.ExpandPath(fullPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
includedBinds, err := p.parseFileWithSource(expanded)
|
fullPath := expanded
|
||||||
|
if !filepath.IsAbs(expanded) {
|
||||||
|
fullPath = filepath.Join(baseDir, expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
includedBinds, err := p.parseFileWithSource(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -521,33 +521,10 @@ func (p *MangoWCParser) handleSource(line, baseDir string, keybinds *[]MangoWCKe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MangoWCParser) parseDMSBindsDirectly(dmsBindsPath string) []MangoWCKeyBinding {
|
func (p *MangoWCParser) parseDMSBindsDirectly(dmsBindsPath string) []MangoWCKeyBinding {
|
||||||
data, err := os.ReadFile(dmsBindsPath)
|
keybinds, err := p.parseFileWithSource(dmsBindsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prevSource := p.currentSource
|
|
||||||
p.currentSource = dmsBindsPath
|
|
||||||
|
|
||||||
var keybinds []MangoWCKeyBinding
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
|
|
||||||
for lineNum, line := range lines {
|
|
||||||
trimmed := strings.TrimSpace(line)
|
|
||||||
if !strings.HasPrefix(trimmed, "bind") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
kb := p.getKeybindAtLineContent(line, lineNum)
|
|
||||||
if kb == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kb.Source = dmsBindsPath
|
|
||||||
p.addBind(kb)
|
|
||||||
keybinds = append(keybinds, *kb)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.currentSource = prevSource
|
|
||||||
p.dmsProcessed = true
|
p.dmsProcessed = true
|
||||||
return keybinds
|
return keybinds
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
||||||
serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
|
serverPlugins "github.com/AvengeMedia/DankMaterialShell/core/internal/server/plugins"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
|
||||||
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
|
serverThemes "github.com/AvengeMedia/DankMaterialShell/core/internal/server/themes"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
||||||
@@ -44,6 +45,15 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(req.Method, "theme.auto.") {
|
||||||
|
if themeModeManager == nil {
|
||||||
|
models.RespondError(conn, req.ID, "theme mode manager not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thememode.HandleRequest(conn, req, themeModeManager)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "loginctl.") {
|
if strings.HasPrefix(req.Method, "loginctl.") {
|
||||||
if loginctlManager == nil {
|
if loginctlManager == nil {
|
||||||
models.RespondError(conn, req.ID, "loginctl manager not initialized")
|
models.RespondError(conn, req.ID, "loginctl manager not initialized")
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
|
||||||
@@ -68,6 +69,7 @@ var evdevManager *evdev.Manager
|
|||||||
var clipboardManager *clipboard.Manager
|
var clipboardManager *clipboard.Manager
|
||||||
var dbusManager *serverDbus.Manager
|
var dbusManager *serverDbus.Manager
|
||||||
var wlContext *wlcontext.SharedContext
|
var wlContext *wlcontext.SharedContext
|
||||||
|
var themeModeManager *thememode.Manager
|
||||||
|
|
||||||
const dbusClientID = "dms-dbus-client"
|
const dbusClientID = "dms-dbus-client"
|
||||||
|
|
||||||
@@ -380,6 +382,14 @@ func InitializeDbusManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitializeThemeModeManager() error {
|
||||||
|
manager := thememode.NewManager()
|
||||||
|
themeModeManager = manager
|
||||||
|
|
||||||
|
log.Info("Theme mode automation manager initialized")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -457,6 +467,10 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "clipboard")
|
caps = append(caps, "clipboard")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if themeModeManager != nil {
|
||||||
|
caps = append(caps, "theme.auto")
|
||||||
|
}
|
||||||
|
|
||||||
if dbusManager != nil {
|
if dbusManager != nil {
|
||||||
caps = append(caps, "dbus")
|
caps = append(caps, "dbus")
|
||||||
}
|
}
|
||||||
@@ -519,6 +533,10 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "clipboard")
|
caps = append(caps, "clipboard")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if themeModeManager != nil {
|
||||||
|
caps = append(caps, "theme.auto")
|
||||||
|
}
|
||||||
|
|
||||||
if dbusManager != nil {
|
if dbusManager != nil {
|
||||||
caps = append(caps, "dbus")
|
caps = append(caps, "dbus")
|
||||||
}
|
}
|
||||||
@@ -791,6 +809,38 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldSubscribe("theme.auto") && themeModeManager != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
themeAutoChan := themeModeManager.Subscribe(clientID + "-theme-auto")
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer themeModeManager.Unsubscribe(clientID + "-theme-auto")
|
||||||
|
|
||||||
|
initialState := themeModeManager.GetState()
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "theme.auto", Data: initialState}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case state, ok := <-themeAutoChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case eventChan <- ServiceEvent{Service: "theme.auto", Data: state}:
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if shouldSubscribe("bluetooth") && bluezManager != nil {
|
if shouldSubscribe("bluetooth") && bluezManager != nil {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
bluezChan := bluezManager.Subscribe(clientID + "-bluetooth")
|
bluezChan := bluezManager.Subscribe(clientID + "-bluetooth")
|
||||||
@@ -1251,6 +1301,9 @@ func cleanupManagers() {
|
|||||||
if dbusManager != nil {
|
if dbusManager != nil {
|
||||||
dbusManager.Close()
|
dbusManager.Close()
|
||||||
}
|
}
|
||||||
|
if themeModeManager != nil {
|
||||||
|
themeModeManager.Close()
|
||||||
|
}
|
||||||
if wlContext != nil {
|
if wlContext != nil {
|
||||||
wlContext.Close()
|
wlContext.Close()
|
||||||
}
|
}
|
||||||
@@ -1346,6 +1399,15 @@ func Start(printDocs bool) error {
|
|||||||
log.Info(" wayland.gamma.setGamma - Set gamma value (params: gamma)")
|
log.Info(" wayland.gamma.setGamma - Set gamma value (params: gamma)")
|
||||||
log.Info(" wayland.gamma.setEnabled - Enable/disable gamma control (params: enabled)")
|
log.Info(" wayland.gamma.setEnabled - Enable/disable gamma control (params: enabled)")
|
||||||
log.Info(" wayland.gamma.subscribe - Subscribe to gamma state changes (streaming)")
|
log.Info(" wayland.gamma.subscribe - Subscribe to gamma state changes (streaming)")
|
||||||
|
log.Info("Theme automation:")
|
||||||
|
log.Info(" theme.auto.getState - Get current theme automation state")
|
||||||
|
log.Info(" theme.auto.setEnabled - Enable/disable theme automation (params: enabled)")
|
||||||
|
log.Info(" theme.auto.setMode - Set automation mode (params: mode [time|location])")
|
||||||
|
log.Info(" theme.auto.setSchedule - Set time schedule (params: startHour, startMinute, endHour, endMinute)")
|
||||||
|
log.Info(" theme.auto.setLocation - Set location (params: latitude, longitude)")
|
||||||
|
log.Info(" theme.auto.setUseIPLocation - Use IP location (params: use)")
|
||||||
|
log.Info(" theme.auto.trigger - Trigger immediate re-evaluation")
|
||||||
|
log.Info(" theme.auto.subscribe - Subscribe to theme automation state changes (streaming)")
|
||||||
log.Info("Bluetooth:")
|
log.Info("Bluetooth:")
|
||||||
log.Info(" bluetooth.getState - Get current bluetooth state")
|
log.Info(" bluetooth.getState - Get current bluetooth state")
|
||||||
log.Info(" bluetooth.startDiscovery - Start device discovery")
|
log.Info(" bluetooth.startDiscovery - Start device discovery")
|
||||||
@@ -1503,6 +1565,12 @@ func Start(printDocs bool) error {
|
|||||||
log.Debugf("WlrOutput manager unavailable: %v", err)
|
log.Debugf("WlrOutput manager unavailable: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := InitializeThemeModeManager(); err != nil {
|
||||||
|
log.Warnf("Theme mode manager unavailable: %v", err)
|
||||||
|
} else {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
}
|
||||||
|
|
||||||
fatalErrChan := make(chan error, 1)
|
fatalErrChan := make(chan error, 1)
|
||||||
if wlrOutputManager != nil {
|
if wlrOutputManager != nil {
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
154
core/internal/server/thememode/handlers.go
Normal file
154
core/internal/server/thememode/handlers.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package thememode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRequest(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
if manager == nil {
|
||||||
|
models.RespondError(conn, req.ID, "theme mode manager not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Method {
|
||||||
|
case "theme.auto.getState":
|
||||||
|
handleGetState(conn, req, manager)
|
||||||
|
case "theme.auto.setEnabled":
|
||||||
|
handleSetEnabled(conn, req, manager)
|
||||||
|
case "theme.auto.setMode":
|
||||||
|
handleSetMode(conn, req, manager)
|
||||||
|
case "theme.auto.setSchedule":
|
||||||
|
handleSetSchedule(conn, req, manager)
|
||||||
|
case "theme.auto.setLocation":
|
||||||
|
handleSetLocation(conn, req, manager)
|
||||||
|
case "theme.auto.setUseIPLocation":
|
||||||
|
handleSetUseIPLocation(conn, req, manager)
|
||||||
|
case "theme.auto.trigger":
|
||||||
|
handleTrigger(conn, req, manager)
|
||||||
|
case "theme.auto.subscribe":
|
||||||
|
handleSubscribe(conn, req, manager)
|
||||||
|
default:
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetState(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
enabled, err := params.Bool(req.Params, "enabled")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetEnabled(enabled)
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto enabled set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetMode(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
mode, err := params.String(req.Params, "mode")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != "time" && mode != "location" {
|
||||||
|
models.RespondError(conn, req.ID, "invalid mode")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetMode(mode)
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto mode set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetSchedule(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
startHour, err := params.Int(req.Params, "startHour")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startMinute, err := params.Int(req.Params, "startMinute")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endHour, err := params.Int(req.Params, "endHour")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endMinute, err := params.Int(req.Params, "endMinute")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.ValidateSchedule(startHour, startMinute, endHour, endMinute); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetSchedule(startHour, startMinute, endHour, endMinute)
|
||||||
|
models.Respond(conn, req.ID, manager.GetState())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
lat, err := params.Float(req.Params, "latitude")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lon, err := params.Float(req.Params, "longitude")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetLocation(lat, lon)
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto location set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
use, err := params.Bool(req.Params, "use")
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.SetUseIPLocation(use)
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto IP location set"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTrigger(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
manager.TriggerUpdate()
|
||||||
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto update triggered"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) {
|
||||||
|
clientID := fmt.Sprintf("client-%p", conn)
|
||||||
|
stateChan := manager.Subscribe(clientID)
|
||||||
|
defer manager.Unsubscribe(clientID)
|
||||||
|
|
||||||
|
initialState := manager.GetState()
|
||||||
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
|
ID: req.ID,
|
||||||
|
Result: &initialState,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for state := range stateChan {
|
||||||
|
if err := json.NewEncoder(conn).Encode(models.Response[State]{
|
||||||
|
Result: &state,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
432
core/internal/server/thememode/manager.go
Normal file
432
core/internal/server/thememode/manager.go
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
package thememode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultStartHour = 18
|
||||||
|
defaultStartMinute = 0
|
||||||
|
defaultEndHour = 6
|
||||||
|
defaultEndMinute = 0
|
||||||
|
defaultElevationTwilight = -6.0
|
||||||
|
defaultElevationDaylight = 3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
config Config
|
||||||
|
configMutex sync.RWMutex
|
||||||
|
|
||||||
|
state *State
|
||||||
|
stateMutex sync.RWMutex
|
||||||
|
|
||||||
|
subscribers syncmap.Map[string, chan State]
|
||||||
|
|
||||||
|
locationMutex sync.RWMutex
|
||||||
|
cachedIPLat *float64
|
||||||
|
cachedIPLon *float64
|
||||||
|
|
||||||
|
stopChan chan struct{}
|
||||||
|
updateTrigger chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() *Manager {
|
||||||
|
m := &Manager{
|
||||||
|
config: Config{
|
||||||
|
Enabled: false,
|
||||||
|
Mode: "time",
|
||||||
|
StartHour: defaultStartHour,
|
||||||
|
StartMinute: defaultStartMinute,
|
||||||
|
EndHour: defaultEndHour,
|
||||||
|
EndMinute: defaultEndMinute,
|
||||||
|
ElevationTwilight: defaultElevationTwilight,
|
||||||
|
ElevationDaylight: defaultElevationDaylight,
|
||||||
|
},
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
updateTrigger: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateState(time.Now())
|
||||||
|
|
||||||
|
m.wg.Add(1)
|
||||||
|
go m.schedulerLoop()
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetState() State {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
defer m.stateMutex.RUnlock()
|
||||||
|
if m.state == nil {
|
||||||
|
return State{Config: m.getConfig()}
|
||||||
|
}
|
||||||
|
stateCopy := *m.state
|
||||||
|
return stateCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
|
ch := make(chan State, 64)
|
||||||
|
m.subscribers.Store(id, ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
|
close(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetEnabled(enabled bool) {
|
||||||
|
m.configMutex.Lock()
|
||||||
|
if m.config.Enabled == enabled {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.config.Enabled = enabled
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetMode(mode string) {
|
||||||
|
m.configMutex.Lock()
|
||||||
|
if m.config.Mode == mode {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.config.Mode = mode
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetSchedule(startHour, startMinute, endHour, endMinute int) {
|
||||||
|
m.configMutex.Lock()
|
||||||
|
changed := m.config.StartHour != startHour ||
|
||||||
|
m.config.StartMinute != startMinute ||
|
||||||
|
m.config.EndHour != endHour ||
|
||||||
|
m.config.EndMinute != endMinute
|
||||||
|
if !changed {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.config.StartHour = startHour
|
||||||
|
m.config.StartMinute = startMinute
|
||||||
|
m.config.EndHour = endHour
|
||||||
|
m.config.EndMinute = endMinute
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetLocation(lat, lon float64) {
|
||||||
|
m.configMutex.Lock()
|
||||||
|
if m.config.Latitude != nil && m.config.Longitude != nil &&
|
||||||
|
*m.config.Latitude == lat && *m.config.Longitude == lon && !m.config.UseIPLocation {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.config.Latitude = &lat
|
||||||
|
m.config.Longitude = &lon
|
||||||
|
m.config.UseIPLocation = false
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
|
||||||
|
m.locationMutex.Lock()
|
||||||
|
m.cachedIPLat = nil
|
||||||
|
m.cachedIPLon = nil
|
||||||
|
m.locationMutex.Unlock()
|
||||||
|
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetUseIPLocation(use bool) {
|
||||||
|
m.configMutex.Lock()
|
||||||
|
if m.config.UseIPLocation == use {
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.config.UseIPLocation = use
|
||||||
|
if use {
|
||||||
|
m.config.Latitude = nil
|
||||||
|
m.config.Longitude = nil
|
||||||
|
}
|
||||||
|
m.configMutex.Unlock()
|
||||||
|
|
||||||
|
if use {
|
||||||
|
m.locationMutex.Lock()
|
||||||
|
m.cachedIPLat = nil
|
||||||
|
m.cachedIPLon = nil
|
||||||
|
m.locationMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) TriggerUpdate() {
|
||||||
|
select {
|
||||||
|
case m.updateTrigger <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() {
|
||||||
|
select {
|
||||||
|
case <-m.stopChan:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(m.stopChan)
|
||||||
|
}
|
||||||
|
m.wg.Wait()
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
close(ch)
|
||||||
|
m.subscribers.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) schedulerLoop() {
|
||||||
|
defer m.wg.Done()
|
||||||
|
|
||||||
|
var timer *time.Timer
|
||||||
|
for {
|
||||||
|
config := m.getConfig()
|
||||||
|
now := time.Now()
|
||||||
|
var isLight bool
|
||||||
|
var next time.Time
|
||||||
|
if config.Enabled {
|
||||||
|
isLight, next = m.computeSchedule(now, config)
|
||||||
|
} else {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
if m.state != nil {
|
||||||
|
isLight = m.state.IsLight
|
||||||
|
}
|
||||||
|
m.stateMutex.RUnlock()
|
||||||
|
next = now.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.updateStateWithValues(config, isLight, next)
|
||||||
|
|
||||||
|
waitDur := time.Until(next)
|
||||||
|
if !config.Enabled {
|
||||||
|
waitDur = 24 * time.Hour
|
||||||
|
}
|
||||||
|
if waitDur < time.Second {
|
||||||
|
waitDur = time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
if timer != nil {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
timer = time.NewTimer(waitDur)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-m.stopChan:
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
case <-m.updateTrigger:
|
||||||
|
timer.Stop()
|
||||||
|
continue
|
||||||
|
case <-timer.C:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) updateState(now time.Time) {
|
||||||
|
config := m.getConfig()
|
||||||
|
var isLight bool
|
||||||
|
var next time.Time
|
||||||
|
if config.Enabled {
|
||||||
|
isLight, next = m.computeSchedule(now, config)
|
||||||
|
} else {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
if m.state != nil {
|
||||||
|
isLight = m.state.IsLight
|
||||||
|
}
|
||||||
|
m.stateMutex.RUnlock()
|
||||||
|
next = now.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
m.updateStateWithValues(config, isLight, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) updateStateWithValues(config Config, isLight bool, next time.Time) {
|
||||||
|
newState := State{
|
||||||
|
Config: config,
|
||||||
|
IsLight: isLight,
|
||||||
|
NextTransition: next,
|
||||||
|
}
|
||||||
|
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
if m.state != nil && statesEqual(m.state, &newState) {
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.state = &newState
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
|
m.notifySubscribers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) notifySubscribers() {
|
||||||
|
state := m.GetState()
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
select {
|
||||||
|
case ch <- state:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) getConfig() Config {
|
||||||
|
m.configMutex.RLock()
|
||||||
|
defer m.configMutex.RUnlock()
|
||||||
|
return m.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) getLocation(config Config) (*float64, *float64) {
|
||||||
|
if config.Latitude != nil && config.Longitude != nil {
|
||||||
|
return config.Latitude, config.Longitude
|
||||||
|
}
|
||||||
|
if !config.UseIPLocation {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.locationMutex.RLock()
|
||||||
|
if m.cachedIPLat != nil && m.cachedIPLon != nil {
|
||||||
|
lat, lon := m.cachedIPLat, m.cachedIPLon
|
||||||
|
m.locationMutex.RUnlock()
|
||||||
|
return lat, lon
|
||||||
|
}
|
||||||
|
m.locationMutex.RUnlock()
|
||||||
|
|
||||||
|
lat, lon, err := wayland.FetchIPLocation()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.locationMutex.Lock()
|
||||||
|
m.cachedIPLat = lat
|
||||||
|
m.cachedIPLon = lon
|
||||||
|
m.locationMutex.Unlock()
|
||||||
|
|
||||||
|
return lat, lon
|
||||||
|
}
|
||||||
|
|
||||||
|
func statesEqual(a, b *State) bool {
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
if a.IsLight != b.IsLight || !a.NextTransition.Equal(b.NextTransition) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.Config == b.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) computeSchedule(now time.Time, config Config) (bool, time.Time) {
|
||||||
|
if config.Mode == "location" {
|
||||||
|
return m.computeLocationSchedule(now, config)
|
||||||
|
}
|
||||||
|
return computeTimeSchedule(now, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeTimeSchedule(now time.Time, config Config) (bool, time.Time) {
|
||||||
|
startMinutes := config.StartHour*60 + config.StartMinute
|
||||||
|
endMinutes := config.EndHour*60 + config.EndMinute
|
||||||
|
currentMinutes := now.Hour()*60 + now.Minute()
|
||||||
|
|
||||||
|
startTime := time.Date(now.Year(), now.Month(), now.Day(), config.StartHour, config.StartMinute, 0, 0, now.Location())
|
||||||
|
endTime := time.Date(now.Year(), now.Month(), now.Day(), config.EndHour, config.EndMinute, 0, 0, now.Location())
|
||||||
|
|
||||||
|
if startMinutes == endMinutes {
|
||||||
|
next := startTime
|
||||||
|
if !next.After(now) {
|
||||||
|
next = next.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
return true, next
|
||||||
|
}
|
||||||
|
|
||||||
|
if startMinutes < endMinutes {
|
||||||
|
if currentMinutes < startMinutes {
|
||||||
|
return true, startTime
|
||||||
|
}
|
||||||
|
if currentMinutes >= endMinutes {
|
||||||
|
return true, startTime.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
return false, endTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentMinutes >= startMinutes {
|
||||||
|
return false, endTime.Add(24 * time.Hour)
|
||||||
|
}
|
||||||
|
if currentMinutes < endMinutes {
|
||||||
|
return false, endTime
|
||||||
|
}
|
||||||
|
return true, startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) computeLocationSchedule(now time.Time, config Config) (bool, time.Time) {
|
||||||
|
lat, lon := m.getLocation(config)
|
||||||
|
if lat == nil || lon == nil {
|
||||||
|
currentIsLight := false
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
if m.state != nil {
|
||||||
|
currentIsLight = m.state.IsLight
|
||||||
|
}
|
||||||
|
m.stateMutex.RUnlock()
|
||||||
|
return currentIsLight, now.Add(10 * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
times, cond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
|
||||||
|
if cond != wayland.SunNormal {
|
||||||
|
if cond == wayland.SunMidnightSun {
|
||||||
|
return true, startOfNextDay(now)
|
||||||
|
}
|
||||||
|
return false, startOfNextDay(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Before(times.Sunrise) {
|
||||||
|
return false, times.Sunrise
|
||||||
|
}
|
||||||
|
if now.Before(times.Sunset) {
|
||||||
|
return true, times.Sunset
|
||||||
|
}
|
||||||
|
|
||||||
|
nextDay := startOfNextDay(now)
|
||||||
|
nextTimes, nextCond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, nextDay, config.ElevationTwilight, config.ElevationDaylight)
|
||||||
|
if nextCond != wayland.SunNormal {
|
||||||
|
if nextCond == wayland.SunMidnightSun {
|
||||||
|
return true, startOfNextDay(nextDay)
|
||||||
|
}
|
||||||
|
return false, startOfNextDay(nextDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nextTimes.Sunrise
|
||||||
|
}
|
||||||
|
|
||||||
|
func startOfNextDay(t time.Time) time.Time {
|
||||||
|
next := t.Add(24 * time.Hour)
|
||||||
|
return time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHourMinute(hour, minute int) bool {
|
||||||
|
if hour < 0 || hour > 23 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if minute < 0 || minute > 59 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ValidateSchedule(startHour, startMinute, endHour, endMinute int) error {
|
||||||
|
if !validateHourMinute(startHour, startMinute) || !validateHourMinute(endHour, endMinute) {
|
||||||
|
return errInvalidTime
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidTime = errors.New("invalid schedule time")
|
||||||
23
core/internal/server/thememode/types.go
Normal file
23
core/internal/server/thememode/types.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package thememode
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
StartHour int `json:"startHour"`
|
||||||
|
StartMinute int `json:"startMinute"`
|
||||||
|
EndHour int `json:"endHour"`
|
||||||
|
EndMinute int `json:"endMinute"`
|
||||||
|
Latitude *float64 `json:"latitude,omitempty"`
|
||||||
|
Longitude *float64 `json:"longitude,omitempty"`
|
||||||
|
UseIPLocation bool `json:"useIPLocation"`
|
||||||
|
ElevationTwilight float64 `json:"elevationTwilight"`
|
||||||
|
ElevationDaylight float64 `json:"elevationDaylight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Config Config `json:"config"`
|
||||||
|
IsLight bool `json:"isLight"`
|
||||||
|
NextTransition time.Time `json:"nextTransition"`
|
||||||
|
}
|
||||||
@@ -626,6 +626,7 @@ func (m *Manager) schedulerLoop() {
|
|||||||
m.schedule.calcDay = time.Time{}
|
m.schedule.calcDay = time.Time{}
|
||||||
m.scheduleMutex.Unlock()
|
m.scheduleMutex.Unlock()
|
||||||
m.recalcSchedule(time.Now())
|
m.recalcSchedule(time.Now())
|
||||||
|
m.updateStateFromSchedule()
|
||||||
m.configMutex.RLock()
|
m.configMutex.RLock()
|
||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
m.configMutex.RUnlock()
|
m.configMutex.RUnlock()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import "settings/SessionStore.js" as Store
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int sessionConfigVersion: 2
|
readonly property int sessionConfigVersion: 3
|
||||||
|
|
||||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||||
property bool _parseError: false
|
property bool _parseError: false
|
||||||
@@ -82,6 +82,15 @@ Singleton {
|
|||||||
property bool nightModeUseIPLocation: false
|
property bool nightModeUseIPLocation: false
|
||||||
property string nightModeLocationProvider: ""
|
property string nightModeLocationProvider: ""
|
||||||
|
|
||||||
|
property bool themeModeAutoEnabled: false
|
||||||
|
property string themeModeAutoMode: "time"
|
||||||
|
property int themeModeStartHour: 18
|
||||||
|
property int themeModeStartMinute: 0
|
||||||
|
property int themeModeEndHour: 6
|
||||||
|
property int themeModeEndMinute: 0
|
||||||
|
property bool themeModeShareGammaSettings: true
|
||||||
|
property string themeModeNextTransition: ""
|
||||||
|
|
||||||
property var pinnedApps: []
|
property var pinnedApps: []
|
||||||
property var barPinnedApps: []
|
property var barPinnedApps: []
|
||||||
property int dockLauncherPosition: 0
|
property int dockLauncherPosition: 0
|
||||||
@@ -109,6 +118,8 @@ Singleton {
|
|||||||
property var appOverrides: ({})
|
property var appOverrides: ({})
|
||||||
property bool searchAppActions: true
|
property bool searchAppActions: true
|
||||||
|
|
||||||
|
property string vpnLastConnected: ""
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -172,7 +183,7 @@ Singleton {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_parseError = true;
|
_parseError = true;
|
||||||
const msg = e.message;
|
const msg = e.message;
|
||||||
console.error("SessionData: Failed to parse session.json - file will not be overwritten. Error:", msg);
|
console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
|
||||||
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
|
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,14 +197,10 @@ Singleton {
|
|||||||
_isReadOnly = !writable;
|
_isReadOnly = !writable;
|
||||||
if (_isReadOnly) {
|
if (_isReadOnly) {
|
||||||
_hasUnsavedChanges = _checkForUnsavedChanges();
|
_hasUnsavedChanges = _checkForUnsavedChanges();
|
||||||
if (!wasReadOnly)
|
|
||||||
console.info("SessionData: session.json is now read-only");
|
|
||||||
} else {
|
} else {
|
||||||
_loadedSessionSnapshot = getCurrentSessionJson();
|
_loadedSessionSnapshot = getCurrentSessionJson();
|
||||||
_hasUnsavedChanges = false;
|
_hasUnsavedChanges = false;
|
||||||
if (wasReadOnly)
|
if (wasReadOnly && _pendingMigration)
|
||||||
console.info("SessionData: session.json is now writable");
|
|
||||||
if (_pendingMigration)
|
|
||||||
settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
|
settingsFile.setText(JSON.stringify(_pendingMigration, null, 2));
|
||||||
}
|
}
|
||||||
_pendingMigration = null;
|
_pendingMigration = null;
|
||||||
@@ -255,7 +262,7 @@ Singleton {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_parseError = true;
|
_parseError = true;
|
||||||
const msg = e.message;
|
const msg = e.message;
|
||||||
console.error("SessionData: Failed to parse session.json - file will not be overwritten. Error:", msg);
|
console.error("SessionData: Failed to parse session.json - file will not be overwritten.");
|
||||||
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
|
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +280,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function migrateFromUndefinedToV1(settings) {
|
function migrateFromUndefinedToV1(settings) {
|
||||||
console.info("SessionData: Migrating configuration from undefined to version 1");
|
|
||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
if (settings.acMonitorTimeout !== undefined) {
|
if (settings.acMonitorTimeout !== undefined) {
|
||||||
SettingsData.set("acMonitorTimeout", settings.acMonitorTimeout);
|
SettingsData.set("acMonitorTimeout", settings.acMonitorTimeout);
|
||||||
@@ -448,7 +454,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
console.warn("SessionData: Screen not found:", screenName);
|
console.warn("SessionData: Screen not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +551,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
console.warn("SessionData: Screen not found:", screenName);
|
console.warn("SessionData: Screen not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +589,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
console.warn("SessionData: Screen not found:", screenName);
|
console.warn("SessionData: Screen not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +627,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
console.warn("SessionData: Screen not found:", screenName);
|
console.warn("SessionData: Screen not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +665,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!screen) {
|
if (!screen) {
|
||||||
console.warn("SessionData: Screen not found:", screenName);
|
console.warn("SessionData: Screen not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +708,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setNightModeAutoEnabled(enabled) {
|
function setNightModeAutoEnabled(enabled) {
|
||||||
console.log("SessionData: Setting nightModeAutoEnabled to", enabled);
|
|
||||||
nightModeAutoEnabled = enabled;
|
nightModeAutoEnabled = enabled;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
@@ -738,13 +743,11 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setLatitude(lat) {
|
function setLatitude(lat) {
|
||||||
console.log("SessionData: Setting latitude to", lat);
|
|
||||||
latitude = lat;
|
latitude = lat;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLongitude(lng) {
|
function setLongitude(lng) {
|
||||||
console.log("SessionData: Setting longitude to", lng);
|
|
||||||
longitude = lng;
|
longitude = lng;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
@@ -754,6 +757,41 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setThemeModeAutoEnabled(enabled) {
|
||||||
|
themeModeAutoEnabled = enabled;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeAutoMode(mode) {
|
||||||
|
themeModeAutoMode = mode;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeStartHour(hour) {
|
||||||
|
themeModeStartHour = hour;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeStartMinute(minute) {
|
||||||
|
themeModeStartMinute = minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeEndHour(hour) {
|
||||||
|
themeModeEndHour = hour;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeEndMinute(minute) {
|
||||||
|
themeModeEndMinute = minute;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setThemeModeShareGammaSettings(share) {
|
||||||
|
themeModeShareGammaSettings = share;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function setPinnedApps(apps) {
|
function setPinnedApps(apps) {
|
||||||
pinnedApps = apps;
|
pinnedApps = apps;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1003,6 +1041,11 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setVpnLastConnected(uuid) {
|
||||||
|
vpnLastConnected = uuid || "";
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function syncWallpaperForCurrentMode() {
|
function syncWallpaperForCurrentMode() {
|
||||||
if (!perModeWallpaper)
|
if (!perModeWallpaper)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -292,13 +292,13 @@ Singleton {
|
|||||||
|
|
||||||
property string _legacyWeatherLocation: "New York, NY"
|
property string _legacyWeatherLocation: "New York, NY"
|
||||||
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
|
property string _legacyWeatherCoordinates: "40.7128,-74.0060"
|
||||||
|
property string _legacyVpnLastConnected: ""
|
||||||
readonly property string weatherLocation: SessionData.weatherLocation
|
readonly property string weatherLocation: SessionData.weatherLocation
|
||||||
readonly property string weatherCoordinates: SessionData.weatherCoordinates
|
readonly property string weatherCoordinates: SessionData.weatherCoordinates
|
||||||
property bool useAutoLocation: false
|
property bool useAutoLocation: false
|
||||||
property bool weatherEnabled: true
|
property bool weatherEnabled: true
|
||||||
|
|
||||||
property string networkPreference: "auto"
|
property string networkPreference: "auto"
|
||||||
property string vpnLastConnected: ""
|
|
||||||
|
|
||||||
property string iconTheme: "System Default"
|
property string iconTheme: "System Default"
|
||||||
property var availableIconThemes: ["System Default"]
|
property var availableIconThemes: ["System Default"]
|
||||||
@@ -1078,6 +1078,11 @@ Singleton {
|
|||||||
_legacyWeatherLocation = obj.weatherLocation;
|
_legacyWeatherLocation = obj.weatherLocation;
|
||||||
if (obj?.weatherCoordinates !== undefined)
|
if (obj?.weatherCoordinates !== undefined)
|
||||||
_legacyWeatherCoordinates = obj.weatherCoordinates;
|
_legacyWeatherCoordinates = obj.weatherCoordinates;
|
||||||
|
if (obj?.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
|
||||||
|
_legacyVpnLastConnected = obj.vpnLastConnected;
|
||||||
|
SessionData.vpnLastConnected = _legacyVpnLastConnected;
|
||||||
|
SessionData.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
|
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
|
||||||
_hasLoaded = true;
|
_hasLoaded = true;
|
||||||
@@ -2311,6 +2316,11 @@ Singleton {
|
|||||||
_legacyWeatherLocation = obj.weatherLocation;
|
_legacyWeatherLocation = obj.weatherLocation;
|
||||||
if (obj.weatherCoordinates !== undefined)
|
if (obj.weatherCoordinates !== undefined)
|
||||||
_legacyWeatherCoordinates = obj.weatherCoordinates;
|
_legacyWeatherCoordinates = obj.weatherCoordinates;
|
||||||
|
if (obj.vpnLastConnected !== undefined && obj.vpnLastConnected !== "") {
|
||||||
|
_legacyVpnLastConnected = obj.vpnLastConnected;
|
||||||
|
SessionData.vpnLastConnected = _legacyVpnLastConnected;
|
||||||
|
SessionData.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
|
_loadedSettingsSnapshot = JSON.stringify(Store.toJson(root));
|
||||||
_hasLoaded = true;
|
_hasLoaded = true;
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ Singleton {
|
|||||||
property var matugenColors: ({})
|
property var matugenColors: ({})
|
||||||
property var _pendingGenerateParams: null
|
property var _pendingGenerateParams: null
|
||||||
|
|
||||||
|
property bool themeModeAutomationActive: false
|
||||||
|
property bool dmsServiceWasDisconnected: true
|
||||||
|
|
||||||
readonly property var dank16: {
|
readonly property var dank16: {
|
||||||
const raw = matugenColors?.dank16;
|
const raw = matugenColors?.dank16;
|
||||||
if (!raw)
|
if (!raw)
|
||||||
@@ -176,6 +179,237 @@ Singleton {
|
|||||||
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
|
if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) {
|
||||||
switchTheme(SettingsData.currentThemeName, false, false);
|
switchTheme(SettingsData.currentThemeName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) {
|
||||||
|
startThemeModeAutomation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
enabled: typeof SessionData !== "undefined"
|
||||||
|
|
||||||
|
function onThemeModeAutoEnabledChanged() {
|
||||||
|
if (SessionData.themeModeAutoEnabled) {
|
||||||
|
root.startThemeModeAutomation();
|
||||||
|
} else {
|
||||||
|
root.stopThemeModeAutomation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeAutoModeChanged() {
|
||||||
|
if (root.themeModeAutomationActive) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeStartHourChanged() {
|
||||||
|
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeStartMinuteChanged() {
|
||||||
|
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeEndHourChanged() {
|
||||||
|
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeEndMinuteChanged() {
|
||||||
|
if (root.themeModeAutomationActive && !SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeModeShareGammaSettingsChanged() {
|
||||||
|
if (root.themeModeAutomationActive) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNightModeStartHourChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNightModeStartMinuteChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNightModeEndHourChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNightModeEndMinuteChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeShareGammaSettings) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLatitudeChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
|
||||||
|
if (!SessionData.nightModeUseIPLocation &&
|
||||||
|
SessionData.latitude !== 0.0 &&
|
||||||
|
SessionData.longitude !== 0.0 &&
|
||||||
|
typeof DMSService !== "undefined") {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
});
|
||||||
|
}
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLongitudeChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
|
||||||
|
if (!SessionData.nightModeUseIPLocation &&
|
||||||
|
SessionData.latitude !== 0.0 &&
|
||||||
|
SessionData.longitude !== 0.0 &&
|
||||||
|
typeof DMSService !== "undefined") {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
});
|
||||||
|
}
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNightModeUseIPLocationChanged() {
|
||||||
|
if (root.themeModeAutomationActive && SessionData.themeModeAutoMode === "location") {
|
||||||
|
if (typeof DMSService !== "undefined") {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
|
||||||
|
"use": SessionData.nightModeUseIPLocation
|
||||||
|
}, response => {
|
||||||
|
if (!response.error && !SessionData.nightModeUseIPLocation &&
|
||||||
|
SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// React to gamma backend's isDay state changes for location-based mode
|
||||||
|
Connections {
|
||||||
|
target: DisplayService
|
||||||
|
enabled: typeof DisplayService !== "undefined" &&
|
||||||
|
typeof SessionData !== "undefined" &&
|
||||||
|
SessionData.themeModeAutoEnabled &&
|
||||||
|
SessionData.themeModeAutoMode === "location" &&
|
||||||
|
!themeAutoBackendAvailable()
|
||||||
|
|
||||||
|
function onGammaIsDayChanged() {
|
||||||
|
if (root.isLightMode !== DisplayService.gammaIsDay) {
|
||||||
|
root.setLightMode(DisplayService.gammaIsDay, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DMSService
|
||||||
|
enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined"
|
||||||
|
|
||||||
|
function onLoginctlEvent(event) {
|
||||||
|
if (!SessionData.themeModeAutoEnabled) return;
|
||||||
|
if (event.event === "unlock" || event.event === "resume") {
|
||||||
|
if (!themeAutoBackendAvailable()) {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DMSService.sendRequest("theme.auto.trigger", {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onThemeAutoStateUpdate(data) {
|
||||||
|
if (!SessionData.themeModeAutoEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyThemeAutoState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnectionStateChanged() {
|
||||||
|
if (DMSService.isConnected && SessionData.themeModeAutoMode === "time") {
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeAutoBackendAvailable() && SessionData.themeModeAutoEnabled) {
|
||||||
|
DMSService.sendRequest("theme.auto.getState", null, response => {
|
||||||
|
if (response && response.result) {
|
||||||
|
applyThemeAutoState(response.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SessionData.themeModeAutoEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DMSService.isConnected && SessionData.themeModeAutoMode === "location") {
|
||||||
|
if (SessionData.nightModeUseIPLocation) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
|
||||||
|
"use": true
|
||||||
|
}, response => {
|
||||||
|
if (!response.error) {
|
||||||
|
console.info("Theme automation: IP location enabled after connection");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
|
||||||
|
"use": false
|
||||||
|
}, response => {
|
||||||
|
if (!response.error) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
}, locationResponse => {
|
||||||
|
if (locationResponse?.error) {
|
||||||
|
console.warn("Theme automation: Failed to set location", locationResponse.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn("Theme automation: No location configured");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyGreeterTheme(themeName) {
|
function applyGreeterTheme(themeName) {
|
||||||
@@ -491,7 +725,9 @@ Singleton {
|
|||||||
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
|
property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 1.0
|
||||||
|
|
||||||
function screenTransition() {
|
function screenTransition() {
|
||||||
CompositorService.isNiri && NiriService.doScreenTransition();
|
if (CompositorService.isNiri) {
|
||||||
|
NiriService.doScreenTransition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
||||||
@@ -543,8 +779,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
|
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
|
||||||
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
|
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
|
||||||
SessionData.setLightMode(light);
|
SessionData.setLightMode(light);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
// Skip with matugen because, our script runner will do it.
|
// Skip with matugen because, our script runner will do it.
|
||||||
if (!matugenAvailable) {
|
if (!matugenAvailable) {
|
||||||
@@ -552,6 +790,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
generateSystemThemesFromCurrentTheme();
|
generateSystemThemesFromCurrentTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLightMode(savePrefs = true) {
|
function toggleLightMode(savePrefs = true) {
|
||||||
@@ -1233,7 +1472,7 @@ Singleton {
|
|||||||
return `#${invR}${invG}${invB}`;
|
return `#${invR}${invG}${invB}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
property string baseLogoColor: {
|
property var baseLogoColor: {
|
||||||
if (typeof SettingsData === "undefined")
|
if (typeof SettingsData === "undefined")
|
||||||
return "";
|
return "";
|
||||||
const colorOverride = SettingsData.launcherLogoColorOverride;
|
const colorOverride = SettingsData.launcherLogoColorOverride;
|
||||||
@@ -1246,7 +1485,7 @@ Singleton {
|
|||||||
return colorOverride;
|
return colorOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
property string effectiveLogoColor: {
|
property var effectiveLogoColor: {
|
||||||
if (typeof SettingsData === "undefined")
|
if (typeof SettingsData === "undefined")
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
@@ -1453,4 +1692,297 @@ Singleton {
|
|||||||
root.switchTheme(defaultTheme, true, false);
|
root.switchTheme(defaultTheme, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Theme mode automation functions
|
||||||
|
function themeAutoBackendAvailable() {
|
||||||
|
return typeof DMSService !== "undefined" &&
|
||||||
|
DMSService.isConnected &&
|
||||||
|
Array.isArray(DMSService.capabilities) &&
|
||||||
|
DMSService.capabilities.includes("theme.auto");
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyThemeAutoState(state) {
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.config && state.config.mode && state.config.mode !== SessionData.themeModeAutoMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof SessionData !== "undefined" && state.nextTransition !== undefined) {
|
||||||
|
SessionData.themeModeNextTransition = state.nextTransition || "";
|
||||||
|
}
|
||||||
|
if (state.isLight !== undefined && root.isLightMode !== state.isLight) {
|
||||||
|
root.setLightMode(state.isLight, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTimeThemeSchedule() {
|
||||||
|
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DMSService.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "time";
|
||||||
|
|
||||||
|
if (!timeModeActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("theme.auto.setMode", {"mode": "time"});
|
||||||
|
|
||||||
|
const shareSettings = SessionData.themeModeShareGammaSettings;
|
||||||
|
const startHour = shareSettings ? SessionData.nightModeStartHour : SessionData.themeModeStartHour;
|
||||||
|
const startMinute = shareSettings ? SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
|
||||||
|
const endHour = shareSettings ? SessionData.nightModeEndHour : SessionData.themeModeEndHour;
|
||||||
|
const endMinute = shareSettings ? SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
|
||||||
|
|
||||||
|
DMSService.sendRequest("theme.auto.setSchedule", {
|
||||||
|
"startHour": startHour,
|
||||||
|
"startMinute": startMinute,
|
||||||
|
"endHour": endHour,
|
||||||
|
"endMinute": endMinute
|
||||||
|
}, response => {
|
||||||
|
if (response && response.error) {
|
||||||
|
console.error("Theme automation: Failed to sync time schedule:", response.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": true});
|
||||||
|
DMSService.sendRequest("theme.auto.trigger", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLocationThemeSchedule() {
|
||||||
|
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DMSService.isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locationModeActive = SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location";
|
||||||
|
|
||||||
|
if (!locationModeActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("theme.auto.setMode", {"mode": "location"});
|
||||||
|
|
||||||
|
if (SessionData.nightModeUseIPLocation) {
|
||||||
|
DMSService.sendRequest("theme.auto.setUseIPLocation", {"use": true});
|
||||||
|
} else {
|
||||||
|
DMSService.sendRequest("theme.auto.setUseIPLocation", {"use": false});
|
||||||
|
if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
||||||
|
DMSService.sendRequest("theme.auto.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": true});
|
||||||
|
DMSService.sendRequest("theme.auto.trigger", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateThemeMode() {
|
||||||
|
if (typeof SessionData === "undefined" || !SessionData.themeModeAutoEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeAutoBackendAvailable()) {
|
||||||
|
DMSService.sendRequest("theme.auto.getState", null, response => {
|
||||||
|
if (response && response.result) {
|
||||||
|
applyThemeAutoState(response.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = SessionData.themeModeAutoMode;
|
||||||
|
|
||||||
|
if (mode === "location") {
|
||||||
|
evaluateLocationBasedThemeMode();
|
||||||
|
} else {
|
||||||
|
evaluateTimeBasedThemeMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateLocationBasedThemeMode() {
|
||||||
|
if (typeof DisplayService !== "undefined") {
|
||||||
|
const shouldBeLight = DisplayService.gammaIsDay;
|
||||||
|
if (root.isLightMode !== shouldBeLight) {
|
||||||
|
root.setLightMode(shouldBeLight, true, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SessionData.nightModeUseIPLocation &&
|
||||||
|
SessionData.latitude !== 0.0 &&
|
||||||
|
SessionData.longitude !== 0.0) {
|
||||||
|
const shouldBeLight = calculateIsDaytime(
|
||||||
|
SessionData.latitude,
|
||||||
|
SessionData.longitude
|
||||||
|
);
|
||||||
|
if (root.isLightMode !== shouldBeLight) {
|
||||||
|
root.setLightMode(shouldBeLight, true, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.themeModeAutomationActive) {
|
||||||
|
if (SessionData.nightModeUseIPLocation) {
|
||||||
|
console.warn("Theme automation: Waiting for IP location from backend");
|
||||||
|
} else {
|
||||||
|
console.warn("Theme automation: Location mode requires coordinates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateTimeBasedThemeMode() {
|
||||||
|
const shareSettings = SessionData.themeModeShareGammaSettings;
|
||||||
|
|
||||||
|
const startHour = shareSettings ?
|
||||||
|
SessionData.nightModeStartHour : SessionData.themeModeStartHour;
|
||||||
|
const startMinute = shareSettings ?
|
||||||
|
SessionData.nightModeStartMinute : SessionData.themeModeStartMinute;
|
||||||
|
const endHour = shareSettings ?
|
||||||
|
SessionData.nightModeEndHour : SessionData.themeModeEndHour;
|
||||||
|
const endMinute = shareSettings ?
|
||||||
|
SessionData.nightModeEndMinute : SessionData.themeModeEndMinute;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
||||||
|
const startMinutes = startHour * 60 + startMinute;
|
||||||
|
const endMinutes = endHour * 60 + endMinute;
|
||||||
|
|
||||||
|
let shouldBeLight;
|
||||||
|
if (startMinutes < endMinutes) {
|
||||||
|
shouldBeLight = currentMinutes < startMinutes || currentMinutes >= endMinutes;
|
||||||
|
} else {
|
||||||
|
shouldBeLight = currentMinutes >= endMinutes && currentMinutes < startMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.isLightMode !== shouldBeLight) {
|
||||||
|
root.setLightMode(shouldBeLight, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateIsDaytime(lat, lng) {
|
||||||
|
const now = new Date();
|
||||||
|
const start = new Date(now.getFullYear(), 0, 0);
|
||||||
|
const diff = now - start;
|
||||||
|
const dayOfYear = Math.floor(diff / 86400000);
|
||||||
|
const latRad = lat * Math.PI / 180;
|
||||||
|
|
||||||
|
const declination = 23.45 * Math.sin((360/365) * (dayOfYear - 81) * Math.PI / 180);
|
||||||
|
const declinationRad = declination * Math.PI / 180;
|
||||||
|
|
||||||
|
const cosHourAngle = -Math.tan(latRad) * Math.tan(declinationRad);
|
||||||
|
|
||||||
|
if (cosHourAngle > 1) {
|
||||||
|
return false; // Polar night
|
||||||
|
}
|
||||||
|
if (cosHourAngle < -1) {
|
||||||
|
return true; // Midnight sun
|
||||||
|
}
|
||||||
|
|
||||||
|
const hourAngle = Math.acos(cosHourAngle);
|
||||||
|
const hourAngleDeg = hourAngle * 180 / Math.PI;
|
||||||
|
|
||||||
|
const sunriseHour = 12 - hourAngleDeg / 15;
|
||||||
|
const sunsetHour = 12 + hourAngleDeg / 15;
|
||||||
|
|
||||||
|
const timeZoneOffset = now.getTimezoneOffset() / 60;
|
||||||
|
const localSunrise = sunriseHour - lng / 15 - timeZoneOffset;
|
||||||
|
const localSunset = sunsetHour - lng / 15 - timeZoneOffset;
|
||||||
|
|
||||||
|
const currentHour = now.getHours() + now.getMinutes() / 60;
|
||||||
|
|
||||||
|
const normalizeSunrise = ((localSunrise % 24) + 24) % 24;
|
||||||
|
const normalizeSunset = ((localSunset % 24) + 24) % 24;
|
||||||
|
|
||||||
|
return currentHour >= normalizeSunrise && currentHour < normalizeSunset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to send location to backend
|
||||||
|
function sendLocationToBackend() {
|
||||||
|
if (typeof SessionData === "undefined" || typeof DMSService === "undefined") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DMSService.isConnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SessionData.nightModeUseIPLocation) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {"use": true}, response => {
|
||||||
|
if (response?.error) {
|
||||||
|
console.warn("Theme automation: Failed to enable IP location", response.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} else if (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {"use": false}, response => {
|
||||||
|
if (!response.error) {
|
||||||
|
DMSService.sendRequest("wayland.gamma.setLocation", {
|
||||||
|
"latitude": SessionData.latitude,
|
||||||
|
"longitude": SessionData.longitude
|
||||||
|
}, locResp => {
|
||||||
|
if (locResp?.error) {
|
||||||
|
console.warn("Theme automation: Failed to set location", locResp.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: locationRetryTimer
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
running: false
|
||||||
|
property int retryCount: 0
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (root.sendLocationToBackend()) {
|
||||||
|
stop();
|
||||||
|
retryCount = 0;
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
} else {
|
||||||
|
retryCount++;
|
||||||
|
if (retryCount >= 10) {
|
||||||
|
stop();
|
||||||
|
retryCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startThemeModeAutomation() {
|
||||||
|
root.themeModeAutomationActive = true;
|
||||||
|
|
||||||
|
root.syncTimeThemeSchedule();
|
||||||
|
root.syncLocationThemeSchedule();
|
||||||
|
|
||||||
|
const sent = root.sendLocationToBackend();
|
||||||
|
|
||||||
|
if (!sent && typeof SessionData !== "undefined" && SessionData.themeModeAutoMode === "location") {
|
||||||
|
locationRetryTimer.start();
|
||||||
|
} else {
|
||||||
|
root.evaluateThemeMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopThemeModeAutomation() {
|
||||||
|
root.themeModeAutomationActive = false;
|
||||||
|
if (typeof DMSService !== "undefined" && DMSService.isConnected) {
|
||||||
|
DMSService.sendRequest("theme.auto.setEnabled", {"enabled": false});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ var SPEC = {
|
|||||||
nightModeUseIPLocation: { def: false },
|
nightModeUseIPLocation: { def: false },
|
||||||
nightModeLocationProvider: { def: "" },
|
nightModeLocationProvider: { def: "" },
|
||||||
|
|
||||||
|
themeModeAutoEnabled: { def: false },
|
||||||
|
themeModeAutoMode: { def: "time" },
|
||||||
|
themeModeStartHour: { def: 18 },
|
||||||
|
themeModeStartMinute: { def: 0 },
|
||||||
|
themeModeEndHour: { def: 6 },
|
||||||
|
themeModeEndMinute: { def: 0 },
|
||||||
|
themeModeShareGammaSettings: { def: true },
|
||||||
|
|
||||||
weatherLocation: { def: "New York, NY" },
|
weatherLocation: { def: "New York, NY" },
|
||||||
weatherCoordinates: { def: "40.7128,-74.0060" },
|
weatherCoordinates: { def: "40.7128,-74.0060" },
|
||||||
|
|
||||||
@@ -61,7 +69,9 @@ var SPEC = {
|
|||||||
|
|
||||||
hiddenApps: { def: [] },
|
hiddenApps: { def: [] },
|
||||||
appOverrides: { def: {} },
|
appOverrides: { def: {} },
|
||||||
searchAppActions: { def: true }
|
searchAppActions: { def: true },
|
||||||
|
|
||||||
|
vpnLastConnected: { def: "" }
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.pragma library
|
.pragma library
|
||||||
|
|
||||||
.import "./SessionSpec.js" as SpecModule
|
.import "./SessionSpec.js" as SpecModule
|
||||||
|
|
||||||
function parse(root, jsonObj) {
|
function parse(root, jsonObj) {
|
||||||
var SPEC = SpecModule.SPEC;
|
var SPEC = SpecModule.SPEC;
|
||||||
@@ -68,6 +68,11 @@ function migrateToVersion(obj, targetVersion, settingsData) {
|
|||||||
session.configVersion = 2;
|
session.configVersion = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 3) {
|
||||||
|
console.info("SessionData: Migrating session to version 3");
|
||||||
|
session.configVersion = 3;
|
||||||
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,16 +79,18 @@ var SPEC = {
|
|||||||
privacyShowCameraIcon: { def: false },
|
privacyShowCameraIcon: { def: false },
|
||||||
privacyShowScreenShareIcon: { def: false },
|
privacyShowScreenShareIcon: { def: false },
|
||||||
|
|
||||||
controlCenterWidgets: { def: [
|
controlCenterWidgets: {
|
||||||
{ id: "volumeSlider", enabled: true, width: 50 },
|
def: [
|
||||||
{ id: "brightnessSlider", enabled: true, width: 50 },
|
{ id: "volumeSlider", enabled: true, width: 50 },
|
||||||
{ id: "wifi", enabled: true, width: 50 },
|
{ id: "brightnessSlider", enabled: true, width: 50 },
|
||||||
{ id: "bluetooth", enabled: true, width: 50 },
|
{ id: "wifi", enabled: true, width: 50 },
|
||||||
{ id: "audioOutput", enabled: true, width: 50 },
|
{ id: "bluetooth", enabled: true, width: 50 },
|
||||||
{ id: "audioInput", enabled: true, width: 50 },
|
{ id: "audioOutput", enabled: true, width: 50 },
|
||||||
{ id: "nightMode", enabled: true, width: 50 },
|
{ id: "audioInput", enabled: true, width: 50 },
|
||||||
{ id: "darkMode", enabled: true, width: 50 }
|
{ id: "nightMode", enabled: true, width: 50 },
|
||||||
]},
|
{ id: "darkMode", enabled: true, width: 50 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
showWorkspaceIndex: { def: false },
|
showWorkspaceIndex: { def: false },
|
||||||
showWorkspaceName: { def: false },
|
showWorkspaceName: { def: false },
|
||||||
@@ -119,13 +121,15 @@ var SPEC = {
|
|||||||
keyboardLayoutNameCompactMode: { def: false },
|
keyboardLayoutNameCompactMode: { def: false },
|
||||||
runningAppsCurrentWorkspace: { def: false },
|
runningAppsCurrentWorkspace: { def: false },
|
||||||
runningAppsGroupByApp: { def: false },
|
runningAppsGroupByApp: { def: false },
|
||||||
appIdSubstitutions: { def: [
|
appIdSubstitutions: {
|
||||||
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
|
def: [
|
||||||
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
|
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
|
||||||
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
|
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
|
||||||
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
|
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
|
||||||
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
|
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
|
||||||
]},
|
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
|
||||||
|
]
|
||||||
|
},
|
||||||
centeringMode: { def: "index" },
|
centeringMode: { def: "index" },
|
||||||
clockDateFormat: { def: "" },
|
clockDateFormat: { def: "" },
|
||||||
lockDateFormat: { def: "" },
|
lockDateFormat: { def: "" },
|
||||||
@@ -153,7 +157,6 @@ var SPEC = {
|
|||||||
weatherEnabled: { def: true },
|
weatherEnabled: { def: true },
|
||||||
|
|
||||||
networkPreference: { def: "auto" },
|
networkPreference: { def: "auto" },
|
||||||
vpnLastConnected: { def: "" },
|
|
||||||
|
|
||||||
iconTheme: { def: "System Default", onChange: "applyStoredIconTheme" },
|
iconTheme: { def: "System Default", onChange: "applyStoredIconTheme" },
|
||||||
availableIconThemes: { def: ["System Default"], persist: false },
|
availableIconThemes: { def: ["System Default"], persist: false },
|
||||||
@@ -306,7 +309,7 @@ var SPEC = {
|
|||||||
osdAlwaysShowValue: { def: false },
|
osdAlwaysShowValue: { def: false },
|
||||||
osdPosition: { def: 5 },
|
osdPosition: { def: 5 },
|
||||||
osdVolumeEnabled: { def: true },
|
osdVolumeEnabled: { def: true },
|
||||||
osdMediaVolumeEnabled : { def: true },
|
osdMediaVolumeEnabled: { def: true },
|
||||||
osdBrightnessEnabled: { def: true },
|
osdBrightnessEnabled: { def: true },
|
||||||
osdIdleInhibitorEnabled: { def: true },
|
osdIdleInhibitorEnabled: { def: true },
|
||||||
osdMicMuteEnabled: { def: true },
|
osdMicMuteEnabled: { def: true },
|
||||||
@@ -337,52 +340,54 @@ var SPEC = {
|
|||||||
niriOutputSettings: { def: {} },
|
niriOutputSettings: { def: {} },
|
||||||
hyprlandOutputSettings: { def: {} },
|
hyprlandOutputSettings: { def: {} },
|
||||||
|
|
||||||
barConfigs: { def: [{
|
barConfigs: {
|
||||||
id: "default",
|
def: [{
|
||||||
name: "Main Bar",
|
id: "default",
|
||||||
enabled: true,
|
name: "Main Bar",
|
||||||
position: 0,
|
enabled: true,
|
||||||
screenPreferences: ["all"],
|
position: 0,
|
||||||
showOnLastDisplay: true,
|
screenPreferences: ["all"],
|
||||||
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
showOnLastDisplay: true,
|
||||||
centerWidgets: ["music", "clock", "weather"],
|
leftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"],
|
||||||
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
centerWidgets: ["music", "clock", "weather"],
|
||||||
spacing: 4,
|
rightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"],
|
||||||
innerPadding: 4,
|
spacing: 4,
|
||||||
bottomGap: 0,
|
innerPadding: 4,
|
||||||
transparency: 1.0,
|
bottomGap: 0,
|
||||||
widgetTransparency: 1.0,
|
transparency: 1.0,
|
||||||
squareCorners: false,
|
widgetTransparency: 1.0,
|
||||||
noBackground: false,
|
squareCorners: false,
|
||||||
gothCornersEnabled: false,
|
noBackground: false,
|
||||||
gothCornerRadiusOverride: false,
|
gothCornersEnabled: false,
|
||||||
gothCornerRadiusValue: 12,
|
gothCornerRadiusOverride: false,
|
||||||
borderEnabled: false,
|
gothCornerRadiusValue: 12,
|
||||||
borderColor: "surfaceText",
|
borderEnabled: false,
|
||||||
borderOpacity: 1.0,
|
borderColor: "surfaceText",
|
||||||
borderThickness: 1,
|
borderOpacity: 1.0,
|
||||||
widgetOutlineEnabled: false,
|
borderThickness: 1,
|
||||||
widgetOutlineColor: "primary",
|
widgetOutlineEnabled: false,
|
||||||
widgetOutlineOpacity: 1.0,
|
widgetOutlineColor: "primary",
|
||||||
widgetOutlineThickness: 1,
|
widgetOutlineOpacity: 1.0,
|
||||||
fontScale: 1.0,
|
widgetOutlineThickness: 1,
|
||||||
autoHide: false,
|
fontScale: 1.0,
|
||||||
autoHideDelay: 250,
|
autoHide: false,
|
||||||
showOnWindowsOpen: false,
|
autoHideDelay: 250,
|
||||||
openOnOverview: false,
|
showOnWindowsOpen: false,
|
||||||
visible: true,
|
openOnOverview: false,
|
||||||
popupGapsAuto: true,
|
visible: true,
|
||||||
popupGapsManual: 4,
|
popupGapsAuto: true,
|
||||||
maximizeDetection: true,
|
popupGapsManual: 4,
|
||||||
scrollEnabled: true,
|
maximizeDetection: true,
|
||||||
scrollXBehavior: "column",
|
scrollEnabled: true,
|
||||||
scrollYBehavior: "workspace",
|
scrollXBehavior: "column",
|
||||||
shadowIntensity: 0,
|
scrollYBehavior: "workspace",
|
||||||
shadowOpacity: 60,
|
shadowIntensity: 0,
|
||||||
shadowColorMode: "text",
|
shadowOpacity: 60,
|
||||||
shadowCustomColor: "#000000",
|
shadowColorMode: "text",
|
||||||
clickThrough: false
|
shadowCustomColor: "#000000",
|
||||||
}], onChange: "updateBarConfigs" },
|
clickThrough: false
|
||||||
|
}], onChange: "updateBarConfigs"
|
||||||
|
},
|
||||||
|
|
||||||
desktopClockEnabled: { def: false },
|
desktopClockEnabled: { def: false },
|
||||||
desktopClockStyle: { def: "analog" },
|
desktopClockStyle: { def: "analog" },
|
||||||
@@ -437,7 +442,7 @@ var SPEC = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
return Object.keys(SPEC).filter(function(k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
|
return Object.keys(SPEC).filter(function (k) { return SPEC[k].persist !== false; }).concat(["configVersion"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(root, key, value, saveFn, hooks) {
|
function set(root, key, value, saveFn, hooks) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.pragma library
|
.pragma library
|
||||||
|
|
||||||
.import "./SettingsSpec.js" as SpecModule
|
.import "./SettingsSpec.js" as SpecModule
|
||||||
|
|
||||||
function parse(root, jsonObj) {
|
function parse(root, jsonObj) {
|
||||||
var SPEC = SpecModule.SPEC;
|
var SPEC = SpecModule.SPEC;
|
||||||
|
|||||||
@@ -1114,6 +1114,79 @@ Item {
|
|||||||
target: "spotlight"
|
target: "spotlight"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
function info(message: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showInfo(message);
|
||||||
|
return "TOAST_INFO_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function infoWith(message: string, details: string, command: string, category: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showInfo(message, details, command, category);
|
||||||
|
return "TOAST_INFO_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn(message: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showWarning(message);
|
||||||
|
return "TOAST_WARN_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function warnWith(message: string, details: string, command: string, category: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showWarning(message, details, command, category);
|
||||||
|
return "TOAST_WARN_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showError(message);
|
||||||
|
return "TOAST_ERROR_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorWith(message: string, details: string, command: string, category: string): string {
|
||||||
|
if (!message)
|
||||||
|
return "ERROR: No message specified";
|
||||||
|
|
||||||
|
ToastService.showError(message, details, command, category);
|
||||||
|
return "TOAST_ERROR_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide(): string {
|
||||||
|
ToastService.hideToast();
|
||||||
|
return "TOAST_HIDE_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismiss(category: string): string {
|
||||||
|
if (!category)
|
||||||
|
return "ERROR: No category specified";
|
||||||
|
|
||||||
|
ToastService.dismissCategory(category);
|
||||||
|
return "TOAST_DISMISS_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
function status(): string {
|
||||||
|
if (!ToastService.toastVisible)
|
||||||
|
return "hidden";
|
||||||
|
|
||||||
|
const levels = ["info", "warn", "error"];
|
||||||
|
return `visible:${levels[ToastService.currentLevel]}:${ToastService.currentMessage}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
target: "toast"
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function open(): string {
|
function open(): string {
|
||||||
FirstLaunchService.showWelcome();
|
FirstLaunchService.showWelcome();
|
||||||
|
|||||||
@@ -48,9 +48,14 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: PluginService
|
target: PluginService
|
||||||
function onRequestLauncherUpdate(pluginId) {
|
function onRequestLauncherUpdate(pluginId) {
|
||||||
if (activePluginId === pluginId || searchQuery) {
|
if (activePluginId === pluginId) {
|
||||||
|
if (activePluginCategories.length <= 1)
|
||||||
|
loadPluginCategories(pluginId);
|
||||||
performSearch();
|
performSearch();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (searchQuery)
|
||||||
|
performSearch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +138,8 @@ Item {
|
|||||||
|
|
||||||
property string pluginFilter: ""
|
property string pluginFilter: ""
|
||||||
property string activePluginName: ""
|
property string activePluginName: ""
|
||||||
|
property var activePluginCategories: []
|
||||||
|
property string activePluginCategory: ""
|
||||||
|
|
||||||
function getSectionViewMode(sectionId) {
|
function getSectionViewMode(sectionId) {
|
||||||
if (sectionId === "browse_plugins")
|
if (sectionId === "browse_plugins")
|
||||||
@@ -307,10 +314,33 @@ Item {
|
|||||||
isSearching = false;
|
isSearching = false;
|
||||||
activePluginId = "";
|
activePluginId = "";
|
||||||
activePluginName = "";
|
activePluginName = "";
|
||||||
|
activePluginCategories = [];
|
||||||
|
activePluginCategory = "";
|
||||||
pluginFilter = "";
|
pluginFilter = "";
|
||||||
collapsedSections = {};
|
collapsedSections = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadPluginCategories(pluginId) {
|
||||||
|
if (!pluginId) {
|
||||||
|
activePluginCategories = [];
|
||||||
|
activePluginCategory = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = AppSearchService.getPluginLauncherCategories(pluginId);
|
||||||
|
activePluginCategories = categories;
|
||||||
|
activePluginCategory = "";
|
||||||
|
AppSearchService.setPluginLauncherCategory(pluginId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActivePluginCategory(categoryId) {
|
||||||
|
if (activePluginCategory === categoryId)
|
||||||
|
return;
|
||||||
|
activePluginCategory = categoryId;
|
||||||
|
AppSearchService.setPluginLauncherCategory(activePluginId, categoryId);
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
|
||||||
function clearPluginFilter() {
|
function clearPluginFilter() {
|
||||||
if (pluginFilter) {
|
if (pluginFilter) {
|
||||||
pluginFilter = "";
|
pluginFilter = "";
|
||||||
@@ -342,6 +372,8 @@ Item {
|
|||||||
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
|
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
|
||||||
activePluginId = "";
|
activePluginId = "";
|
||||||
activePluginName = "";
|
activePluginName = "";
|
||||||
|
activePluginCategories = [];
|
||||||
|
activePluginCategory = "";
|
||||||
clearActivePluginViewPreference();
|
clearActivePluginViewPreference();
|
||||||
sections = cachedSections.map(function (s) {
|
sections = cachedSections.map(function (s) {
|
||||||
var copy = Object.assign({}, s, {
|
var copy = Object.assign({}, s, {
|
||||||
@@ -363,10 +395,14 @@ Item {
|
|||||||
|
|
||||||
var triggerMatch = detectTrigger(searchQuery);
|
var triggerMatch = detectTrigger(searchQuery);
|
||||||
if (triggerMatch.pluginId) {
|
if (triggerMatch.pluginId) {
|
||||||
|
var pluginChanged = activePluginId !== triggerMatch.pluginId;
|
||||||
activePluginId = triggerMatch.pluginId;
|
activePluginId = triggerMatch.pluginId;
|
||||||
activePluginName = getPluginName(triggerMatch.pluginId, triggerMatch.isBuiltIn);
|
activePluginName = getPluginName(triggerMatch.pluginId, triggerMatch.isBuiltIn);
|
||||||
applyActivePluginViewPreference(triggerMatch.pluginId, triggerMatch.isBuiltIn);
|
applyActivePluginViewPreference(triggerMatch.pluginId, triggerMatch.isBuiltIn);
|
||||||
|
|
||||||
|
if (pluginChanged && !triggerMatch.isBuiltIn)
|
||||||
|
loadPluginCategories(triggerMatch.pluginId);
|
||||||
|
|
||||||
var pluginItems = getPluginItems(triggerMatch.pluginId, triggerMatch.query);
|
var pluginItems = getPluginItems(triggerMatch.pluginId, triggerMatch.query);
|
||||||
allItems = allItems.concat(pluginItems);
|
allItems = allItems.concat(pluginItems);
|
||||||
|
|
||||||
@@ -401,6 +437,8 @@ Item {
|
|||||||
|
|
||||||
activePluginId = "";
|
activePluginId = "";
|
||||||
activePluginName = "";
|
activePluginName = "";
|
||||||
|
activePluginCategories = [];
|
||||||
|
activePluginCategory = "";
|
||||||
clearActivePluginViewPreference();
|
clearActivePluginViewPreference();
|
||||||
|
|
||||||
if (searchMode === "files") {
|
if (searchMode === "files") {
|
||||||
|
|||||||
@@ -483,9 +483,64 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: categoryRow
|
||||||
|
width: parent.width
|
||||||
|
height: controller.activePluginCategories.length > 0 ? 36 : 0
|
||||||
|
visible: controller.activePluginCategories.length > 0
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
id: categoryDropdown
|
||||||
|
width: Math.min(200, parent.width)
|
||||||
|
compactMode: true
|
||||||
|
dropdownWidth: 200
|
||||||
|
popupWidth: 240
|
||||||
|
maxPopupHeight: 300
|
||||||
|
enableFuzzySearch: controller.activePluginCategories.length > 8
|
||||||
|
currentValue: {
|
||||||
|
const cats = controller.activePluginCategories;
|
||||||
|
const current = controller.activePluginCategory;
|
||||||
|
if (!current)
|
||||||
|
return cats.length > 0 ? cats[0].name : "";
|
||||||
|
for (let i = 0; i < cats.length; i++) {
|
||||||
|
if (cats[i].id === current)
|
||||||
|
return cats[i].name;
|
||||||
|
}
|
||||||
|
return cats.length > 0 ? cats[0].name : "";
|
||||||
|
}
|
||||||
|
options: {
|
||||||
|
const cats = controller.activePluginCategories;
|
||||||
|
const names = [];
|
||||||
|
for (let i = 0; i < cats.length; i++)
|
||||||
|
names.push(cats[i].name);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChanged: value => {
|
||||||
|
const cats = controller.activePluginCategories;
|
||||||
|
for (let i = 0; i < cats.length; i++) {
|
||||||
|
if (cats[i].name === value) {
|
||||||
|
controller.setActivePluginCategory(cats[i].id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - actionPanel.height - Theme.spacingXS * 2
|
height: parent.height - searchField.height - categoryRow.height - actionPanel.height - Theme.spacingXS * (categoryRow.visible ? 3 : 2)
|
||||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||||
|
|
||||||
ResultsList {
|
ResultsList {
|
||||||
|
|||||||
@@ -508,18 +508,17 @@ Item {
|
|||||||
|
|
||||||
function getRealWorkspaces() {
|
function getRealWorkspaces() {
|
||||||
return root.workspaceList.filter(ws => {
|
return root.workspaceList.filter(ws => {
|
||||||
if (useExtWorkspace)
|
if (useExtWorkspace)
|
||||||
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
|
return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri)
|
||||||
return ws && ws.idx !== -1;
|
return ws && ws.idx !== -1;
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return ws && ws.id !== -1;
|
return ws && ws.id !== -1;
|
||||||
if (CompositorService.isDwl)
|
if (CompositorService.isDwl)
|
||||||
return ws && ws.tag !== -1;
|
return ws && ws.tag !== -1;
|
||||||
if (CompositorService.isSway || CompositorService.isScroll)
|
if (CompositorService.isSway || CompositorService.isScroll)
|
||||||
return ws && ws.num !== -1;
|
return ws && ws.num !== -1;
|
||||||
return ws !== -1;
|
return ws !== -1;
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,6 +863,60 @@ Item {
|
|||||||
property bool loadedHasIcon: false
|
property bool loadedHasIcon: false
|
||||||
property var loadedIcons: []
|
property var loadedIcons: []
|
||||||
|
|
||||||
|
readonly property int stableIconCount: {
|
||||||
|
if (!SettingsData.showWorkspaceApps || isPlaceholder)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
let targetWorkspaceId;
|
||||||
|
if (root.useExtWorkspace) {
|
||||||
|
targetWorkspaceId = modelData?.id || modelData?.name;
|
||||||
|
} else if (CompositorService.isNiri) {
|
||||||
|
targetWorkspaceId = modelData?.id;
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
targetWorkspaceId = modelData?.id;
|
||||||
|
} else if (CompositorService.isDwl) {
|
||||||
|
targetWorkspaceId = modelData?.tag;
|
||||||
|
} else if (CompositorService.isSway || CompositorService.isScroll) {
|
||||||
|
targetWorkspaceId = modelData?.num;
|
||||||
|
}
|
||||||
|
if (targetWorkspaceId === undefined || targetWorkspaceId === null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
|
||||||
|
const seen = {};
|
||||||
|
let groupedCount = 0;
|
||||||
|
let totalCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < wins.length; i++) {
|
||||||
|
const w = wins[i];
|
||||||
|
if (!w)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let winWs = null;
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
winWs = w.workspace_id;
|
||||||
|
} else if (CompositorService.isSway || CompositorService.isScroll) {
|
||||||
|
winWs = w.workspace?.num;
|
||||||
|
} else if (CompositorService.isHyprland) {
|
||||||
|
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
|
||||||
|
const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
|
||||||
|
winWs = hyprToplevel?.workspace?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winWs !== targetWorkspaceId)
|
||||||
|
continue;
|
||||||
|
totalCount++;
|
||||||
|
|
||||||
|
const appKey = w.app_id || w.appId || w.class || w.windowClass || "unknown";
|
||||||
|
if (!seen[appKey]) {
|
||||||
|
seen[appKey] = true;
|
||||||
|
groupedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettingsData.groupWorkspaceApps ? groupedCount : totalCount;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5) : (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7)
|
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5) : (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7)
|
||||||
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5)
|
readonly property real baseHeight: root.isVertical ? (isActive ? root.widgetHeight * 1.05 : root.widgetHeight * 0.7) : (SettingsData.showWorkspaceApps ? widgetHeight * 0.7 : widgetHeight * 0.5)
|
||||||
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
|
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
|
||||||
@@ -872,27 +925,29 @@ Item {
|
|||||||
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0
|
readonly property real contentImplicitHeight: (workspaceNamesEnabled || loadedHasIcon) ? (appIconsLoader.item?.contentHeight ?? 0) : 0
|
||||||
|
|
||||||
readonly property real iconsExtraWidth: {
|
readonly property real iconsExtraWidth: {
|
||||||
if (!root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
|
||||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
|
||||||
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
readonly property real iconsExtraHeight: {
|
readonly property real iconsExtraHeight: {
|
||||||
if (root.isVertical && SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
|
if (root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
|
||||||
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
|
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
|
||||||
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property real visualWidth: {
|
readonly property real visualWidth: {
|
||||||
if (contentImplicitWidth <= 0) return baseWidth + iconsExtraWidth;
|
if (contentImplicitWidth <= 0)
|
||||||
|
return baseWidth + iconsExtraWidth;
|
||||||
const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
|
const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
|
||||||
return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
|
return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
|
||||||
}
|
}
|
||||||
readonly property real visualHeight: {
|
readonly property real visualHeight: {
|
||||||
if (contentImplicitHeight <= 0) return baseHeight + iconsExtraHeight;
|
if (contentImplicitHeight <= 0)
|
||||||
|
return baseHeight + iconsExtraHeight;
|
||||||
const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
|
const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
|
||||||
return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
|
return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
|
||||||
}
|
}
|
||||||
@@ -1080,6 +1135,20 @@ Item {
|
|||||||
width: root.isVertical ? root.widgetHeight : visualWidth
|
width: root.isVertical ? root.widgetHeight : visualWidth
|
||||||
height: root.isVertical ? visualHeight : root.widgetHeight
|
height: root.isVertical ? visualHeight : root.widgetHeight
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: focusedBorderRing
|
id: focusedBorderRing
|
||||||
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
||||||
@@ -1349,6 +1418,15 @@ Item {
|
|||||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: (SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: root.getWorkspaceIndex(modelData, index)
|
||||||
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
||||||
|
|||||||
@@ -125,6 +125,19 @@ Item {
|
|||||||
return Theme.warning;
|
return Theme.warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatThemeAutoTime(isoString) {
|
||||||
|
if (!isoString)
|
||||||
|
return "";
|
||||||
|
try {
|
||||||
|
const date = new Date(isoString);
|
||||||
|
if (isNaN(date.getTime()))
|
||||||
|
return "";
|
||||||
|
return date.toLocaleTimeString(Qt.locale(), "HH:mm");
|
||||||
|
} catch (e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
SettingsData.detectAvailableIconThemes();
|
SettingsData.detectAvailableIconThemes();
|
||||||
SettingsData.detectAvailableCursorThemes();
|
SettingsData.detectAvailableCursorThemes();
|
||||||
@@ -152,7 +165,6 @@ Item {
|
|||||||
}
|
}
|
||||||
themeColorsTab.templateDetection = detection;
|
themeColorsTab.templateDetection = detection;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("ThemeColorsTab: Failed to parse template check:", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -962,6 +974,453 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
tab: "theme"
|
||||||
|
tags: ["automatic", "color", "mode", "schedule", "sunrise", "sunset"]
|
||||||
|
title: I18n.tr("Automatic Color Mode")
|
||||||
|
settingKey: "automaticColorMode"
|
||||||
|
iconName: "schedule"
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: themeModeAutoToggle
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Enable Automatic Switching")
|
||||||
|
description: I18n.tr("Automatically switch between light and dark modes based on time or sunrise/sunset")
|
||||||
|
checked: SessionData.themeModeAutoEnabled
|
||||||
|
onToggled: checked => {
|
||||||
|
SessionData.setThemeModeAutoEnabled(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onThemeModeAutoEnabledChanged() {
|
||||||
|
themeModeAutoToggle.checked = SessionData.themeModeAutoEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SessionData.themeModeAutoEnabled
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Share Gamma Control Settings")
|
||||||
|
description: I18n.tr("Use the same time and location settings as gamma control")
|
||||||
|
checked: SessionData.themeModeShareGammaSettings
|
||||||
|
onToggled: checked => {
|
||||||
|
SessionData.setThemeModeShareGammaSettings(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 45 + Theme.spacingM
|
||||||
|
|
||||||
|
DankTabBar {
|
||||||
|
id: themeModeTabBar
|
||||||
|
width: 200
|
||||||
|
height: 45
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
model: [
|
||||||
|
{ "text": "Time", "icon": "access_time" },
|
||||||
|
{ "text": "Location", "icon": "place" }
|
||||||
|
]
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
|
||||||
|
Qt.callLater(updateIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTabClicked: index => {
|
||||||
|
SessionData.setThemeModeAutoMode(index === 1 ? "location" : "time");
|
||||||
|
currentIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onThemeModeAutoModeChanged() {
|
||||||
|
themeModeTabBar.currentIndex = SessionData.themeModeAutoMode === "location" ? 1 : 0;
|
||||||
|
Qt.callLater(themeModeTabBar.updateIndicator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SessionData.themeModeAutoMode === "time" && !SessionData.themeModeShareGammaSettings
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: ""
|
||||||
|
width: 80
|
||||||
|
height: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Hour")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: 70
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Minute")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: 70
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Dark Start")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: 80
|
||||||
|
height: 40
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
dropdownWidth: 70
|
||||||
|
currentValue: SessionData.themeModeStartHour.toString()
|
||||||
|
options: {
|
||||||
|
var hours = [];
|
||||||
|
for (var i = 0; i < 24; i++) hours.push(i.toString());
|
||||||
|
return hours;
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
SessionData.setThemeModeStartHour(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
dropdownWidth: 70
|
||||||
|
currentValue: SessionData.themeModeStartMinute.toString().padStart(2, '0')
|
||||||
|
options: {
|
||||||
|
var minutes = [];
|
||||||
|
for (var i = 0; i < 60; i += 5) {
|
||||||
|
minutes.push(i.toString().padStart(2, '0'));
|
||||||
|
}
|
||||||
|
return minutes;
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
SessionData.setThemeModeStartMinute(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Light Start")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: 80
|
||||||
|
height: 40
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
dropdownWidth: 70
|
||||||
|
currentValue: SessionData.themeModeEndHour.toString()
|
||||||
|
options: {
|
||||||
|
var hours = [];
|
||||||
|
for (var i = 0; i < 24; i++) hours.push(i.toString());
|
||||||
|
return hours;
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
SessionData.setThemeModeEndHour(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
dropdownWidth: 70
|
||||||
|
currentValue: SessionData.themeModeEndMinute.toString().padStart(2, '0')
|
||||||
|
options: {
|
||||||
|
var minutes = [];
|
||||||
|
for (var i = 0; i < 60; i += 5) {
|
||||||
|
minutes.push(i.toString().padStart(2, '0'));
|
||||||
|
}
|
||||||
|
return minutes;
|
||||||
|
}
|
||||||
|
onValueChanged: value => {
|
||||||
|
SessionData.setThemeModeEndMinute(parseInt(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SessionData.isLightMode ? I18n.tr("Light mode will be active from Light Start to Dark Start") : I18n.tr("Dark mode will be active from Dark Start to Light Start")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SessionData.themeModeAutoMode === "location" && !SessionData.themeModeShareGammaSettings
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
id: themeModeIpLocationToggle
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Use IP Location")
|
||||||
|
description: I18n.tr("Automatically detect location based on IP address")
|
||||||
|
checked: SessionData.nightModeUseIPLocation || false
|
||||||
|
onToggled: checked => {
|
||||||
|
SessionData.setNightModeUseIPLocation(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onNightModeUseIPLocationChanged() {
|
||||||
|
themeModeIpLocationToggle.checked = SessionData.nightModeUseIPLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: !SessionData.nightModeUseIPLocation
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Manual Coordinates")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Latitude")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
text: SessionData.latitude.toString()
|
||||||
|
placeholderText: "0.0"
|
||||||
|
onEditingFinished: {
|
||||||
|
const lat = parseFloat(text);
|
||||||
|
if (!isNaN(lat) && lat >= -90 && lat <= 90 && lat !== SessionData.latitude) {
|
||||||
|
SessionData.setLatitude(lat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Longitude")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: 120
|
||||||
|
height: 40
|
||||||
|
text: SessionData.longitude.toString()
|
||||||
|
placeholderText: "0.0"
|
||||||
|
onEditingFinished: {
|
||||||
|
const lon = parseFloat(text);
|
||||||
|
if (!isNaN(lon) && lon >= -180 && lon <= 180 && lon !== SessionData.longitude) {
|
||||||
|
SessionData.setLongitude(lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Uses sunrise/sunset times to automatically adjust theme mode based on your location.")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SessionData.isLightMode ? I18n.tr("Light mode will be active from sunrise to sunset") : I18n.tr("Dark mode will be active from sunset to sunrise")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
visible: SessionData.nightModeUseIPLocation || (SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Using shared settings from Gamma Control")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.primary
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
visible: SessionData.themeModeShareGammaSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: statusRow.implicitHeight + Theme.spacingM * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: statusRow
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
width: (parent.width - Theme.spacingL * 2) / 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 8
|
||||||
|
height: 8
|
||||||
|
radius: 4
|
||||||
|
color: SessionData.themeModeAutoEnabled ? Theme.success : Theme.error
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Automation")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SessionData.themeModeAutoEnabled ? I18n.tr("Enabled") : I18n.tr("Disabled")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
width: (parent.width - Theme.spacingL * 2) / 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: SessionData.isLightMode ? "light_mode" : "dark_mode"
|
||||||
|
size: Theme.iconSizeMedium
|
||||||
|
color: SessionData.isLightMode ? "#FFA726" : "#7E57C2"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SessionData.isLightMode ? I18n.tr("Light Mode") : I18n.tr("Dark Mode")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Active")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 2
|
||||||
|
width: (parent.width - Theme.spacingL * 2) / 3
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
visible: SessionData.themeModeAutoEnabled && SessionData.themeModeNextTransition
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "schedule"
|
||||||
|
size: Theme.iconSizeMedium
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Next Transition")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: themeColorsTab.formatThemeAutoTime(SessionData.themeModeNextTransition)
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "theme"
|
tab: "theme"
|
||||||
tags: ["light", "dark", "mode", "appearance"]
|
tags: ["light", "dark", "mode", "appearance"]
|
||||||
|
|||||||
@@ -884,4 +884,52 @@ Singleton {
|
|||||||
|
|
||||||
return allItems;
|
return allItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPluginLauncherCategories(pluginId) {
|
||||||
|
if (typeof PluginService === "undefined")
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const instance = PluginService.pluginInstances[pluginId];
|
||||||
|
if (!instance)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
if (typeof instance.getCategories !== "function")
|
||||||
|
return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
return instance.getCategories() || [];
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("AppSearchService: Error getting categories from plugin", pluginId, ":", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPluginLauncherCategory(pluginId, categoryId) {
|
||||||
|
if (typeof PluginService === "undefined")
|
||||||
|
return;
|
||||||
|
|
||||||
|
const instance = PluginService.pluginInstances[pluginId];
|
||||||
|
if (!instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (typeof instance.setCategory !== "function")
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
instance.setCategory(categoryId);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("AppSearchService: Error setting category on plugin", pluginId, ":", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pluginHasCategories(pluginId) {
|
||||||
|
if (typeof PluginService === "undefined")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const instance = PluginService.pluginInstances[pluginId];
|
||||||
|
if (!instance)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return typeof instance.getCategories === "function";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ Singleton {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
root.userPreference = SettingsData.networkPreference;
|
root.userPreference = SettingsData.networkPreference;
|
||||||
lastConnectedVpnUuid = SettingsData.vpnLastConnected || "";
|
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
checkDMSCapabilities();
|
checkDMSCapabilities();
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ Singleton {
|
|||||||
|
|
||||||
if (vpnConnected && activeUuid) {
|
if (vpnConnected && activeUuid) {
|
||||||
lastConnectedVpnUuid = activeUuid;
|
lastConnectedVpnUuid = activeUuid;
|
||||||
SettingsData.set("vpnLastConnected", activeUuid);
|
SessionData.setVpnLastConnected(activeUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vpnIsBusy) {
|
if (vpnIsBusy) {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ Singleton {
|
|||||||
signal wlrOutputStateUpdate(var data)
|
signal wlrOutputStateUpdate(var data)
|
||||||
signal evdevStateUpdate(var data)
|
signal evdevStateUpdate(var data)
|
||||||
signal gammaStateUpdate(var data)
|
signal gammaStateUpdate(var data)
|
||||||
|
signal themeAutoStateUpdate(var data)
|
||||||
signal openUrlRequested(string url)
|
signal openUrlRequested(string url)
|
||||||
signal appPickerRequested(var data)
|
signal appPickerRequested(var data)
|
||||||
signal screensaverStateUpdate(var data)
|
signal screensaverStateUpdate(var data)
|
||||||
@@ -64,7 +65,7 @@ Singleton {
|
|||||||
property bool screensaverInhibited: false
|
property bool screensaverInhibited: false
|
||||||
property var screensaverInhibitors: []
|
property var screensaverInhibitors: []
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "bluetooth", "bluetooth.pairing", "dwl", "brightness", "wlroutput", "evdev", "browser", "dbus"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -304,7 +305,7 @@ Singleton {
|
|||||||
excludeServices = [excludeServices];
|
excludeServices = [excludeServices];
|
||||||
}
|
}
|
||||||
|
|
||||||
const allServices = ["network", "loginctl", "freedesktop", "gamma", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
|
const allServices = ["network", "loginctl", "freedesktop", "gamma", "theme.auto", "bluetooth", "cups", "dwl", "brightness", "extworkspace", "browser", "dbus"];
|
||||||
const filtered = allServices.filter(s => !excludeServices.includes(s));
|
const filtered = allServices.filter(s => !excludeServices.includes(s));
|
||||||
subscribe(filtered);
|
subscribe(filtered);
|
||||||
}
|
}
|
||||||
@@ -373,6 +374,8 @@ Singleton {
|
|||||||
evdevStateUpdate(data);
|
evdevStateUpdate(data);
|
||||||
} else if (service === "gamma") {
|
} else if (service === "gamma") {
|
||||||
gammaStateUpdate(data);
|
gammaStateUpdate(data);
|
||||||
|
} else if (service === "theme.auto") {
|
||||||
|
themeAutoStateUpdate(data);
|
||||||
} else if (service === "browser.open_requested") {
|
} else if (service === "browser.open_requested") {
|
||||||
if (data.target) {
|
if (data.target) {
|
||||||
if (data.requestType === "url" || !data.requestType) {
|
if (data.requestType === "url" || !data.requestType) {
|
||||||
|
|||||||
@@ -396,7 +396,6 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.currentValue = delegateRoot.modelData;
|
|
||||||
root.valueChanged(delegateRoot.modelData);
|
root.valueChanged(delegateRoot.modelData);
|
||||||
dropdownMenu.close();
|
dropdownMenu.close();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user