mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 11:38:30 -04:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52068fb61b | |||
| 64461c534f | |||
| 73da4879f6 | |||
| 8594a414ec | |||
| df396bfa43 | |||
| 33677150b1 | |||
| c67b185076 | |||
| bab2078dfd | |||
| e9bc0169f6 | |||
| 59c83d19e4 | |||
| 9b7d3c64fe | |||
| c367153bac | |||
| 1f94c3cbd4 | |||
| 564583951c | |||
| eed3617a0d | |||
| 24cb3d19a0 | |||
| 3182c70857 | |||
| 8bb7396362 | |||
| 62648ed75a | |||
| fa629fbf99 | |||
| 7222e072af | |||
| f57f254855 | |||
| fa094db127 | |||
| 77a357109a | |||
| 295657492d | |||
| 2bafc21229 | |||
| ea03fb2788 | |||
| 0815e48465 | |||
| 42060366cb | |||
| 8af71036cc | |||
| 5854327c28 | |||
| 99f452c999 | |||
| d818067bee | |||
| 5538913320 | |||
| 8188ced955 | |||
| 75eac83b47 |
@@ -40,6 +40,7 @@ var resolveIncludeCmd = &cobra.Command{
|
|||||||
"layout.kdl",
|
"layout.kdl",
|
||||||
"outputs.kdl",
|
"outputs.kdl",
|
||||||
"binds.kdl",
|
"binds.kdl",
|
||||||
|
"input.kdl",
|
||||||
"cursor.conf",
|
"cursor.conf",
|
||||||
"layout.conf",
|
"layout.conf",
|
||||||
"outputs.conf",
|
"outputs.conf",
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ var dmsConfigSpecs = map[string]dmsConfigSpec{
|
|||||||
niriFile: "alttab.kdl",
|
niriFile: "alttab.kdl",
|
||||||
niriContent: func(_ string) string { return config.NiriAlttabConfig },
|
niriContent: func(_ string) string { return config.NiriAlttabConfig },
|
||||||
},
|
},
|
||||||
|
"input": {
|
||||||
|
niriFile: "input.kdl",
|
||||||
|
niriContent: func(_ string) string { return config.NiriInputConfig },
|
||||||
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
niriFile: "outputs.kdl",
|
niriFile: "outputs.kdl",
|
||||||
hyprFile: "outputs.lua",
|
hyprFile: "outputs.lua",
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260721162324-38d48943054d
|
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260721162324-38d48943054d h1:EZN2x2uAX975Uvw4MByqWaaVTp2fOJrNTl4V4EQbSn8=
|
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05 h1:Ij/yzOT8y2HL7V5Rkec1GxJV0rUvhKqfAzyGxVRTk1o=
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260721162324-38d48943054d/go.mod h1:7p7cfydr4WM1G6eOPFlANXF3IV5du3FoA4CbDPprHAo=
|
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05/go.mod h1:xt8RldAfti0QCWidwYIzsSSoJWsE61WgEhTu2H9UpD4=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
|
|||||||
{"layout.kdl", NiriLayoutConfig},
|
{"layout.kdl", NiriLayoutConfig},
|
||||||
{"alttab.kdl", NiriAlttabConfig},
|
{"alttab.kdl", NiriAlttabConfig},
|
||||||
{"binds.kdl", strings.ReplaceAll(NiriBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)},
|
{"binds.kdl", strings.ReplaceAll(NiriBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)},
|
||||||
|
{"input.kdl", NiriInputConfig},
|
||||||
{"outputs.kdl", ""},
|
{"outputs.kdl", ""},
|
||||||
{"cursor.kdl", ""},
|
{"cursor.kdl", ""},
|
||||||
{"windowrules.kdl", ""},
|
{"windowrules.kdl", ""},
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
|
input {
|
||||||
|
mouse {
|
||||||
|
accel-speed 0.0
|
||||||
|
}
|
||||||
|
touchpad {
|
||||||
|
tap
|
||||||
|
accel-speed 0.0
|
||||||
|
natural-scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,31 +34,6 @@ input {
|
|||||||
numlock
|
numlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next sections include libinput settings.
|
|
||||||
// Omitting settings disables them, or leaves them at their default values.
|
|
||||||
// All commented-out settings here are examples, not defaults.
|
|
||||||
touchpad {
|
|
||||||
// off
|
|
||||||
tap
|
|
||||||
// dwt
|
|
||||||
// dwtp
|
|
||||||
// drag false
|
|
||||||
// drag-lock
|
|
||||||
natural-scroll
|
|
||||||
// accel-speed 0.2
|
|
||||||
// accel-profile "flat"
|
|
||||||
// scroll-method "two-finger"
|
|
||||||
// disabled-on-external-mouse
|
|
||||||
}
|
|
||||||
|
|
||||||
mouse {
|
|
||||||
// off
|
|
||||||
// natural-scroll
|
|
||||||
// accel-speed 0.2
|
|
||||||
// accel-profile "flat"
|
|
||||||
// scroll-method "no-scroll"
|
|
||||||
}
|
|
||||||
|
|
||||||
trackpoint {
|
trackpoint {
|
||||||
// off
|
// off
|
||||||
// natural-scroll
|
// natural-scroll
|
||||||
@@ -77,6 +52,7 @@ input {
|
|||||||
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
|
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
|
||||||
// focus-follows-mouse max-scroll-amount="0%"
|
// focus-follows-mouse max-scroll-amount="0%"
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can configure outputs by their name, which you can find
|
// You can configure outputs by their name, which you can find
|
||||||
// by running `niri msg outputs` while inside a niri instance.
|
// by running `niri msg outputs` while inside a niri instance.
|
||||||
// The built-in laptop monitor is usually called "eDP-1".
|
// The built-in laptop monitor is usually called "eDP-1".
|
||||||
@@ -271,3 +247,4 @@ include optional=true "dms/alttab.kdl"
|
|||||||
include optional=true "dms/binds.kdl"
|
include optional=true "dms/binds.kdl"
|
||||||
include optional=true "dms/outputs.kdl"
|
include optional=true "dms/outputs.kdl"
|
||||||
include optional=true "dms/cursor.kdl"
|
include optional=true "dms/cursor.kdl"
|
||||||
|
include optional=true "dms/input.kdl"
|
||||||
|
|||||||
@@ -16,3 +16,6 @@ var NiriAlttabConfig string
|
|||||||
|
|
||||||
//go:embed embedded/niri-binds.kdl
|
//go:embed embedded/niri-binds.kdl
|
||||||
var NiriBindsConfig string
|
var NiriBindsConfig string
|
||||||
|
|
||||||
|
//go:embed embedded/niri-input.kdl
|
||||||
|
var NiriInputConfig string
|
||||||
|
|||||||
@@ -386,8 +386,8 @@ func buildOnce(opts *Options) (bool, error) {
|
|||||||
default:
|
default:
|
||||||
syncAccentColor(primaryDark)
|
syncAccentColor(primaryDark)
|
||||||
}
|
}
|
||||||
refreshGTK(opts.Mode)
|
refreshGTKTheme(opts.Mode)
|
||||||
refreshGTK4()
|
refreshGTKColorScheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDMSKDEColorSchemeActive(opts.ConfigDir) {
|
if isDMSKDEColorSchemeActive(opts.ConfigDir) {
|
||||||
@@ -911,7 +911,7 @@ func generateDank16Variants(primaryDark, primaryLight, surface string, mode Colo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isDMSGTKActive(configDir string) bool {
|
func isDMSGTKActive(configDir string) bool {
|
||||||
gtkCSS := filepath.Join(configDir, "gtk-3.0", "gtk.css")
|
gtkCSS := filepath.Join(configDir, "gtk-4.0", "gtk.css")
|
||||||
|
|
||||||
info, err := os.Lstat(gtkCSS)
|
info, err := os.Lstat(gtkCSS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -968,11 +968,32 @@ func applyKDEColorScheme(mode ColorMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshGTK(mode ColorMode) {
|
func gtkThemeInstalled(theme string) bool {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Join(home, ".local/share/themes", theme),
|
||||||
|
filepath.Join(home, ".themes", theme),
|
||||||
|
filepath.Join("/usr/share/themes", theme),
|
||||||
|
filepath.Join("/usr/local/share/themes", theme),
|
||||||
|
}
|
||||||
|
for _, dir := range candidates {
|
||||||
|
if info, err := os.Stat(dir); err == nil && info.IsDir() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshGTKTheme(mode ColorMode) {
|
||||||
|
theme := mode.GTKTheme()
|
||||||
|
if !gtkThemeInstalled(theme) {
|
||||||
|
log.Infof("Skipping gtk-theme refresh: %s is not installed", theme)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
|
||||||
log.Warnf("Failed to reset gtk-theme: %v", err)
|
log.Warnf("Failed to reset gtk-theme: %v", err)
|
||||||
}
|
}
|
||||||
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", mode.GTKTheme()); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", theme); err != nil {
|
||||||
log.Warnf("Failed to set gtk-theme: %v", err)
|
log.Warnf("Failed to set gtk-theme: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -993,8 +1014,8 @@ func expectColorSchemeEcho(scheme string) {
|
|||||||
// apps reload ~/.config/gtk-4.0 CSS (a gtk-theme flip does not). But apps
|
// apps reload ~/.config/gtk-4.0 CSS (a gtk-theme flip does not). But apps
|
||||||
// following the portal color-scheme (Chromium) can drop the restore signal
|
// following the portal color-scheme (Chromium) can drop the restore signal
|
||||||
// mid-repaint and latch the wrong mode, so this is opt-in.
|
// mid-repaint and latch the wrong mode, so this is opt-in.
|
||||||
func refreshGTK4() {
|
func refreshGTKColorScheme() {
|
||||||
if os.Getenv("DMS_ENABLE_GTK4_REFRESH") != "1" {
|
if os.Getenv("DMS_ENABLE_GTK_REFRESH") != "1" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
|
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
|
||||||
@@ -1012,13 +1033,13 @@ func refreshGTK4() {
|
|||||||
|
|
||||||
expectColorSchemeEcho(toggle)
|
expectColorSchemeEcho(toggle)
|
||||||
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
|
||||||
log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err)
|
log.Warnf("Failed to toggle color-scheme for GTK refresh: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(400 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
expectColorSchemeEcho(current)
|
expectColorSchemeEcho(current)
|
||||||
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil {
|
||||||
log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err)
|
log.Warnf("Failed to restore color-scheme for GTK refresh: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func NewManager() (*Manager, error) {
|
|||||||
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
|
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
|
||||||
m.promptBroker = broker
|
m.promptBroker = broker
|
||||||
|
|
||||||
adapter, err := m.findAdapter()
|
adapter, err := findAdapter(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
m.adapterPath = adapter
|
m.adapterPath = adapter
|
||||||
|
|
||||||
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
|
||||||
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
|
obj := conn.Object(bluezService, dbus.ObjectPath("/"))
|
||||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||||
|
|
||||||
if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
|
if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("%w: %v", ErrNoAdapter, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for path, interfaces := range objects {
|
for path, interfaces := range objects {
|
||||||
@@ -89,7 +89,7 @@ func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("no adapter found")
|
return "", ErrNoAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) initialize() error {
|
func (m *Manager) initialize() error {
|
||||||
@@ -487,6 +487,11 @@ func (m *Manager) StopDiscovery() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetPowered(powered bool) error {
|
func (m *Manager) SetPowered(powered bool) error {
|
||||||
|
if powered {
|
||||||
|
if err := rfkillUnblockBluetooth(); err != nil {
|
||||||
|
log.Debugf("[BluezManager] rfkill unblock failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
obj := m.dbusConn.Object(bluezService, m.adapterPath)
|
obj := m.dbusConn.Object(bluezService, m.adapterPath)
|
||||||
return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
|
return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
func rfkillUnblockBluetooth() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// linux/rfkill.h: struct rfkill_event { __u32 idx; __u8 type; __u8 op; __u8 soft; __u8 hard; },
|
||||||
|
// RFKILL_TYPE_BLUETOOTH=2, RFKILL_OP_CHANGE_ALL=3
|
||||||
|
func rfkillUnblockBluetooth() error {
|
||||||
|
f, err := os.OpenFile("/dev/rfkill", os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var event [8]byte
|
||||||
|
event[4] = 2
|
||||||
|
event[5] = 3
|
||||||
|
_, err = f.Write(event[:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoAdapter = errors.New("no bluetooth adapter found")
|
||||||
|
|
||||||
|
func WaitForAdapter() error {
|
||||||
|
conn, err := dbus.ConnectSystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if err := conn.AddMatchSignal(
|
||||||
|
dbus.WithMatchInterface(objectMgrIface),
|
||||||
|
dbus.WithMatchMember("InterfacesAdded"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signals := make(chan *dbus.Signal, 64)
|
||||||
|
conn.Signal(signals)
|
||||||
|
|
||||||
|
if _, err := findAdapter(conn); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for sig := range signals {
|
||||||
|
if sig == nil || sig.Name != objectMgrIface+".InterfacesAdded" || len(sig.Body) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ifaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := ifaces[adapter1Iface]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("dbus signal stream closed")
|
||||||
|
}
|
||||||
@@ -229,6 +229,9 @@ func (m *Manager) snapshotState() CUPSState {
|
|||||||
func (m *Manager) Subscribe(id string) chan CUPSState {
|
func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||||
ch := make(chan CUPSState, 64)
|
ch := make(chan CUPSState, 64)
|
||||||
|
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
|
defer m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
wasEmpty := true
|
wasEmpty := true
|
||||||
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
wasEmpty = false
|
wasEmpty = false
|
||||||
@@ -237,19 +240,25 @@ func (m *Manager) Subscribe(id string) chan CUPSState {
|
|||||||
|
|
||||||
m.subscribers.Store(id, ch)
|
m.subscribers.Store(id, ch)
|
||||||
|
|
||||||
if wasEmpty && m.subscription != nil {
|
if !wasEmpty || m.subscription == nil {
|
||||||
if err := m.subscription.Start(); err != nil {
|
return ch
|
||||||
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
|
||||||
} else {
|
|
||||||
m.eventWG.Add(1)
|
|
||||||
go m.eventHandler()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := m.subscription.Start(); err != nil {
|
||||||
|
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
m.eventWG.Add(1)
|
||||||
|
go m.eventHandler()
|
||||||
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
|
defer m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
close(val)
|
close(val)
|
||||||
}
|
}
|
||||||
@@ -260,18 +269,22 @@ func (m *Manager) Unsubscribe(id string) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if isEmpty && m.subscription != nil {
|
if !isEmpty || m.subscription == nil {
|
||||||
m.subscription.Stop()
|
return
|
||||||
m.eventWG.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.subscription.Stop()
|
||||||
|
m.eventWG.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
close(m.stopChan)
|
close(m.stopChan)
|
||||||
|
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
if m.subscription != nil {
|
if m.subscription != nil {
|
||||||
m.subscription.Stop()
|
m.subscription.Stop()
|
||||||
}
|
}
|
||||||
|
m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
m.eventWG.Wait()
|
m.eventWG.Wait()
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package cups
|
package cups
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
|
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
|
||||||
@@ -75,6 +78,67 @@ func TestManager_Subscribe(t *testing.T) {
|
|||||||
assert.Equal(t, 0, count)
|
assert.Equal(t, 0, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mirrors the real managers: eventChan guarded by mu, conn/running deliberately
|
||||||
|
// unsynchronized so overlapping Start/Stop trips the race detector
|
||||||
|
type stubSubscription struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
events chan SubscriptionEvent
|
||||||
|
conn *int
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Start() error {
|
||||||
|
if s.running {
|
||||||
|
return errors.New("already running")
|
||||||
|
}
|
||||||
|
s.running = true
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
s.events = make(chan SubscriptionEvent)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
v := 0
|
||||||
|
s.conn = &v
|
||||||
|
*s.conn++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Stop() {
|
||||||
|
if !s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.running = false
|
||||||
|
s.conn = nil
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
close(s.events)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Events() <-chan SubscriptionEvent {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.events
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_SubscribeUnsubscribeRace(t *testing.T) {
|
||||||
|
m := NewTestManager(mocks_cups.NewMockCUPSClientInterface(t), nil)
|
||||||
|
m.subscription = &stubSubscription{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := range 8 {
|
||||||
|
wg.Go(func() {
|
||||||
|
id := fmt.Sprintf("client-%d", i)
|
||||||
|
for range 50 {
|
||||||
|
m.Subscribe(id)
|
||||||
|
m.Unsubscribe(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestManager_Close(t *testing.T) {
|
func TestManager_Close(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ type Manager struct {
|
|||||||
client CUPSClientInterface
|
client CUPSClientInterface
|
||||||
pkHelper PkHelper
|
pkHelper PkHelper
|
||||||
subscription SubscriptionManagerInterface
|
subscription SubscriptionManagerInterface
|
||||||
|
subLifecycleMu sync.Mutex
|
||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
subscribers syncmap.Map[string, chan CUPSState]
|
subscribers syncmap.Map[string, chan CUPSState]
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
|||||||
@@ -116,11 +116,12 @@ func RouteRequest(conn *models.Conn, req models.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "cups.") {
|
if strings.HasPrefix(req.Method, "cups.") {
|
||||||
if cupsManager == nil {
|
mgr, err := ensureCupsManager()
|
||||||
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cups.HandleRequest(conn, req, cupsManager)
|
cups.HandleRequest(conn, req, mgr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
|
||||||
@@ -80,8 +79,8 @@ var geoClientInstance geolocation.Client
|
|||||||
const dbusClientID = "dms-dbus-client"
|
const dbusClientID = "dms-dbus-client"
|
||||||
|
|
||||||
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
||||||
var cupsSubscribers syncmap.Map[string, bool]
|
var cupsMu sync.Mutex
|
||||||
var cupsSubscriberCount atomic.Int32
|
var cupsSubscriberCount int
|
||||||
|
|
||||||
var appPaths = paths.New("danklinux")
|
var appPaths = paths.New("danklinux")
|
||||||
|
|
||||||
@@ -174,17 +173,55 @@ func InitializeAppPickerManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeCupsManager() error {
|
func initializeCupsManagerLocked() (bool, error) {
|
||||||
|
if cupsManager != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
manager, err := cups.NewManager()
|
manager, err := cups.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to initialize cups manager: %v", err)
|
log.Warnf("Failed to initialize cups manager: %v", err)
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cupsManager = manager
|
cupsManager = manager
|
||||||
|
|
||||||
log.Info("CUPS manager initialized")
|
log.Info("CUPS manager initialized")
|
||||||
return nil
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureCupsManager() (*cups.Manager, error) {
|
||||||
|
cupsMu.Lock()
|
||||||
|
created, err := initializeCupsManagerLocked()
|
||||||
|
mgr := cupsManager
|
||||||
|
cupsMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if created {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
}
|
||||||
|
return mgr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cupsAvailable() bool {
|
||||||
|
cupsMu.Lock()
|
||||||
|
defer cupsMu.Unlock()
|
||||||
|
return cupsManager != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseCupsSubscriber() {
|
||||||
|
cupsMu.Lock()
|
||||||
|
cupsSubscriberCount--
|
||||||
|
var mgr *cups.Manager
|
||||||
|
if cupsSubscriberCount == 0 && cupsManager != nil {
|
||||||
|
mgr = cupsManager
|
||||||
|
cupsManager = nil
|
||||||
|
}
|
||||||
|
cupsMu.Unlock()
|
||||||
|
if mgr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Last CUPS subscriber disconnected, shutting down CUPS manager")
|
||||||
|
mgr.Close()
|
||||||
|
notifyCapabilityChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeBrightnessManager() error {
|
func InitializeBrightnessManager() error {
|
||||||
@@ -379,7 +416,7 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "browser")
|
caps = append(caps, "browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if cupsAvailable() {
|
||||||
caps = append(caps, "cups")
|
caps = append(caps, "cups")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +486,7 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "browser")
|
caps = append(caps, "browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if cupsAvailable() {
|
||||||
caps = append(caps, "cups")
|
caps = append(caps, "cups")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,38 +950,31 @@ func handleSubscribe(conn *models.Conn, req models.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldSubscribe("cups") {
|
if shouldSubscribe("cups") {
|
||||||
cupsSubscribers.Store(clientID+"-cups", true)
|
cupsMu.Lock()
|
||||||
count := cupsSubscriberCount.Add(1)
|
cupsSubscriberCount++
|
||||||
|
created, err := initializeCupsManagerLocked()
|
||||||
|
mgr := cupsManager
|
||||||
|
cupsMu.Unlock()
|
||||||
|
|
||||||
if count == 1 {
|
if err != nil {
|
||||||
if err := InitializeCupsManager(); err != nil {
|
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
||||||
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
} else if created {
|
||||||
} else {
|
notifyCapabilityChange()
|
||||||
notifyCapabilityChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if mgr == nil {
|
||||||
|
releaseCupsSubscriber()
|
||||||
|
} else {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
cupsChan := cupsManager.Subscribe(clientID + "-cups")
|
cupsChan := mgr.Subscribe(clientID + "-cups")
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
defer func() {
|
defer func() {
|
||||||
cupsManager.Unsubscribe(clientID + "-cups")
|
mgr.Unsubscribe(clientID + "-cups")
|
||||||
cupsSubscribers.Delete(clientID + "-cups")
|
releaseCupsSubscriber()
|
||||||
count := cupsSubscriberCount.Add(-1)
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
log.Info("Last CUPS subscriber disconnected, shutting down CUPS manager")
|
|
||||||
if cupsManager != nil {
|
|
||||||
cupsManager.Close()
|
|
||||||
cupsManager = nil
|
|
||||||
notifyCapabilityChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
initialState := cupsManager.GetState()
|
initialState := mgr.GetState()
|
||||||
select {
|
select {
|
||||||
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
|
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
@@ -1254,9 +1284,12 @@ func cleanupManagers() {
|
|||||||
if appPickerManager != nil {
|
if appPickerManager != nil {
|
||||||
appPickerManager.Close()
|
appPickerManager.Close()
|
||||||
}
|
}
|
||||||
|
cupsMu.Lock()
|
||||||
if cupsManager != nil {
|
if cupsManager != nil {
|
||||||
cupsManager.Close()
|
cupsManager.Close()
|
||||||
|
cupsManager = nil
|
||||||
}
|
}
|
||||||
|
cupsMu.Unlock()
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
brightnessManager.Close()
|
brightnessManager.Close()
|
||||||
}
|
}
|
||||||
@@ -1635,10 +1668,21 @@ func (s *Server) Serve(printDocs bool) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := InitializeBluezManager(); err != nil {
|
for {
|
||||||
|
err := InitializeBluezManager()
|
||||||
|
if err == nil {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Warnf("Bluez manager unavailable: %v", err)
|
log.Warnf("Bluez manager unavailable: %v", err)
|
||||||
} else {
|
if !errors.Is(err, bluez.ErrNoAdapter) {
|
||||||
notifyCapabilityChange()
|
return
|
||||||
|
}
|
||||||
|
if err := bluez.WaitForAdapter(); err != nil {
|
||||||
|
log.Warnf("Bluetooth adapter watch failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Bluetooth adapter appeared, initializing bluez manager")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ func wrapInTerminal(term, title, shellCmd string, extraArgs []string) []string {
|
|||||||
case "konsole":
|
case "konsole":
|
||||||
argv = []string{term, "-p", "tabtitle=" + title}
|
argv = []string{term, "-p", "tabtitle=" + title}
|
||||||
case "gnome-terminal":
|
case "gnome-terminal":
|
||||||
argv = []string{term, "--title=" + title}
|
// --wait: the factory process otherwise returns immediately
|
||||||
|
argv = []string{term, "--wait", "--title=" + title}
|
||||||
execFlag = "--"
|
execFlag = "--"
|
||||||
default:
|
default:
|
||||||
argv = []string{term}
|
argv = []string{term}
|
||||||
|
|||||||
@@ -412,25 +412,30 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, opts UpgradeOptions) {
|
|||||||
onLine := func(line string) { m.appendLog(line) }
|
onLine := func(line string) { m.appendLog(line) }
|
||||||
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
|
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
|
||||||
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
|
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
|
||||||
code := ErrCodeBackendFailed
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
||||||
code = ErrCodeTimeout
|
m.failCustomUpgrade(ErrCodeTimeout, err)
|
||||||
|
return
|
||||||
case errors.Is(ctx.Err(), context.Canceled):
|
case errors.Is(ctx.Err(), context.Canceled):
|
||||||
code = ErrCodeCancelled
|
m.failCustomUpgrade(ErrCodeCancelled, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
m.mu.Lock()
|
// exit status reflects the trailing `read`, not the update command
|
||||||
m.state.Phase = PhaseError
|
m.appendLog(fmt.Sprintf("Terminal exited early: %v", err))
|
||||||
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
|
|
||||||
m.mu.Unlock()
|
|
||||||
m.markDirty()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.finishSuccessfulUpgrade(false)
|
m.finishSuccessfulUpgrade(false)
|
||||||
m.runRefresh(context.Background(), false)
|
m.runRefresh(context.Background(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) failCustomUpgrade(code ErrorCode, err error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.state.Phase = PhaseError
|
||||||
|
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
|
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
|
||||||
m.appendLog("Upgrade complete.")
|
m.appendLog("Upgrade complete.")
|
||||||
|
|
||||||
|
|||||||
@@ -233,3 +233,27 @@ func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
|
|||||||
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
|
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWrapInTerminal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
term string
|
||||||
|
wantPrefix []string
|
||||||
|
}{
|
||||||
|
{"kitty", []string{"kitty", "--class", "com.danklinux.dms", "-T", "Title"}},
|
||||||
|
{"gnome-terminal", []string{"gnome-terminal", "--wait", "--title=Title"}},
|
||||||
|
{"foot", []string{"foot", "--app-id=com.danklinux.dms", "--title=Title"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := wrapInTerminal(tt.term, "Title", "echo hi", nil)
|
||||||
|
if len(got) < len(tt.wantPrefix) || !reflect.DeepEqual(got[:len(tt.wantPrefix)], tt.wantPrefix) {
|
||||||
|
t.Errorf("wrapInTerminal(%q) = %#v, want prefix %#v", tt.term, got, tt.wantPrefix)
|
||||||
|
}
|
||||||
|
tail := got[len(got)-3:]
|
||||||
|
if tail[0] != "sh" || tail[1] != "-c" {
|
||||||
|
t.Errorf("wrapInTerminal(%q) tail = %#v, want [sh -c <cmd>]", tt.term, tail)
|
||||||
|
}
|
||||||
|
if !strings.Contains(tail[2], "echo hi") {
|
||||||
|
t.Errorf("wrapInTerminal(%q) command %q does not contain shell command", tt.term, tail[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/dankgo/boottimer"
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
@@ -23,9 +24,6 @@ import (
|
|||||||
|
|
||||||
const animKelvinStep = 25
|
const animKelvinStep = 25
|
||||||
|
|
||||||
// Go timers freeze during suspend; cap sleeps so wall-clock deadlines can't be missed.
|
|
||||||
const maxScheduleWait = 5 * time.Minute
|
|
||||||
|
|
||||||
func NewManager(display wlclient.WaylandDisplay, config Config) (*Manager, error) {
|
func NewManager(display wlclient.WaylandDisplay, config Config) (*Manager, error) {
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -752,7 +750,8 @@ func (m *Manager) schedulerLoop() {
|
|||||||
m.post(func() { m.applyCurrentTemp("startup") })
|
m.post(func() { m.applyCurrentTemp("startup") })
|
||||||
}
|
}
|
||||||
|
|
||||||
var timer *time.Timer
|
timer := boottimer.New(24 * time.Hour)
|
||||||
|
defer timer.Stop()
|
||||||
for {
|
for {
|
||||||
m.configMutex.RLock()
|
m.configMutex.RLock()
|
||||||
enabled := m.config.Enabled
|
enabled := m.config.Enabled
|
||||||
@@ -761,31 +760,19 @@ func (m *Manager) schedulerLoop() {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
m.recalcSchedule(now)
|
m.recalcSchedule(now)
|
||||||
|
|
||||||
var waitDur time.Duration
|
waitDur := 24 * time.Hour
|
||||||
if enabled {
|
if enabled {
|
||||||
deadline := m.getNextDeadline(now)
|
deadline := m.getNextDeadline(now)
|
||||||
waitDur = time.Until(deadline)
|
if waitDur = time.Until(deadline); waitDur < time.Second {
|
||||||
switch {
|
|
||||||
case waitDur < time.Second:
|
|
||||||
waitDur = time.Second
|
waitDur = time.Second
|
||||||
case waitDur > maxScheduleWait:
|
|
||||||
waitDur = maxScheduleWait
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
waitDur = 24 * time.Hour
|
|
||||||
}
|
}
|
||||||
|
timer.Reset(waitDur)
|
||||||
if timer != nil {
|
|
||||||
timer.Stop()
|
|
||||||
}
|
|
||||||
timer = time.NewTimer(waitDur)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-m.stopChan:
|
case <-m.stopChan:
|
||||||
timer.Stop()
|
|
||||||
return
|
return
|
||||||
case <-m.updateTrigger:
|
case <-m.updateTrigger:
|
||||||
timer.Stop()
|
|
||||||
m.scheduleMutex.Lock()
|
m.scheduleMutex.Lock()
|
||||||
m.schedule.calcDay = time.Time{}
|
m.schedule.calcDay = time.Time{}
|
||||||
m.scheduleMutex.Unlock()
|
m.scheduleMutex.Unlock()
|
||||||
@@ -1086,14 +1073,11 @@ func (m *Manager) handleResume() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compositor gamma state is unknown after resume; force a resend (#1235)
|
// Compositor gamma state is unknown after resume; force a resend (#1235)
|
||||||
// and re-arm the scheduler timer, which froze during suspend.
|
|
||||||
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
m.outputs.Range(func(_ uint32, out *outputState) bool {
|
||||||
out.lastTemp = 0
|
out.lastTemp = 0
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
m.recalcSchedule(time.Now())
|
|
||||||
m.applyCurrentTemp("resume")
|
m.applyCurrentTemp("resume")
|
||||||
m.triggerUpdate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) triggerUpdate() {
|
func (m *Manager) triggerUpdate() {
|
||||||
|
|||||||
+1
-1
Submodule dank-qml-common updated: 1919595f0d...7cc4564e59
Generated
+3
-3
@@ -3,11 +3,11 @@
|
|||||||
"dank-qml-common": {
|
"dank-qml-common": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784647395,
|
"lastModified": 1785121997,
|
||||||
"narHash": "sha256-kcLSJC8XBzmWoSBc90Zn81qrNsqWMkwQ1m/zZixOw30=",
|
"narHash": "sha256-/MslqFCpjxws8DZqipEKCCTBa0m/SCV3+v1NRT6EuvQ=",
|
||||||
"owner": "AvengeMedia",
|
"owner": "AvengeMedia",
|
||||||
"repo": "dank-qml-common",
|
"repo": "dank-qml-common",
|
||||||
"rev": "1919595f0dde4f5bf5ac0baa1902ff6d070971d5",
|
"rev": "7cc4564e5903a2955fe7da76969f20252cacf9bf",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
inherit version;
|
inherit version;
|
||||||
pname = "dms-shell";
|
pname = "dms-shell";
|
||||||
src = ./core;
|
src = ./core;
|
||||||
vendorHash = "sha256-G0KCUaNwjWfLcSnb68TMXjpB2xxwheXgrBkFEzcSZTc=";
|
vendorHash = "sha256-ZvaOPC92ZFRPqSyLJa2TA9OUKQ3QnWCIMxrnYLGnC58=";
|
||||||
|
|
||||||
subPackages = [ "cmd/dms" ];
|
subPackages = [ "cmd/dms" ];
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,27 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("CacheData")
|
readonly property var log: Log.scoped("CacheData")
|
||||||
|
|
||||||
readonly property int cacheConfigVersion: 1
|
readonly property int cacheConfigVersion: 2
|
||||||
|
|
||||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
|
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
|
||||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||||
|
|
||||||
property bool _loading: false
|
property bool _loading: false
|
||||||
|
property bool _hasLoaded: false
|
||||||
|
property int _loadedCacheVersion: 0
|
||||||
|
|
||||||
|
readonly property var _pinKeys: ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]
|
||||||
|
readonly property var _dataKeys: ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings"].concat(_pinKeys)
|
||||||
|
|
||||||
property string wallpaperLastPath: ""
|
property string wallpaperLastPath: ""
|
||||||
property string profileLastPath: ""
|
property string profileLastPath: ""
|
||||||
|
|
||||||
|
property var brightnessDevicePins: ({})
|
||||||
|
property var wifiNetworkPins: ({})
|
||||||
|
property var bluetoothDevicePins: ({})
|
||||||
|
property var audioInputDevicePins: ({})
|
||||||
|
property var audioOutputDevicePins: ({})
|
||||||
|
|
||||||
property var fileBrowserSettings: ({
|
property var fileBrowserSettings: ({
|
||||||
"wallpaper": {
|
"wallpaper": {
|
||||||
"lastPath": "",
|
"lastPath": "",
|
||||||
@@ -78,8 +89,46 @@ Singleton {
|
|||||||
|
|
||||||
function loadCache() {
|
function loadCache() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
parseCache(cacheFile.text());
|
try {
|
||||||
_loading = false;
|
parseCache(cacheFile.text());
|
||||||
|
} finally {
|
||||||
|
_loading = false;
|
||||||
|
_hasLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(key, value) {
|
||||||
|
if (_dataKeys.indexOf(key) < 0) {
|
||||||
|
log.warn("Unknown cache key:", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root[key] = value;
|
||||||
|
saveCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function migratePins(pins) {
|
||||||
|
if (!pins)
|
||||||
|
return;
|
||||||
|
if (!_hasLoaded)
|
||||||
|
loadCache();
|
||||||
|
if (_loadedCacheVersion >= cacheConfigVersion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let migrated = false;
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
const legacy = pins[key];
|
||||||
|
if (!legacy || Object.keys(legacy).length === 0)
|
||||||
|
continue;
|
||||||
|
if (Object.keys(root[key] || {}).length > 0)
|
||||||
|
continue;
|
||||||
|
root[key] = legacy;
|
||||||
|
migrated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!migrated)
|
||||||
|
return;
|
||||||
|
log.info("Migrated device pins from settings.json");
|
||||||
|
saveCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCache(content) {
|
function parseCache(content) {
|
||||||
@@ -87,6 +136,7 @@ Singleton {
|
|||||||
try {
|
try {
|
||||||
if (content && content.trim()) {
|
if (content && content.trim()) {
|
||||||
const cache = JSON.parse(content);
|
const cache = JSON.parse(content);
|
||||||
|
_loadedCacheVersion = cache.configVersion || 0;
|
||||||
|
|
||||||
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
|
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
|
||||||
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
|
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
|
||||||
@@ -122,6 +172,10 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
root[key] = cache[key] !== undefined ? cache[key] : {};
|
||||||
|
}
|
||||||
|
|
||||||
if (cache.configVersion === undefined) {
|
if (cache.configVersion === undefined) {
|
||||||
migrateFromUndefinedToV1(cache);
|
migrateFromUndefinedToV1(cache);
|
||||||
cleanupUnusedKeys();
|
cleanupUnusedKeys();
|
||||||
@@ -138,12 +192,16 @@ Singleton {
|
|||||||
function saveCache() {
|
function saveCache() {
|
||||||
if (_loading)
|
if (_loading)
|
||||||
return;
|
return;
|
||||||
cacheFile.setText(JSON.stringify({
|
const data = {
|
||||||
"wallpaperLastPath": wallpaperLastPath,
|
"wallpaperLastPath": wallpaperLastPath,
|
||||||
"profileLastPath": profileLastPath,
|
"profileLastPath": profileLastPath,
|
||||||
"fileBrowserSettings": fileBrowserSettings,
|
"fileBrowserSettings": fileBrowserSettings,
|
||||||
"configVersion": cacheConfigVersion
|
"configVersion": cacheConfigVersion
|
||||||
}, null, 2));
|
};
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
data[key] = root[key];
|
||||||
|
}
|
||||||
|
cacheFile.setText(JSON.stringify(data, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateFromUndefinedToV1(cache) {
|
function migrateFromUndefinedToV1(cache) {
|
||||||
@@ -151,7 +209,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupUnusedKeys() {
|
function cleanupUnusedKeys() {
|
||||||
const validKeys = ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings", "configVersion"];
|
const validKeys = _dataKeys.concat(["configVersion"]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = cacheFile.text();
|
const content = cacheFile.text();
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
function clearImageCache() {
|
|
||||||
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
|
|
||||||
Paths.mkdir(Paths.imagecache);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearOldCache(ageInMinutes) {
|
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearCacheForSize(size) {
|
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCacheSize(callback) {
|
|
||||||
Proc.runCommand("cache_size", ["du", "-sm", Paths.stringify(Paths.imagecache)], function (output, exitCode) {
|
|
||||||
const sizeMB = parseInt(output.split("\t")[0]) || 0;
|
|
||||||
callback(sizeMB);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,26 +114,6 @@ function withRevision(descriptor, revision) {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
function withAnimationOffset(descriptor, x, y) {
|
|
||||||
var next = normalize(descriptor);
|
|
||||||
next.animationOffset = {
|
|
||||||
"x": x === undefined ? next.animationOffset.x : _number(x, next.animationOffset.x),
|
|
||||||
"y": y === undefined ? next.animationOffset.y : _number(y, next.animationOffset.y)
|
|
||||||
};
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function withBodyRect(descriptor, x, y, width, height) {
|
|
||||||
var next = normalize(descriptor);
|
|
||||||
next.bodyRect = {
|
|
||||||
"x": x === undefined ? next.bodyRect.x : _number(x, next.bodyRect.x),
|
|
||||||
"y": y === undefined ? next.bodyRect.y : _number(y, next.bodyRect.y),
|
|
||||||
"width": width === undefined ? next.bodyRect.width : Math.max(0, _number(width, next.bodyRect.width)),
|
|
||||||
"height": height === undefined ? next.bodyRect.height : Math.max(0, _number(height, next.bodyRect.height))
|
|
||||||
};
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function same(a, b, threshold) {
|
function same(a, b, threshold) {
|
||||||
if (!a || !b)
|
if (!a || !b)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -162,21 +162,6 @@ function fillBounds(rect, side, seamOverlap, dpr) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function clipEnvelope(rect, side, radii, seamOverlap, dpr) {
|
|
||||||
var fill = fillBounds(rect, side, seamOverlap, dpr);
|
|
||||||
var chrome = chromeBounds(fill, side, radii.start, radii.end, radii.farExtent, dpr);
|
|
||||||
return {
|
|
||||||
"x": chrome.x,
|
|
||||||
"y": chrome.y,
|
|
||||||
"width": chrome.width,
|
|
||||||
"height": chrome.height,
|
|
||||||
"bodyX": snap(fill.x - chrome.x, dpr),
|
|
||||||
"bodyY": snap(fill.y - chrome.y, dpr),
|
|
||||||
"bodyWidth": fill.width,
|
|
||||||
"bodyHeight": fill.height
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function blurRegions(descriptor, rect, radii, dpr) {
|
function blurRegions(descriptor, rect, radii, dpr) {
|
||||||
var side = descriptor.barSide;
|
var side = descriptor.barSide;
|
||||||
var regions = [bodyRect(rect, dpr)];
|
var regions = [bodyRect(rect, dpr)];
|
||||||
@@ -219,14 +204,3 @@ function unionBounds(rects, padding, dpr) {
|
|||||||
"height": Math.max(0, snap(maxY - minY + pad * 2, dpr))
|
"height": Math.max(0, snap(maxY - minY + pad * 2, dpr))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowSourceBounds(descriptor, rect, radii, padding, dpr) {
|
|
||||||
return unionBounds(blurRegions(descriptor, rect, radii, dpr), padding, dpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stableEqual(a, b, dpr) {
|
|
||||||
if (!a || !b)
|
|
||||||
return false;
|
|
||||||
var threshold = 0.5 / (dpr || 1);
|
|
||||||
return Math.abs(a.x - b.x) < threshold && Math.abs(a.y - b.y) < threshold && Math.abs(a.width - b.width) < threshold && Math.abs(a.height - b.height) < threshold;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,16 +44,6 @@ function connectorX(barSide, baseX, bodyWidth, placement, spacing, radius) {
|
|||||||
return barSide === "left" ? s : s - w;
|
return barSide === "left" ? s : s - w;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectorY(barSide, baseY, bodyHeight, placement, spacing, radius) {
|
|
||||||
var s = seamY(barSide, baseY, bodyHeight, placement);
|
|
||||||
var h = connectorHeight(barSide, spacing, radius);
|
|
||||||
if (barSide === "top")
|
|
||||||
return s;
|
|
||||||
if (barSide === "bottom")
|
|
||||||
return s - h;
|
|
||||||
return placement === "left" ? s - h : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Which corner of the connector's bounding rect hosts the concave arc that
|
// Which corner of the connector's bounding rect hosts the concave arc that
|
||||||
// carves into the body. Used for arc-sweep orientation.
|
// carves into the body. Used for arc-sweep orientation.
|
||||||
function arcCorner(barSide, placement) {
|
function arcCorner(barSide, placement) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
.pragma library
|
||||||
|
|
||||||
|
function formatRate(bytesPerSec, gbDecimals) {
|
||||||
|
if (bytesPerSec < 1024)
|
||||||
|
return bytesPerSec.toFixed(0) + " B/s";
|
||||||
|
if (bytesPerSec < 1024 * 1024)
|
||||||
|
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
|
||||||
|
if (bytesPerSec < 1024 * 1024 * 1024)
|
||||||
|
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
|
||||||
|
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(gbDecimals ?? 2) + " GB/s";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (bytes < 1024)
|
||||||
|
return bytes.toFixed(0) + "B";
|
||||||
|
if (bytes < 1024 * 1024)
|
||||||
|
return (bytes / 1024).toFixed(0) + "K";
|
||||||
|
if (bytes < 1024 * 1024 * 1024)
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + "M";
|
||||||
|
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatIsoTime(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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRemaining(ms, zeroText, minText, hText, hmText) {
|
||||||
|
if (ms <= 0)
|
||||||
|
return zeroText;
|
||||||
|
const totalMinutes = Math.ceil(ms / 60000);
|
||||||
|
if (totalMinutes < 60)
|
||||||
|
return minText.arg(totalMinutes);
|
||||||
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
|
const mins = totalMinutes - hours * 60;
|
||||||
|
if (mins === 0)
|
||||||
|
return hText.arg(hours);
|
||||||
|
return hmText.arg(hours).arg(mins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad2(n) {
|
||||||
|
return n < 10 ? "0" + n : "" + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUntil(ts, use24h) {
|
||||||
|
if (!ts)
|
||||||
|
return "";
|
||||||
|
const d = new Date(ts);
|
||||||
|
const hours = d.getHours();
|
||||||
|
const minutes = d.getMinutes();
|
||||||
|
if (use24h)
|
||||||
|
return pad2(hours) + ":" + pad2(minutes);
|
||||||
|
const suffix = hours >= 12 ? "PM" : "AM";
|
||||||
|
const h12 = ((hours + 11) % 12) + 1;
|
||||||
|
return h12 + ":" + pad2(minutes) + " " + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToHistory(arr, val, max) {
|
||||||
|
const newArr = arr.slice();
|
||||||
|
newArr.push(val);
|
||||||
|
if (newArr.length > max)
|
||||||
|
newArr.shift();
|
||||||
|
return newArr;
|
||||||
|
}
|
||||||
@@ -1370,10 +1370,3 @@ function buildDmsAction(baseKey, args) {
|
|||||||
|
|
||||||
return parts.join(" ");
|
return parts.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScreenshotOptions() {
|
|
||||||
return [
|
|
||||||
{ id: "write-to-disk", label: "Save to disk", type: "bool" },
|
|
||||||
{ id: "show-pointer", label: "Show pointer", type: "bool" }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ Singleton {
|
|||||||
|
|
||||||
readonly property url imagecache: `${cache}/imagecache`
|
readonly property url imagecache: `${cache}/imagecache`
|
||||||
|
|
||||||
|
property var iconResolver: null
|
||||||
|
property var desktopIconResolver: null
|
||||||
|
property var trashHandler: null
|
||||||
|
|
||||||
Component.onCompleted: mkdir(imagecache)
|
Component.onCompleted: mkdir(imagecache)
|
||||||
|
|
||||||
function stringify(path: url): string {
|
function stringify(path: url): string {
|
||||||
@@ -88,7 +92,7 @@ Singleton {
|
|||||||
function themedIconPath(name: string): string {
|
function themedIconPath(name: string): string {
|
||||||
if (!name)
|
if (!name)
|
||||||
return "";
|
return "";
|
||||||
const themed = (typeof IconThemeService !== "undefined") ? IconThemeService.resolve(name) : "";
|
const themed = iconResolver ? iconResolver(name) : "";
|
||||||
if (themed)
|
if (themed)
|
||||||
return themed;
|
return themed;
|
||||||
return Quickshell.iconPath(name, true);
|
return Quickshell.iconPath(name, true);
|
||||||
@@ -105,11 +109,15 @@ Singleton {
|
|||||||
return moddedId;
|
return moddedId;
|
||||||
return themedIconPath(moddedId);
|
return themedIconPath(moddedId);
|
||||||
}
|
}
|
||||||
return themedIconPath(iconName) || DesktopService.resolveIconPath(iconName);
|
if (!desktopIconResolver)
|
||||||
|
return themedIconPath(iconName);
|
||||||
|
return themedIconPath(iconName) || desktopIconResolver(iconName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function trashPath(path: string, callback): void {
|
function trashPath(path: string, callback): void {
|
||||||
TrashService.trashPath(path, callback);
|
if (!trashHandler)
|
||||||
|
return;
|
||||||
|
trashHandler(path, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyPathToClipboard(path: string): void {
|
function copyPathToClipboard(path: string): void {
|
||||||
@@ -125,7 +133,7 @@ Singleton {
|
|||||||
return toFileUrl(expandTilde(target));
|
return toFileUrl(expandTilde(target));
|
||||||
if (target.startsWith("file://"))
|
if (target.startsWith("file://"))
|
||||||
return target;
|
return target;
|
||||||
const themed = (typeof IconThemeService !== "undefined") ? IconThemeService.resolve(target) : "";
|
const themed = iconResolver ? iconResolver(target) : "";
|
||||||
if (themed)
|
if (themed)
|
||||||
return themed;
|
return themed;
|
||||||
return "image://icon/" + target;
|
return "image://icon/" + target;
|
||||||
@@ -148,7 +156,9 @@ Singleton {
|
|||||||
if (icon && icon !== "")
|
if (icon && icon !== "")
|
||||||
return icon;
|
return icon;
|
||||||
|
|
||||||
return DesktopService.resolveIconPath(appId);
|
if (!desktopIconResolver)
|
||||||
|
return "";
|
||||||
|
return desktopIconResolver(appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppName(appId: string, desktopEntry: var): string {
|
function getAppName(appId: string, desktopEntry: var): string {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.pragma library
|
||||||
|
|
||||||
|
function findParentFlickable(item) {
|
||||||
|
while (item) {
|
||||||
|
if (item.hasOwnProperty("contentY") && item.hasOwnProperty("contentItem"))
|
||||||
|
return item;
|
||||||
|
item = item.parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSettings(item) {
|
||||||
|
while (item) {
|
||||||
|
if (item.saveValue !== undefined && item.loadValue !== undefined)
|
||||||
|
return item;
|
||||||
|
item = item.parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePinList(value) {
|
||||||
|
if (Array.isArray(value))
|
||||||
|
return value.filter(v => v);
|
||||||
|
if (typeof value === "string" && value.length > 0)
|
||||||
|
return [value];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ Singleton {
|
|||||||
|
|
||||||
readonly property int sessionConfigVersion: 3
|
readonly property int sessionConfigVersion: 3
|
||||||
|
|
||||||
|
signal loaded
|
||||||
|
signal brightnessDisplayHintChanged(string deviceName)
|
||||||
|
signal loadErrorOccurred(string file, string message)
|
||||||
|
|
||||||
property bool _parseError: false
|
property bool _parseError: false
|
||||||
property bool _hasLoaded: false
|
property bool _hasLoaded: false
|
||||||
property bool _isReadOnly: false
|
property bool _isReadOnly: false
|
||||||
@@ -224,6 +228,7 @@ Singleton {
|
|||||||
property string niriOverviewLastMode: "apps"
|
property string niriOverviewLastMode: "apps"
|
||||||
property string settingsSidebarExpandedIds: ","
|
property string settingsSidebarExpandedIds: ","
|
||||||
property string settingsSidebarCollapsedIds: ","
|
property string settingsSidebarCollapsedIds: ","
|
||||||
|
property bool showConfigReloadToast: true
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -275,15 +280,14 @@ Singleton {
|
|||||||
if (typeof Theme !== "undefined")
|
if (typeof Theme !== "undefined")
|
||||||
Theme.generateSystemThemesFromCurrentTheme();
|
Theme.generateSystemThemesFromCurrentTheme();
|
||||||
|
|
||||||
if (typeof WallpaperCyclingService !== "undefined")
|
loaded();
|
||||||
WallpaperCyclingService.updateCyclingState();
|
|
||||||
|
|
||||||
_checkSessionWritable();
|
_checkSessionWritable();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_parseError = true;
|
_parseError = true;
|
||||||
const msg = e.message;
|
const msg = e.message;
|
||||||
log.error("Failed to parse session.json - file will not be overwritten.");
|
log.error("Failed to parse session.json - file will not be overwritten.");
|
||||||
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg));
|
Qt.callLater(() => loadErrorOccurred("session.json", msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,13 +361,12 @@ Singleton {
|
|||||||
if (typeof Theme !== "undefined")
|
if (typeof Theme !== "undefined")
|
||||||
Theme.generateSystemThemesFromCurrentTheme();
|
Theme.generateSystemThemesFromCurrentTheme();
|
||||||
|
|
||||||
if (typeof WallpaperCyclingService !== "undefined")
|
loaded();
|
||||||
WallpaperCyclingService.updateCyclingState();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_parseError = true;
|
_parseError = true;
|
||||||
const msg = e.message;
|
const msg = e.message;
|
||||||
log.error("Failed to parse session.json - file will not be overwritten.");
|
log.error("Failed to parse session.json - file will not be overwritten.");
|
||||||
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg));
|
Qt.callLater(() => loadErrorOccurred("session.json", msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,11 +467,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWallpaperPath(path) {
|
|
||||||
wallpaperPath = path;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWallpaper(imagePath) {
|
function setWallpaper(imagePath) {
|
||||||
wallpaperPath = imagePath;
|
wallpaperPath = imagePath;
|
||||||
if (perModeWallpaper) {
|
if (perModeWallpaper) {
|
||||||
@@ -847,11 +845,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNightModeLocationProvider(provider) {
|
|
||||||
nightModeLocationProvider = provider;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setThemeModeAutoEnabled(enabled) {
|
function setThemeModeAutoEnabled(enabled) {
|
||||||
themeModeAutoEnabled = enabled;
|
themeModeAutoEnabled = enabled;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -940,10 +933,6 @@ Singleton {
|
|||||||
setBarPinnedApps(currentPinned);
|
setBarPinnedApps(currentPinned);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isBarPinnedApp(appId) {
|
|
||||||
return appId && barPinnedApps.indexOf(appId) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideTrayId(trayId) {
|
function hideTrayId(trayId) {
|
||||||
if (!trayId)
|
if (!trayId)
|
||||||
return;
|
return;
|
||||||
@@ -1006,11 +995,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLaunchPrefix(prefix) {
|
|
||||||
launchPrefix = prefix;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLastBrightnessDevice(device) {
|
function setLastBrightnessDevice(device) {
|
||||||
lastBrightnessDevice = device;
|
lastBrightnessDevice = device;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1025,10 +1009,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
brightnessExponentialDevices = newSettings;
|
brightnessExponentialDevices = newSettings;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
brightnessDisplayHintChanged(deviceName);
|
||||||
if (typeof DisplayService !== "undefined") {
|
|
||||||
DisplayService.updateDeviceBrightnessDisplay(deviceName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBrightnessExponential(deviceName) {
|
function getBrightnessExponential(deviceName) {
|
||||||
@@ -1042,10 +1023,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBrightnessUserSetValue(deviceName) {
|
|
||||||
return brightnessUserSetValues[deviceName];
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearBrightnessUserSetValue(deviceName) {
|
function clearBrightnessUserSetValue(deviceName) {
|
||||||
var newValues = Object.assign({}, brightnessUserSetValues);
|
var newValues = Object.assign({}, brightnessUserSetValues);
|
||||||
delete newValues[deviceName];
|
delete newValues[deviceName];
|
||||||
@@ -1069,21 +1046,6 @@ Singleton {
|
|||||||
return value !== undefined ? value : 1.2;
|
return value !== undefined ? value : 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSelectedGpuIndex(index) {
|
|
||||||
selectedGpuIndex = index;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNvidiaGpuTempEnabled(enabled) {
|
|
||||||
nvidiaGpuTempEnabled = enabled;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNonNvidiaGpuTempEnabled(enabled) {
|
|
||||||
nonNvidiaGpuTempEnabled = enabled;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setEnabledGpuPciIds(pciIds) {
|
function setEnabledGpuPciIds(pciIds) {
|
||||||
enabledGpuPciIds = pciIds;
|
enabledGpuPciIds = pciIds;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1199,15 +1161,6 @@ Singleton {
|
|||||||
return deviceMaxVolumes[nodeName] ?? 100;
|
return deviceMaxVolumes[nodeName] ?? 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDeviceMaxVolume(nodeName) {
|
|
||||||
if (!nodeName)
|
|
||||||
return;
|
|
||||||
const updated = Object.assign({}, deviceMaxVolumes);
|
|
||||||
delete updated[nodeName];
|
|
||||||
deviceMaxVolumes = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLocale() {
|
function updateLocale() {
|
||||||
if (!locale) {
|
if (!locale) {
|
||||||
I18n._pickTranslation();
|
I18n._pickTranslation();
|
||||||
@@ -1271,12 +1224,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearLauncherHistory() {
|
|
||||||
launcherLastQuery = "";
|
|
||||||
launcherSearchHistory = [];
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAppDrawerLastMode(mode) {
|
function setAppDrawerLastMode(mode) {
|
||||||
appDrawerLastMode = mode;
|
appDrawerLastMode = mode;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1416,141 +1363,4 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
|
||||||
target: "wallpaper"
|
|
||||||
|
|
||||||
function get(): string {
|
|
||||||
if (root.perMonitorWallpaper) {
|
|
||||||
return "ERROR: Per-monitor mode enabled. Use getFor(screenName) instead.";
|
|
||||||
}
|
|
||||||
return root.wallpaperPath || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function set(path: string): string {
|
|
||||||
if (root.perMonitorWallpaper) {
|
|
||||||
return "ERROR: Per-monitor mode enabled. Use setFor(screenName, path) instead.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
return "ERROR: No path provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path;
|
|
||||||
|
|
||||||
try {
|
|
||||||
root.setWallpaper(absolutePath);
|
|
||||||
return "SUCCESS: Wallpaper set to " + absolutePath;
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to set wallpaper: " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear(): string {
|
|
||||||
root.setWallpaper("");
|
|
||||||
root.setPerMonitorWallpaper(false);
|
|
||||||
root.monitorWallpapers = {};
|
|
||||||
root.saveSettings();
|
|
||||||
return "SUCCESS: All wallpapers cleared";
|
|
||||||
}
|
|
||||||
|
|
||||||
function next(): string {
|
|
||||||
if (root.perMonitorWallpaper) {
|
|
||||||
return "ERROR: Per-monitor mode enabled. Use nextFor(screenName) instead.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.wallpaperPath) {
|
|
||||||
return "ERROR: No wallpaper set";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
WallpaperCyclingService.cycleNextManually();
|
|
||||||
return "SUCCESS: Cycling to next wallpaper";
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to cycle wallpaper: " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prev(): string {
|
|
||||||
if (root.perMonitorWallpaper) {
|
|
||||||
return "ERROR: Per-monitor mode enabled. Use prevFor(screenName) instead.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.wallpaperPath) {
|
|
||||||
return "ERROR: No wallpaper set";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
WallpaperCyclingService.cyclePrevManually();
|
|
||||||
return "SUCCESS: Cycling to previous wallpaper";
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to cycle wallpaper: " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFor(screenName: string): string {
|
|
||||||
if (!screenName) {
|
|
||||||
return "ERROR: No screen name provided";
|
|
||||||
}
|
|
||||||
return root.getMonitorWallpaper(screenName) || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFor(screenName: string, path: string): string {
|
|
||||||
if (!screenName) {
|
|
||||||
return "ERROR: No screen name provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
return "ERROR: No path provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!root.perMonitorWallpaper) {
|
|
||||||
root.setPerMonitorWallpaper(true);
|
|
||||||
}
|
|
||||||
root.setMonitorWallpaper(screenName, absolutePath);
|
|
||||||
return "SUCCESS: Wallpaper set for " + screenName + " to " + absolutePath;
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to set wallpaper for " + screenName + ": " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextFor(screenName: string): string {
|
|
||||||
if (!screenName) {
|
|
||||||
return "ERROR: No screen name provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentWallpaper = root.getMonitorWallpaper(screenName);
|
|
||||||
if (!currentWallpaper) {
|
|
||||||
return "ERROR: No wallpaper set for " + screenName;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
WallpaperCyclingService.cycleNextForMonitor(screenName);
|
|
||||||
return "SUCCESS: Cycling to next wallpaper for " + screenName;
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prevFor(screenName: string): string {
|
|
||||||
if (!screenName) {
|
|
||||||
return "ERROR: No screen name provided";
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentWallpaper = root.getMonitorWallpaper(screenName);
|
|
||||||
if (!currentWallpaper) {
|
|
||||||
return "ERROR: No wallpaper set for " + screenName;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
WallpaperCyclingService.cyclePrevForMonitor(screenName);
|
|
||||||
return "SUCCESS: Cycling to previous wallpaper for " + screenName;
|
|
||||||
} catch (e) {
|
|
||||||
return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("SettingsData")
|
readonly property var log: Log.scoped("SettingsData")
|
||||||
|
|
||||||
readonly property int settingsConfigVersion: 12
|
readonly property int settingsConfigVersion: 13
|
||||||
|
|
||||||
enum Position {
|
enum Position {
|
||||||
Top,
|
Top,
|
||||||
@@ -184,6 +184,24 @@ Singleton {
|
|||||||
property int mangoLayoutRadiusOverride: -1
|
property int mangoLayoutRadiusOverride: -1
|
||||||
property int mangoLayoutBorderSize: -1
|
property int mangoLayoutBorderSize: -1
|
||||||
property bool mangoTrackpadNaturalScrolling: true
|
property bool mangoTrackpadNaturalScrolling: true
|
||||||
|
property string mouseAccelProfile: "default"
|
||||||
|
property real mouseAccelSpeed: 0.0
|
||||||
|
property bool mouseLeftHanded: false
|
||||||
|
property bool mouseMiddleEmulation: false
|
||||||
|
property bool mouseNaturalScroll: false
|
||||||
|
property real mouseScrollFactor: 1.0
|
||||||
|
property string mouseScrollMethod: "default"
|
||||||
|
property string touchpadAccelProfile: "default"
|
||||||
|
property real touchpadAccelSpeed: 0.0
|
||||||
|
property bool touchpadDisableOnExternalMouse: false
|
||||||
|
property bool touchpadDisableWhileTyping: true
|
||||||
|
property bool touchpadDragLock: false
|
||||||
|
property bool touchpadMiddleEmulation: false
|
||||||
|
property bool touchpadNaturalScroll: true
|
||||||
|
property real touchpadScrollFactor: 1.0
|
||||||
|
property string touchpadScrollMethod: "default"
|
||||||
|
property bool touchpadTapAndDrag: true
|
||||||
|
property bool touchpadTapToClick: true
|
||||||
|
|
||||||
property int firstDayOfWeek: -1
|
property int firstDayOfWeek: -1
|
||||||
property bool showWeekNumber: false
|
property bool showWeekNumber: false
|
||||||
@@ -694,16 +712,23 @@ Singleton {
|
|||||||
property bool notepadUseCompositorGap: false
|
property bool notepadUseCompositorGap: false
|
||||||
property int notepadEdgeGap: 0
|
property int notepadEdgeGap: 0
|
||||||
|
|
||||||
|
property string activeCompositor: ""
|
||||||
|
|
||||||
// Compositor layout gap when enabled and available, else the manual value.
|
// Compositor layout gap when enabled and available, else the manual value.
|
||||||
readonly property int notepadEffectiveEdgeGap: {
|
readonly property int notepadEffectiveEdgeGap: {
|
||||||
if (notepadUseCompositorGap) {
|
if (notepadUseCompositorGap) {
|
||||||
var g = -1;
|
var g = -1;
|
||||||
if (CompositorService.isNiri)
|
switch (activeCompositor) {
|
||||||
|
case "niri":
|
||||||
g = niriLayoutGapsOverride;
|
g = niriLayoutGapsOverride;
|
||||||
else if (CompositorService.isHyprland)
|
break;
|
||||||
|
case "hyprland":
|
||||||
g = hyprlandLayoutGapsOverride;
|
g = hyprlandLayoutGapsOverride;
|
||||||
else if (CompositorService.isMango)
|
break;
|
||||||
|
case "mango":
|
||||||
g = mangoLayoutGapsOverride;
|
g = mangoLayoutGapsOverride;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (g >= 0)
|
if (g >= 0)
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
@@ -772,11 +797,6 @@ Singleton {
|
|||||||
property bool fadeToDpmsEnabled: true
|
property bool fadeToDpmsEnabled: true
|
||||||
property int fadeToDpmsGracePeriod: 5
|
property int fadeToDpmsGracePeriod: 5
|
||||||
property string launchPrefix: ""
|
property string launchPrefix: ""
|
||||||
property var brightnessDevicePins: ({})
|
|
||||||
property var wifiNetworkPins: ({})
|
|
||||||
property var bluetoothDevicePins: ({})
|
|
||||||
property var audioInputDevicePins: ({})
|
|
||||||
property var audioOutputDevicePins: ({})
|
|
||||||
|
|
||||||
property bool gtkThemingEnabled: false
|
property bool gtkThemingEnabled: false
|
||||||
property bool qtThemingEnabled: false
|
property bool qtThemingEnabled: false
|
||||||
@@ -916,6 +936,7 @@ Singleton {
|
|||||||
property int notificationTimeoutLow: 5000
|
property int notificationTimeoutLow: 5000
|
||||||
property int notificationTimeoutNormal: 5000
|
property int notificationTimeoutNormal: 5000
|
||||||
property int notificationTimeoutCritical: 0
|
property int notificationTimeoutCritical: 0
|
||||||
|
property bool notificationIgnoreAppTimeout: false
|
||||||
property bool notificationCompactMode: false
|
property bool notificationCompactMode: false
|
||||||
property bool notificationShowTimeoutBar: false
|
property bool notificationShowTimeoutBar: false
|
||||||
property bool notificationDedupeEnabled: true
|
property bool notificationDedupeEnabled: true
|
||||||
@@ -1112,44 +1133,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemMonitorVariants() {
|
|
||||||
return systemMonitorVariants || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSystemMonitorVariant(name, config) {
|
|
||||||
const id = "sysmon_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
|
||||||
const variant = {
|
|
||||||
id: id,
|
|
||||||
name: name,
|
|
||||||
config: config || getDefaultSystemMonitorConfig()
|
|
||||||
};
|
|
||||||
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
|
||||||
variants.push(variant);
|
|
||||||
systemMonitorVariants = variants;
|
|
||||||
saveSettings();
|
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSystemMonitorVariant(variantId, updates) {
|
|
||||||
const variants = JSON.parse(JSON.stringify(systemMonitorVariants || []));
|
|
||||||
const idx = variants.findIndex(v => v.id === variantId);
|
|
||||||
if (idx === -1)
|
|
||||||
return;
|
|
||||||
Object.assign(variants[idx], updates);
|
|
||||||
systemMonitorVariants = variants;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSystemMonitorVariant(variantId) {
|
|
||||||
const variants = (systemMonitorVariants || []).filter(v => v.id !== variantId);
|
|
||||||
systemMonitorVariants = variants;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSystemMonitorVariant(variantId) {
|
|
||||||
return (systemMonitorVariants || []).find(v => v.id === variantId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultSystemMonitorConfig() {
|
function getDefaultSystemMonitorConfig() {
|
||||||
return {
|
return {
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
@@ -1290,70 +1273,6 @@ Singleton {
|
|||||||
return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || null;
|
return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDesktopWidgetInstancesOfType(widgetType) {
|
|
||||||
return (desktopWidgetInstances || []).filter(inst => inst.widgetType === widgetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEnabledDesktopWidgetInstances() {
|
|
||||||
return (desktopWidgetInstances || []).filter(inst => inst.enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveDesktopWidgetInstance(instanceId, direction) {
|
|
||||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
|
||||||
const idx = instances.findIndex(inst => inst.id === instanceId);
|
|
||||||
if (idx === -1)
|
|
||||||
return false;
|
|
||||||
const targetIdx = direction === "up" ? idx - 1 : idx + 1;
|
|
||||||
if (targetIdx < 0 || targetIdx >= instances.length)
|
|
||||||
return false;
|
|
||||||
const temp = instances[idx];
|
|
||||||
instances[idx] = instances[targetIdx];
|
|
||||||
instances[targetIdx] = temp;
|
|
||||||
desktopWidgetInstances = instances;
|
|
||||||
saveSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reorderDesktopWidgetInstance(instanceId, newIndex) {
|
|
||||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
|
||||||
const idx = instances.findIndex(inst => inst.id === instanceId);
|
|
||||||
if (idx === -1 || newIndex < 0 || newIndex >= instances.length)
|
|
||||||
return false;
|
|
||||||
const [item] = instances.splice(idx, 1);
|
|
||||||
instances.splice(newIndex, 0, item);
|
|
||||||
desktopWidgetInstances = instances;
|
|
||||||
saveSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reorderDesktopWidgetInstanceInGroup(instanceId, groupId, newIndexInGroup) {
|
|
||||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
|
||||||
const groups = desktopWidgetGroups || [];
|
|
||||||
const groupMatches = inst => {
|
|
||||||
if (groupId === null)
|
|
||||||
return !inst.group || !groups.some(g => g.id === inst.group);
|
|
||||||
return inst.group === groupId;
|
|
||||||
};
|
|
||||||
const groupInstances = instances.filter(groupMatches);
|
|
||||||
const currentGroupIdx = groupInstances.findIndex(inst => inst.id === instanceId);
|
|
||||||
if (currentGroupIdx === -1 || currentGroupIdx === newIndexInGroup)
|
|
||||||
return false;
|
|
||||||
if (newIndexInGroup < 0 || newIndexInGroup >= groupInstances.length)
|
|
||||||
return false;
|
|
||||||
const globalIdx = instances.findIndex(inst => inst.id === instanceId);
|
|
||||||
if (globalIdx === -1)
|
|
||||||
return false;
|
|
||||||
const [item] = instances.splice(globalIdx, 1);
|
|
||||||
const targetInstance = groupInstances[newIndexInGroup];
|
|
||||||
let targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id);
|
|
||||||
if (newIndexInGroup > currentGroupIdx)
|
|
||||||
targetGlobalIdx++;
|
|
||||||
instances.splice(targetGlobalIdx, 0, item);
|
|
||||||
desktopWidgetInstances = instances;
|
|
||||||
saveSettings();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) {
|
function moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) {
|
||||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
||||||
const groups = desktopWidgetGroups || [];
|
const groups = desktopWidgetGroups || [];
|
||||||
@@ -1419,22 +1338,14 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDesktopWidgetGroup(groupId) {
|
|
||||||
return (desktopWidgetGroups || []).find(g => g.id === groupId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDesktopWidgetInstancesByGroup(groupId) {
|
|
||||||
return (desktopWidgetInstances || []).filter(inst => inst.group === groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUngroupedDesktopWidgetInstances() {
|
|
||||||
return (desktopWidgetInstances || []).filter(inst => !inst.group);
|
|
||||||
}
|
|
||||||
|
|
||||||
signal forceDankBarLayoutRefresh
|
signal forceDankBarLayoutRefresh
|
||||||
signal forceDockLayoutRefresh
|
signal forceDockLayoutRefresh
|
||||||
signal widgetDataChanged
|
signal widgetDataChanged
|
||||||
signal workspaceIconsUpdated
|
signal workspaceIconsUpdated
|
||||||
|
signal compositorLayoutRefreshNeeded(bool frame)
|
||||||
|
signal compositorInputRefreshNeeded
|
||||||
|
signal compositorCursorRefreshNeeded
|
||||||
|
signal notificationPopupsInvalidated
|
||||||
|
|
||||||
function refreshAuthAvailability() {
|
function refreshAuthAvailability() {
|
||||||
Processes.detectAuthCapabilities();
|
Processes.detectAuthCapabilities();
|
||||||
@@ -1469,24 +1380,15 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateCompositorLayout() {
|
function updateCompositorLayout() {
|
||||||
if (typeof CompositorService === "undefined")
|
compositorLayoutRefreshNeeded(false);
|
||||||
return;
|
}
|
||||||
if (CompositorService.isNiri && typeof NiriService !== "undefined")
|
|
||||||
NiriService.generateNiriLayoutConfig();
|
function updateCompositorInput() {
|
||||||
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
compositorInputRefreshNeeded();
|
||||||
HyprlandService.generateLayoutConfig();
|
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined")
|
|
||||||
MangoService.generateLayoutConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFrameCompositorLayout() {
|
function updateFrameCompositorLayout() {
|
||||||
// Generate before begin() so compositor readiness is already pending at transitionRequested
|
compositorLayoutRefreshNeeded(true);
|
||||||
if (typeof CompositorService !== "undefined") {
|
|
||||||
if (CompositorService.isNiri && typeof NiriService !== "undefined")
|
|
||||||
NiriService.generateNiriLayoutConfig(true);
|
|
||||||
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined")
|
|
||||||
HyprlandService.generateLayoutConfig(true);
|
|
||||||
}
|
|
||||||
FrameTransitionState.begin();
|
FrameTransitionState.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1707,6 +1609,7 @@ Singleton {
|
|||||||
"applyStoredTheme": applyStoredTheme,
|
"applyStoredTheme": applyStoredTheme,
|
||||||
"regenSystemThemes": regenSystemThemes,
|
"regenSystemThemes": regenSystemThemes,
|
||||||
"updateCompositorLayout": updateCompositorLayout,
|
"updateCompositorLayout": updateCompositorLayout,
|
||||||
|
"updateCompositorInput": updateCompositorInput,
|
||||||
"applyStoredIconTheme": applyStoredIconTheme,
|
"applyStoredIconTheme": applyStoredIconTheme,
|
||||||
"updateBarConfigs": updateBarConfigs,
|
"updateBarConfigs": updateBarConfigs,
|
||||||
"updateCompositorCursor": updateCompositorCursor,
|
"updateCompositorCursor": updateCompositorCursor,
|
||||||
@@ -1730,6 +1633,7 @@ Singleton {
|
|||||||
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
|
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
|
||||||
|
|
||||||
const oldVersion = obj?.configVersion ?? 0;
|
const oldVersion = obj?.configVersion ?? 0;
|
||||||
|
const legacyPins = oldVersion < 13 ? Store.extractPins(obj) : null;
|
||||||
if (oldVersion < settingsConfigVersion) {
|
if (oldVersion < settingsConfigVersion) {
|
||||||
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
|
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
|
||||||
if (migrated) {
|
if (migrated) {
|
||||||
@@ -1737,6 +1641,8 @@ Singleton {
|
|||||||
obj = migrated;
|
obj = migrated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (legacyPins)
|
||||||
|
Qt.callLater(() => CacheData.migratePins(legacyPins));
|
||||||
|
|
||||||
if (obj?.lockScreenActiveMonitor !== undefined) {
|
if (obj?.lockScreenActiveMonitor !== undefined) {
|
||||||
var oldVal = obj.lockScreenActiveMonitor;
|
var oldVal = obj.lockScreenActiveMonitor;
|
||||||
@@ -2135,14 +2041,6 @@ Singleton {
|
|||||||
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
|
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEffectiveClockDateFormat() {
|
|
||||||
return clockDateFormat && clockDateFormat.length > 0 ? clockDateFormat : "ddd d";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEffectiveLockDateFormat() {
|
|
||||||
return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeListModels() {
|
function initializeListModels() {
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||||
if (defaultBar) {
|
if (defaultBar) {
|
||||||
@@ -2155,39 +2053,6 @@ Singleton {
|
|||||||
widgetDataChanged();
|
widgetDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasNamedWorkspaces() {
|
|
||||||
if (typeof NiriService === "undefined" || !CompositorService.isNiri)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
||||||
var ws = NiriService.allWorkspaces[i];
|
|
||||||
if (ws.name && ws.name.trim() !== "")
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNamedWorkspaces() {
|
|
||||||
var namedWorkspaces = [];
|
|
||||||
if (typeof NiriService === "undefined" || !CompositorService.isNiri)
|
|
||||||
return namedWorkspaces;
|
|
||||||
|
|
||||||
for (const ws of NiriService.allWorkspaces) {
|
|
||||||
if (ws.name && ws.name.trim() !== "") {
|
|
||||||
namedWorkspaces.push(ws.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return namedWorkspaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPopupYPosition(barHeight) {
|
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
|
||||||
const gothOffset = defaultBar?.gothCornersEnabled ? Theme.cornerRadius : 0;
|
|
||||||
const spacing = defaultBar?.spacing ?? 4;
|
|
||||||
const bottomGap = defaultBar?.bottomGap ?? 0;
|
|
||||||
return barHeight + spacing + bottomGap - gothOffset + Theme.popupDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
|
function getPopupTriggerPosition(pos, screen, barThickness, widgetWidth, barSpacing, barPosition, barConfig) {
|
||||||
const relativeX = pos.x;
|
const relativeX = pos.x;
|
||||||
const relativeY = pos.y;
|
const relativeY = pos.y;
|
||||||
@@ -2481,51 +2346,10 @@ Singleton {
|
|||||||
updateBarConfigs();
|
updateBarConfigs();
|
||||||
|
|
||||||
if (positionChanged) {
|
if (positionChanged) {
|
||||||
NotificationService.dismissAllPopups();
|
notificationPopupsInvalidated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkBarCollisions(barId) {
|
|
||||||
const bar = getBarConfig(barId);
|
|
||||||
if (!bar || !bar.enabled)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
const conflicts = [];
|
|
||||||
const enabledBars = getEnabledBarConfigs();
|
|
||||||
|
|
||||||
for (var i = 0; i < enabledBars.length; i++) {
|
|
||||||
const other = enabledBars[i];
|
|
||||||
if (other.id === barId)
|
|
||||||
continue;
|
|
||||||
const samePosition = bar.position === other.position;
|
|
||||||
if (!samePosition)
|
|
||||||
continue;
|
|
||||||
const barScreens = bar.screenPreferences || ["all"];
|
|
||||||
const otherScreens = other.screenPreferences || ["all"];
|
|
||||||
|
|
||||||
const hasAll = barScreens.includes("all") || otherScreens.includes("all");
|
|
||||||
if (hasAll) {
|
|
||||||
conflicts.push({
|
|
||||||
"barId": other.id,
|
|
||||||
"barName": other.name,
|
|
||||||
"reason": "Same position on all screens"
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlapping = barScreens.some(screen => otherScreens.includes(screen));
|
|
||||||
if (overlapping) {
|
|
||||||
conflicts.push({
|
|
||||||
"barId": other.id,
|
|
||||||
"barName": other.name,
|
|
||||||
"reason": "Same position on overlapping screens"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return conflicts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteBarConfig(barId) {
|
function deleteBarConfig(barId) {
|
||||||
if (barId === "default")
|
if (barId === "default")
|
||||||
return;
|
return;
|
||||||
@@ -2674,38 +2498,6 @@ Singleton {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFrameFilteredScreens() {
|
|
||||||
var prefs = frameScreenPreferences || ["all"];
|
|
||||||
if (!prefs || prefs.length === 0 || prefs.includes("all")) {
|
|
||||||
return Quickshell.screens;
|
|
||||||
}
|
|
||||||
return Quickshell.screens.filter(screen => isScreenInPreferences(screen, prefs));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveBarEdgeForScreen(screen) {
|
|
||||||
if (!screen)
|
|
||||||
return "";
|
|
||||||
for (var i = 0; i < barConfigs.length; i++) {
|
|
||||||
var bc = barConfigs[i];
|
|
||||||
if (!bc.enabled)
|
|
||||||
continue;
|
|
||||||
var prefs = bc.screenPreferences || ["all"];
|
|
||||||
if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs))
|
|
||||||
continue;
|
|
||||||
switch (bc.position ?? 0) {
|
|
||||||
case SettingsData.Position.Top:
|
|
||||||
return "top";
|
|
||||||
case SettingsData.Position.Bottom:
|
|
||||||
return "bottom";
|
|
||||||
case SettingsData.Position.Left:
|
|
||||||
return "left";
|
|
||||||
case SettingsData.Position.Right:
|
|
||||||
return "right";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveBarEdgesForScreen(screen) {
|
function getActiveBarEdgesForScreen(screen) {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return [];
|
return [];
|
||||||
@@ -2735,51 +2527,48 @@ Singleton {
|
|||||||
return edges;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function frameEdgeInsetForSide(screen, side) {
|
function getOverlayBarEdgesForScreen(screen) {
|
||||||
if (!frameEnabled || !screen)
|
|
||||||
return 0;
|
|
||||||
const edges = getActiveBarEdgesForScreen(screen);
|
|
||||||
return edges.includes(side) ? frameBarSize : frameThickness;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveBarThicknessForScreen(screen) {
|
|
||||||
if (frameEnabled)
|
|
||||||
return frameBarSize;
|
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return frameThickness;
|
return [];
|
||||||
|
var edges = [];
|
||||||
for (var i = 0; i < barConfigs.length; i++) {
|
for (var i = 0; i < barConfigs.length; i++) {
|
||||||
var bc = barConfigs[i];
|
var bc = barConfigs[i];
|
||||||
if (!bc.enabled)
|
if (!bc.enabled || !(bc.useOverlayLayer ?? false))
|
||||||
continue;
|
continue;
|
||||||
var prefs = bc.screenPreferences || ["all"];
|
var prefs = bc.screenPreferences || ["all"];
|
||||||
if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs))
|
if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs))
|
||||||
continue;
|
continue;
|
||||||
const innerPadding = bc.innerPadding ?? 4;
|
switch (bc.position ?? 0) {
|
||||||
const barT = Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding));
|
case SettingsData.Position.Top:
|
||||||
const spacing = bc.spacing ?? 4;
|
edges.push("top");
|
||||||
const bottomGap = bc.bottomGap ?? 0;
|
break;
|
||||||
return barT + spacing + bottomGap;
|
case SettingsData.Position.Bottom:
|
||||||
|
edges.push("bottom");
|
||||||
|
break;
|
||||||
|
case SettingsData.Position.Left:
|
||||||
|
edges.push("left");
|
||||||
|
break;
|
||||||
|
case SettingsData.Position.Right:
|
||||||
|
edges.push("right");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return frameThickness;
|
return edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendTestNotifications() {
|
readonly property real frameBarContentGap: frameBarInsetPadding < 0 ? frameThickness : frameBarInsetPadding
|
||||||
NotificationService.dismissAllPopups();
|
readonly property real frameBarContentGapExtra: Math.max(0, frameBarContentGap - frameThickness)
|
||||||
sendTestNotification(0);
|
|
||||||
testNotifTimer1.start();
|
function frameEdgeReservation(screen, edge) {
|
||||||
testNotifTimer2.start();
|
if (!screen)
|
||||||
|
return 0;
|
||||||
|
return getActiveBarEdgesForScreen(screen).includes(edge) ? frameBarSize : frameThickness;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendTestNotification(index) {
|
function frameEdgeInsetForSide(screen, side) {
|
||||||
const notifications = [["Notification Position Test", "DMS test notification 1 of 3 ~ Hi there!", "preferences-system"], ["Second Test", "DMS Notification 2 of 3 ~ Check it out!", "applications-graphics"], ["Third Test", "DMS notification 3 of 3 ~ Enjoy!", "face-smile"]];
|
if (!frameEnabled)
|
||||||
|
return 0;
|
||||||
if (index < 0 || index >= notifications.length) {
|
return frameEdgeReservation(screen, side);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notif = notifications[index];
|
|
||||||
testNotificationProcess.command = ["notify-send", "-h", "int:transient:1", "-a", "DMS", "-i", notif[2], notif[0], notif[1]];
|
|
||||||
testNotificationProcess.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMatugenScheme(scheme) {
|
function setMatugenScheme(scheme) {
|
||||||
@@ -2798,15 +2587,6 @@ Singleton {
|
|||||||
set("matugenContrast", value);
|
set("matugenContrast", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRunUserMatugenTemplates(enabled) {
|
|
||||||
if (runUserMatugenTemplates === enabled)
|
|
||||||
return;
|
|
||||||
set("runUserMatugenTemplates", enabled);
|
|
||||||
if (typeof Theme !== "undefined") {
|
|
||||||
Theme.generateSystemThemesFromCurrentTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMatugenTargetMonitor(monitorName) {
|
function setMatugenTargetMonitor(monitorName) {
|
||||||
if (matugenTargetMonitor === monitorName)
|
if (matugenTargetMonitor === monitorName)
|
||||||
return;
|
return;
|
||||||
@@ -2825,11 +2605,6 @@ Singleton {
|
|||||||
SessionData.setWeatherLocation(displayName, coordinates);
|
SessionData.setWeatherLocation(displayName, coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setIconTheme(themeName) {
|
|
||||||
const light = iconThemePerMode && typeof SessionData !== "undefined" && SessionData.isLightMode;
|
|
||||||
setIconThemeForMode(themeName, light);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setIconThemeForMode(themeName, light) {
|
function setIconThemeForMode(themeName, light) {
|
||||||
if (light)
|
if (light)
|
||||||
iconThemeLight = themeName;
|
iconThemeLight = themeName;
|
||||||
@@ -2875,20 +2650,7 @@ Singleton {
|
|||||||
// https://github.com/Supreeeme/xwayland-satellite/issues/104
|
// https://github.com/Supreeeme/xwayland-satellite/issues/104
|
||||||
// no idea if this matters on other compositors but we also set XCURSOR stuff in the launcher
|
// no idea if this matters on other compositors but we also set XCURSOR stuff in the launcher
|
||||||
function updateCompositorCursor() {
|
function updateCompositorCursor() {
|
||||||
if (typeof CompositorService === "undefined")
|
compositorCursorRefreshNeeded();
|
||||||
return;
|
|
||||||
if (CompositorService.isNiri && typeof NiriService !== "undefined") {
|
|
||||||
NiriService.generateNiriCursorConfig();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CompositorService.isHyprland && typeof HyprlandService !== "undefined") {
|
|
||||||
HyprlandService.generateCursorConfig();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (CompositorService.isMango && typeof MangoService !== "undefined") {
|
|
||||||
MangoService.generateCursorConfig();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateXResources() {
|
function updateXResources() {
|
||||||
@@ -2956,20 +2718,6 @@ Singleton {
|
|||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGtkThemingEnabled(enabled) {
|
|
||||||
set("gtkThemingEnabled", enabled);
|
|
||||||
if (enabled && typeof Theme !== "undefined") {
|
|
||||||
Theme.generateSystemThemesFromCurrentTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setQtThemingEnabled(enabled) {
|
|
||||||
set("qtThemingEnabled", enabled);
|
|
||||||
if (enabled && typeof Theme !== "undefined") {
|
|
||||||
Theme.generateSystemThemesFromCurrentTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setShowDock(enabled) {
|
function setShowDock(enabled) {
|
||||||
showDock = enabled;
|
showDock = enabled;
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||||
@@ -3015,16 +2763,6 @@ Singleton {
|
|||||||
Qt.callLater(() => forceDockLayoutRefresh());
|
Qt.callLater(() => forceDockLayoutRefresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDankBarSpacing(spacing) {
|
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
|
||||||
if (defaultBar) {
|
|
||||||
updateBarConfig(defaultBar.id, {
|
|
||||||
"spacing": spacing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateCompositorLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDankBarPosition(position) {
|
function setDankBarPosition(position) {
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
const defaultBar = barConfigs[0] || getBarConfig("default");
|
||||||
if (!defaultBar)
|
if (!defaultBar)
|
||||||
@@ -3080,39 +2818,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetDankBarWidgetsToDefault() {
|
|
||||||
var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"];
|
|
||||||
var defaultCenter = ["music", "clock", "weather"];
|
|
||||||
var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"];
|
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
|
||||||
if (defaultBar) {
|
|
||||||
updateBarConfig(defaultBar.id, {
|
|
||||||
"leftWidgets": defaultLeft,
|
|
||||||
"centerWidgets": defaultCenter,
|
|
||||||
"rightWidgets": defaultRight
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateListModel(leftWidgetsModel, defaultLeft);
|
|
||||||
updateListModel(centerWidgetsModel, defaultCenter);
|
|
||||||
updateListModel(rightWidgetsModel, defaultRight);
|
|
||||||
showLauncherButton = true;
|
|
||||||
showWorkspaceSwitcher = true;
|
|
||||||
showFocusedWindow = true;
|
|
||||||
showWeather = true;
|
|
||||||
showMusic = true;
|
|
||||||
showClipboard = true;
|
|
||||||
showCpuUsage = true;
|
|
||||||
showMemUsage = true;
|
|
||||||
showCpuTemp = true;
|
|
||||||
showGpuTemp = true;
|
|
||||||
showSystemTray = true;
|
|
||||||
showClock = true;
|
|
||||||
showNotificationButton = true;
|
|
||||||
showBattery = true;
|
|
||||||
showControlCenterButton = true;
|
|
||||||
showCapsLockIndicator = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setWorkspaceNameIcon(workspaceName, iconData) {
|
function setWorkspaceNameIcon(workspaceName, iconData) {
|
||||||
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
|
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
|
||||||
iconMap[workspaceName] = iconData;
|
iconMap[workspaceName] = iconData;
|
||||||
@@ -3394,15 +3099,6 @@ Singleton {
|
|||||||
Theme.reloadCustomThemeVariant();
|
Theme.reloadCustomThemeVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDankBarVisible() {
|
|
||||||
const defaultBar = barConfigs[0] || getBarConfig("default");
|
|
||||||
if (defaultBar) {
|
|
||||||
updateBarConfig(defaultBar.id, {
|
|
||||||
"visible": !defaultBar.visible
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleShowDock() {
|
function toggleShowDock() {
|
||||||
setShowDock(!showDock);
|
setShowDock(!showDock);
|
||||||
}
|
}
|
||||||
@@ -3424,13 +3120,6 @@ Singleton {
|
|||||||
savePluginSettings();
|
savePluginSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePluginSettings(pluginId) {
|
|
||||||
if (pluginSettings[pluginId]) {
|
|
||||||
delete pluginSettings[pluginId];
|
|
||||||
savePluginSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPluginSettingsForPlugin(pluginId) {
|
function getPluginSettingsForPlugin(pluginId) {
|
||||||
const settings = pluginSettings[pluginId];
|
const settings = pluginSettings[pluginId];
|
||||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||||
@@ -3456,22 +3145,6 @@ Singleton {
|
|||||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNiriOutputSettings(outputId, settings) {
|
|
||||||
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
|
|
||||||
updated[outputId] = settings;
|
|
||||||
niriOutputSettings = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNiriOutputSettings(outputId) {
|
|
||||||
if (!niriOutputSettings[outputId])
|
|
||||||
return;
|
|
||||||
const updated = JSON.parse(JSON.stringify(niriOutputSettings));
|
|
||||||
delete updated[outputId];
|
|
||||||
niriOutputSettings = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHyprlandOutputSetting(outputId, key, defaultValue) {
|
function getHyprlandOutputSetting(outputId, key, defaultValue) {
|
||||||
if (!hyprlandOutputSettings[outputId])
|
if (!hyprlandOutputSettings[outputId])
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
@@ -3496,40 +3169,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHyprlandOutputSettings(outputId) {
|
|
||||||
const settings = hyprlandOutputSettings[outputId];
|
|
||||||
return settings ? JSON.parse(JSON.stringify(settings)) : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHyprlandOutputSettings(outputId, settings) {
|
|
||||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
|
||||||
updated[outputId] = settings;
|
|
||||||
hyprlandOutputSettings = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeHyprlandOutputSettings(outputId) {
|
|
||||||
if (!hyprlandOutputSettings[outputId])
|
|
||||||
return;
|
|
||||||
const updated = JSON.parse(JSON.stringify(hyprlandOutputSettings));
|
|
||||||
delete updated[outputId];
|
|
||||||
hyprlandOutputSettings = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDisplayProfiles(compositor) {
|
|
||||||
return displayProfiles[compositor] || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDisplayProfile(compositor, profileId, data) {
|
|
||||||
const updated = JSON.parse(JSON.stringify(displayProfiles));
|
|
||||||
if (!updated[compositor])
|
|
||||||
updated[compositor] = {};
|
|
||||||
updated[compositor][profileId] = data;
|
|
||||||
displayProfiles = updated;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDisplayProfile(compositor, profileId) {
|
function removeDisplayProfile(compositor, profileId) {
|
||||||
if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId])
|
if (!displayProfiles[compositor] || !displayProfiles[compositor][profileId])
|
||||||
return;
|
return;
|
||||||
@@ -3583,29 +3222,6 @@ Singleton {
|
|||||||
id: rightWidgetsModel
|
id: rightWidgetsModel
|
||||||
}
|
}
|
||||||
|
|
||||||
property Process testNotificationProcess
|
|
||||||
|
|
||||||
testNotificationProcess: Process {
|
|
||||||
command: []
|
|
||||||
running: false
|
|
||||||
}
|
|
||||||
|
|
||||||
property Timer testNotifTimer1
|
|
||||||
|
|
||||||
testNotifTimer1: Timer {
|
|
||||||
interval: 400
|
|
||||||
repeat: false
|
|
||||||
onTriggered: sendTestNotification(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
property Timer testNotifTimer2
|
|
||||||
|
|
||||||
testNotifTimer2: Timer {
|
|
||||||
interval: 800
|
|
||||||
repeat: false
|
|
||||||
onTriggered: sendTestNotification(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
property alias settingsFile: settingsFile
|
property alias settingsFile: settingsFile
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
|||||||
@@ -416,24 +416,6 @@ const StockThemes = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeCategories = {
|
|
||||||
GENERIC: {
|
|
||||||
name: "Generic",
|
|
||||||
variants: [
|
|
||||||
"blue",
|
|
||||||
"purple",
|
|
||||||
"green",
|
|
||||||
"orange",
|
|
||||||
"red",
|
|
||||||
"cyan",
|
|
||||||
"pink",
|
|
||||||
"amber",
|
|
||||||
"coral",
|
|
||||||
"monochrome",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ThemeNames = {
|
const ThemeNames = {
|
||||||
BLUE: "blue",
|
BLUE: "blue",
|
||||||
PURPLE: "purple",
|
PURPLE: "purple",
|
||||||
@@ -448,10 +430,6 @@ const ThemeNames = {
|
|||||||
DYNAMIC: "dynamic",
|
DYNAMIC: "dynamic",
|
||||||
};
|
};
|
||||||
|
|
||||||
function isStockTheme(themeName) {
|
|
||||||
return Object.keys(StockThemes.DARK).includes(themeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAvailableThemes(isLight = false) {
|
function getAvailableThemes(isLight = false) {
|
||||||
return isLight ? StockThemes.LIGHT : StockThemes.DARK;
|
return isLight ? StockThemes.LIGHT : StockThemes.DARK;
|
||||||
}
|
}
|
||||||
@@ -464,7 +442,3 @@ function getThemeByName(themeName, isLight = false) {
|
|||||||
function getAllThemeNames() {
|
function getAllThemeNames() {
|
||||||
return Object.keys(StockThemes.DARK);
|
return Object.keys(StockThemes.DARK);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThemeCategories() {
|
|
||||||
return ThemeCategories;
|
|
||||||
}
|
|
||||||
|
|||||||
+64
-605
@@ -107,8 +107,11 @@ Singleton {
|
|||||||
property int _colorsRetryCount: 0
|
property int _colorsRetryCount: 0
|
||||||
property double _lastGenerateMs: 0
|
property double _lastGenerateMs: 0
|
||||||
|
|
||||||
property bool themeModeAutomationActive: false
|
property bool blurLayersActive: false
|
||||||
property bool dmsServiceWasDisconnected: true
|
property bool matugenToastSuppressed: false
|
||||||
|
|
||||||
|
signal screenTransitionNeeded
|
||||||
|
signal themeGenerationStarting
|
||||||
|
|
||||||
readonly property var dank16: {
|
readonly property var dank16: {
|
||||||
const raw = matugenColors?.dank16;
|
const raw = matugenColors?.dank16;
|
||||||
@@ -144,7 +147,7 @@ Singleton {
|
|||||||
Quickshell.execDetached(["mkdir", "-p", stateDir]);
|
Quickshell.execDetached(["mkdir", "-p", stateDir]);
|
||||||
// shellDir may be an embedded-UI extraction, which is read-only and
|
// shellDir may be an embedded-UI extraction, which is read-only and
|
||||||
// unexecutable (dankgo shellapp/shellfs makeReadOnly chmods 0444)
|
// unexecutable (dankgo shellapp/shellfs makeReadOnly chmods 0444)
|
||||||
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "", shellDir, "assets-only"]);
|
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "assets", "", shellDir]);
|
||||||
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
|
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
|
||||||
matugenAvailable = (code === 0) && !envDisableMatugen;
|
matugenAvailable = (code === 0) && !envDisableMatugen;
|
||||||
|
|
||||||
@@ -196,235 +199,6 @@ Singleton {
|
|||||||
const currentIsLight = (typeof SessionData !== "undefined") ? SessionData.isLightMode : false;
|
const currentIsLight = (typeof SessionData !== "undefined") ? SessionData.isLightMode : false;
|
||||||
SettingsData.updateCosmicThemeMode(currentIsLight);
|
SettingsData.updateCosmicThemeMode(currentIsLight);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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) {
|
|
||||||
log.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) {
|
|
||||||
log.warn("Theme automation: Failed to set location", locationResponse.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.warn("Theme automation: No location configured");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SessionService
|
|
||||||
enabled: SessionData.themeModeAutoEnabled
|
|
||||||
|
|
||||||
function onSessionUnlocked() {
|
|
||||||
root.triggerThemeAutomationRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSessionResumed() {
|
|
||||||
root.triggerThemeAutomationRefresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerThemeAutomationRefresh() {
|
|
||||||
if (!themeAutoBackendAvailable()) {
|
|
||||||
root.evaluateThemeMode();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DMSService.sendRequest("theme.auto.trigger", {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMatugenColor(path, fallback) {
|
function getMatugenColor(path, fallback) {
|
||||||
@@ -571,8 +345,8 @@ Singleton {
|
|||||||
property color surfaceVariantAlpha: withAlpha(surfaceVariant, 0.2)
|
property color surfaceVariantAlpha: withAlpha(surfaceVariant, 0.2)
|
||||||
|
|
||||||
readonly property bool foregroundLayers: typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true)
|
readonly property bool foregroundLayers: typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true)
|
||||||
readonly property bool blurForegroundLayers: BlurService.enabled && foregroundLayers
|
readonly property bool blurForegroundLayers: blurLayersActive && foregroundLayers
|
||||||
readonly property bool transparentBlurLayers: BlurService.enabled && !foregroundLayers
|
readonly property bool transparentBlurLayers: blurLayersActive && !foregroundLayers
|
||||||
readonly property bool notificationForegroundLayers: typeof SettingsData === "undefined" || (SettingsData.notificationForegroundLayers ?? true)
|
readonly property bool notificationForegroundLayers: typeof SettingsData === "undefined" || (SettingsData.notificationForegroundLayers ?? true)
|
||||||
readonly property color readableSurface: withAlpha(surfaceContainer, popupTransparency)
|
readonly property color readableSurface: withAlpha(surfaceContainer, popupTransparency)
|
||||||
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
||||||
@@ -658,7 +432,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (foregroundLayers ? withAlpha(surfaceContainerHigh, BlurService.enabled ? Math.min(popupTransparency, 0.24) : popupTransparency) : withAlpha(surfaceContainer, 0))
|
readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (foregroundLayers ? withAlpha(surfaceContainerHigh, blurLayersActive ? Math.min(popupTransparency, 0.24) : popupTransparency) : withAlpha(surfaceContainer, 0))
|
||||||
readonly property color ccPillInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.08) : nestedSurface
|
readonly property color ccPillInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.08) : nestedSurface
|
||||||
readonly property color ccPillInactiveHoverBg: transparentBlurLayers ? withAlpha(primary, 0.10) : primaryPressed
|
readonly property color ccPillInactiveHoverBg: transparentBlurLayers ? withAlpha(primary, 0.10) : primaryPressed
|
||||||
readonly property color ccSliderTrackColor: transparentBlurLayers ? surfaceText : surfaceContainerHigh
|
readonly property color ccSliderTrackColor: transparentBlurLayers ? surfaceText : surfaceContainerHigh
|
||||||
@@ -978,22 +752,6 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function elevationTintOpacity(level) {
|
|
||||||
if (!level)
|
|
||||||
return 0;
|
|
||||||
if (level === elevationLevel1)
|
|
||||||
return 0.05;
|
|
||||||
if (level === elevationLevel2)
|
|
||||||
return 0.08;
|
|
||||||
if (level === elevationLevel3)
|
|
||||||
return 0.11;
|
|
||||||
if (level === elevationLevel4)
|
|
||||||
return 0.12;
|
|
||||||
if (level === elevationLevel5)
|
|
||||||
return 0.14;
|
|
||||||
return 0.08;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var animationDurations: [
|
readonly property var animationDurations: [
|
||||||
{
|
{
|
||||||
"shorter": 0,
|
"shorter": 0,
|
||||||
@@ -1261,9 +1019,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function screenTransition() {
|
function screenTransition() {
|
||||||
if (CompositorService.isNiri) {
|
screenTransitionNeeded();
|
||||||
NiriService.doScreenTransition();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
function switchTheme(themeName, savePrefs = true, enableTransition = true) {
|
||||||
@@ -1315,7 +1071,6 @@ Singleton {
|
|||||||
SessionData.setLightMode(light);
|
SessionData.setLightMode(light);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortalService.setLightMode(light);
|
|
||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
SettingsData.updateCosmicThemeMode(light);
|
SettingsData.updateCosmicThemeMode(light);
|
||||||
}
|
}
|
||||||
@@ -1326,22 +1081,6 @@ Singleton {
|
|||||||
setLightMode(!isLightMode, savePrefs, true);
|
setLightMode(!isLightMode, savePrefs, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function forceGenerateSystemThemes() {
|
|
||||||
if (!matugenAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
generateSystemThemesFromCurrentTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAvailableThemes() {
|
|
||||||
return StockThemes.getAllThemeNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThemeDisplayName(themeName) {
|
|
||||||
const themeData = StockThemes.getThemeByName(themeName, isLightMode);
|
|
||||||
return themeData.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThemeColors(themeName) {
|
function getThemeColors(themeName) {
|
||||||
if (themeName === "custom" && customThemeData) {
|
if (themeName === "custom" && customThemeData) {
|
||||||
return customThemeData;
|
return customThemeData;
|
||||||
@@ -1457,10 +1196,6 @@ Singleton {
|
|||||||
readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
|
readonly property var _availableThemeNames: StockThemes.getAllThemeNames()
|
||||||
property string currentThemeName: currentTheme
|
property string currentThemeName: currentTheme
|
||||||
|
|
||||||
function panelBackground() {
|
|
||||||
return withAlpha(surfaceContainer, panelTransparency);
|
|
||||||
}
|
|
||||||
|
|
||||||
property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
|
property real notepadTransparency: SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : popupTransparency
|
||||||
|
|
||||||
property bool widgetBackgroundHasAlpha: {
|
property bool widgetBackgroundHasAlpha: {
|
||||||
@@ -1537,10 +1272,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isColorDark(c) {
|
|
||||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
function barIconSize(barThickness, offset, maximizeIcon, iconScale) {
|
function barIconSize(barThickness, offset, maximizeIcon, iconScale) {
|
||||||
const defaultOffset = offset !== undefined ? offset : -6;
|
const defaultOffset = offset !== undefined ? offset : -6;
|
||||||
const size = (maximizeIcon ?? false) ? iconSizeLarge : iconSize;
|
const size = (maximizeIcon ?? false) ? iconSizeLarge : iconSize;
|
||||||
@@ -1623,19 +1354,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPowerProfileDescription(profile) {
|
|
||||||
switch (profile) {
|
|
||||||
case 0:
|
|
||||||
return I18n.tr("Extend battery life", "power profile description");
|
|
||||||
case 1:
|
|
||||||
return I18n.tr("Balance power and performance", "power profile description");
|
|
||||||
case 2:
|
|
||||||
return I18n.tr("Prioritize performance", "power profile description");
|
|
||||||
default:
|
|
||||||
return I18n.tr("Custom power profile", "power profile description");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLightModeChanged() {
|
function onLightModeChanged() {
|
||||||
if (currentTheme === "custom" && customThemeFileView.path) {
|
if (currentTheme === "custom" && customThemeFileView.path) {
|
||||||
customThemeFileView.reload();
|
customThemeFileView.reload();
|
||||||
@@ -1663,9 +1381,7 @@ Singleton {
|
|||||||
|
|
||||||
log.info("Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)");
|
log.info("Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)");
|
||||||
|
|
||||||
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
|
themeGenerationStarting();
|
||||||
NiriService.suppressNextToast();
|
|
||||||
}
|
|
||||||
|
|
||||||
const desired = {
|
const desired = {
|
||||||
"kind": kind,
|
"kind": kind,
|
||||||
@@ -1777,8 +1493,7 @@ Singleton {
|
|||||||
|
|
||||||
if (currentTheme === dynamic) {
|
if (currentTheme === dynamic) {
|
||||||
if (!rawWallpaperPath) {
|
if (!rawWallpaperPath) {
|
||||||
log.warn("Auto theme has no wallpaper - skipping matugen, syncing portal mode only");
|
log.warn("Auto theme has no wallpaper - skipping matugen");
|
||||||
PortalService.setLightMode(isLight);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
|
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
|
||||||
@@ -1926,6 +1641,47 @@ Singleton {
|
|||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshGtkTheme() {
|
||||||
|
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
|
||||||
|
const theme = isLight ? "adw-gtk3" : "adw-gtk3-dark";
|
||||||
|
const schema = "org.gnome.desktop.interface";
|
||||||
|
const key = "gtk-theme";
|
||||||
|
|
||||||
|
const makeCmd = (tool, schema, val) => {
|
||||||
|
if (tool === "gsettings") {
|
||||||
|
return `gsettings set ${schema} ${key} '' && gsettings set ${schema} ${key} ${val}`;
|
||||||
|
} else {
|
||||||
|
const dconfPath = `/${schema.replace(/\./g, "/")}`;
|
||||||
|
return `dconf write ${dconfPath}/${key} "''" && dconf write ${dconfPath}/${key} "'${val}'"`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Proc.runCommand("gtkRefresher", ["sh", "-c", makeCmd("gsettings", schema, theme)], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
Proc.runCommand("gtkRefreshFallback", ["sh", "-c", makeCmd("dconf", schema, theme)], (output, exitCode) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
log.warn("Failed to refresh gtk-theme");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchGtk3colors() {
|
||||||
|
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
|
||||||
|
Proc.runCommand("gtk3Patcher", ["bash", shellDir + "/scripts/gtk.sh", configDir, "patch", isLight, shellDir], (output, exitCode) => {
|
||||||
|
switch (exitCode) {
|
||||||
|
case 0:
|
||||||
|
refreshGtkTheme();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn(`Failed to patch GTK3 colors: ${output}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyGtkColors() {
|
function applyGtkColors() {
|
||||||
if (!matugenAvailable) {
|
if (!matugenAvailable) {
|
||||||
if (typeof ToastService !== "undefined") {
|
if (typeof ToastService !== "undefined") {
|
||||||
@@ -1935,9 +1691,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
|
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
|
||||||
Proc.runCommand("gtkApplier", ["bash", shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
|
Proc.runCommand("gtkApplier", ["bash", shellDir + "/scripts/gtk.sh", configDir, "apply", isLight, shellDir], (output, exitCode) => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
if (typeof ToastService !== "undefined" && !root.matugenToastSuppressed) {
|
||||||
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
|
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1975,18 +1731,17 @@ Singleton {
|
|||||||
return Qt.rgba(c.r, c.g, c.b, a);
|
return Qt.rgba(c.r, c.g, c.b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
function popupLayerColor(baseColor) {
|
|
||||||
if (isConnectedEffect)
|
|
||||||
return connectedSurfaceColor;
|
|
||||||
return withAlpha(baseColor, popupTransparency);
|
|
||||||
}
|
|
||||||
|
|
||||||
function blendAlpha(c, a) {
|
function blendAlpha(c, a) {
|
||||||
if (!c || c.r === undefined)
|
if (!c || c.r === undefined)
|
||||||
return Qt.rgba(0, 0, 0, 0);
|
return Qt.rgba(0, 0, 0, 0);
|
||||||
return Qt.rgba(c.r, c.g, c.b, c.a * a);
|
return Qt.rgba(c.r, c.g, c.b, c.a * a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hoverTint(base) {
|
||||||
|
const factor = 1.2;
|
||||||
|
return isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||||
|
}
|
||||||
|
|
||||||
function blend(c1, c2, r) {
|
function blend(c1, c2, r) {
|
||||||
return Qt.rgba(c1.r * (1 - r) + c2.r * r, c1.g * (1 - r) + c2.g * r, c1.b * (1 - r) + c2.b * r, c1.a * (1 - r) + c2.a * r);
|
return Qt.rgba(c1.r * (1 - r) + c2.r * r, c1.g * (1 - r) + c2.g * r, c1.b * (1 - r) + c2.b * r, c1.a * (1 - r) + c2.a * r);
|
||||||
}
|
}
|
||||||
@@ -2131,8 +1886,11 @@ Singleton {
|
|||||||
root.matugenCompleted(currentMode, "error");
|
root.matugenCompleted(currentMode, "error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pendingThemeRequest)
|
if (!pendingThemeRequest) {
|
||||||
|
if (SettingsData.matugenTemplateGtk)
|
||||||
|
patchGtk3colors();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const req = pendingThemeRequest;
|
const req = pendingThemeRequest;
|
||||||
pendingThemeRequest = null;
|
pendingThemeRequest = null;
|
||||||
@@ -2300,303 +2058,4 @@ 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) {
|
|
||||||
log.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) {
|
|
||||||
log.warn("Theme automation: Waiting for IP location from backend");
|
|
||||||
} else {
|
|
||||||
log.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) {
|
|
||||||
log.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) {
|
|
||||||
log.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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,13 @@
|
|||||||
function markdownToHtml(text) {
|
function markdownToHtml(text) {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
// Store code blocks and inline code to protect them from further processing
|
|
||||||
const codeBlocks = [];
|
const codeBlocks = [];
|
||||||
const inlineCode = [];
|
const inlineCode = [];
|
||||||
let blockIndex = 0;
|
let blockIndex = 0;
|
||||||
let inlineIndex = 0;
|
let inlineIndex = 0;
|
||||||
|
|
||||||
// First, extract and replace code blocks with placeholders
|
|
||||||
let html = text.replace(/```([\s\S]*?)```/g, (match, code) => {
|
let html = text.replace(/```([\s\S]*?)```/g, (match, code) => {
|
||||||
// Trim leading and trailing blank lines only
|
|
||||||
const trimmedCode = code.replace(/^\n+|\n+$/g, '');
|
const trimmedCode = code.replace(/^\n+|\n+$/g, '');
|
||||||
// Escape HTML entities in code
|
|
||||||
const escapedCode = trimmedCode.replace(/&/g, '&')
|
const escapedCode = trimmedCode.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>');
|
.replace(/>/g, '>');
|
||||||
@@ -22,9 +18,7 @@ function markdownToHtml(text) {
|
|||||||
return `\x00CODEBLOCK${blockIndex++}\x00`;
|
return `\x00CODEBLOCK${blockIndex++}\x00`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract and replace inline code
|
|
||||||
html = html.replace(/`([^`]+)`/g, (match, code) => {
|
html = html.replace(/`([^`]+)`/g, (match, code) => {
|
||||||
// Escape HTML entities in code
|
|
||||||
const escapedCode = code.replace(/&/g, '&')
|
const escapedCode = code.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>');
|
.replace(/>/g, '>');
|
||||||
@@ -40,12 +34,10 @@ function markdownToHtml(text) {
|
|||||||
return prefix + `\x00URL${urlIndex++}\x00`;
|
return prefix + `\x00URL${urlIndex++}\x00`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Escape HTML entities (but not in code blocks or URLs)
|
|
||||||
html = html.replace(/&/g, '&')
|
html = html.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>');
|
.replace(/>/g, '>');
|
||||||
|
|
||||||
// Headers
|
|
||||||
html = html.replace(/^### (.*?)$/gm, '<h3>$1</h3>');
|
html = html.replace(/^### (.*?)$/gm, '<h3>$1</h3>');
|
||||||
html = html.replace(/^## (.*?)$/gm, '<h2>$1</h2>');
|
html = html.replace(/^## (.*?)$/gm, '<h2>$1</h2>');
|
||||||
html = html.replace(/^# (.*?)$/gm, '<h1>$1</h1>');
|
html = html.replace(/^# (.*?)$/gm, '<h1>$1</h1>');
|
||||||
@@ -58,20 +50,16 @@ function markdownToHtml(text) {
|
|||||||
html = html.replace(/__(.*?)__/g, '<b>$1</b>');
|
html = html.replace(/__(.*?)__/g, '<b>$1</b>');
|
||||||
html = html.replace(/_(.*?)_/g, '<i>$1</i>');
|
html = html.replace(/_(.*?)_/g, '<i>$1</i>');
|
||||||
|
|
||||||
// Links
|
|
||||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
||||||
|
|
||||||
// Lists
|
|
||||||
html = html.replace(/^\* (.*?)$/gm, '<li>$1</li>');
|
html = html.replace(/^\* (.*?)$/gm, '<li>$1</li>');
|
||||||
html = html.replace(/^- (.*?)$/gm, '<li>$1</li>');
|
html = html.replace(/^- (.*?)$/gm, '<li>$1</li>');
|
||||||
html = html.replace(/^\d+\. (.*?)$/gm, '<li>$1</li>');
|
html = html.replace(/^\d+\. (.*?)$/gm, '<li>$1</li>');
|
||||||
|
|
||||||
// Wrap consecutive list items in ul/ol tags
|
|
||||||
html = html.replace(/(<li>[\s\S]*?<\/li>\s*)+/g, function(match) {
|
html = html.replace(/(<li>[\s\S]*?<\/li>\s*)+/g, function(match) {
|
||||||
return '<ul>' + match + '</ul>';
|
return '<ul>' + match + '</ul>';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore extracted URLs as anchor tags (preserves raw & in href)
|
|
||||||
html = html.replace(/\x00URL(\d+)\x00/g, (_, index) => {
|
html = html.replace(/\x00URL(\d+)\x00/g, (_, index) => {
|
||||||
const url = urls[parseInt(index)];
|
const url = urls[parseInt(index)];
|
||||||
const display = url.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
const display = url.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
@@ -87,30 +75,23 @@ function markdownToHtml(text) {
|
|||||||
return inlineCode[parseInt(index)];
|
return inlineCode[parseInt(index)];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Line breaks (after code blocks are restored)
|
|
||||||
html = html.replace(/\n\n/g, '</p><p>');
|
html = html.replace(/\n\n/g, '</p><p>');
|
||||||
html = html.replace(/\n/g, '<br/>');
|
html = html.replace(/\n/g, '<br/>');
|
||||||
|
|
||||||
// Wrap in paragraph tags if not already wrapped
|
|
||||||
if (!html.startsWith('<')) {
|
if (!html.startsWith('<')) {
|
||||||
html = '<p>' + html + '</p>';
|
html = '<p>' + html + '</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the final HTML
|
|
||||||
// Remove <br/> tags immediately before block elements
|
|
||||||
html = html.replace(/<br\/>\s*<pre>/g, '<pre>');
|
html = html.replace(/<br\/>\s*<pre>/g, '<pre>');
|
||||||
html = html.replace(/<br\/>\s*<ul>/g, '<ul>');
|
html = html.replace(/<br\/>\s*<ul>/g, '<ul>');
|
||||||
html = html.replace(/<br\/>\s*<h[1-6]>/g, '<h$1>');
|
html = html.replace(/<br\/>\s*<h[1-6]>/g, '<h$1>');
|
||||||
|
|
||||||
// Remove empty paragraphs
|
|
||||||
html = html.replace(/<p>\s*<\/p>/g, '');
|
html = html.replace(/<p>\s*<\/p>/g, '');
|
||||||
html = html.replace(/<p>\s*<br\/>\s*<\/p>/g, '');
|
html = html.replace(/<p>\s*<br\/>\s*<\/p>/g, '');
|
||||||
|
|
||||||
// Remove excessive line breaks
|
html = html.replace(/(<br\/>){3,}/g, '<br/><br/>');
|
||||||
html = html.replace(/(<br\/>){3,}/g, '<br/><br/>'); // Max 2 consecutive line breaks
|
html = html.replace(/(<\/p>)\s*(<p>)/g, '$1$2');
|
||||||
html = html.replace(/(<\/p>)\s*(<p>)/g, '$1$2'); // Remove whitespace between paragraphs
|
|
||||||
|
|
||||||
// Remove leading/trailing whitespace
|
|
||||||
html = html.trim();
|
html = html.trim();
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
signal toastRequested(int severity, string title, string body, string command, string category)
|
||||||
|
signal toastCategoryDismissed(string category)
|
||||||
|
|
||||||
property var settingsRoot: null
|
property var settingsRoot: null
|
||||||
|
|
||||||
onSettingsRootChanged: {
|
onSettingsRootChanged: {
|
||||||
@@ -317,7 +319,7 @@ Singleton {
|
|||||||
function launchAuthApplyTerminalFallback(fromPrecheck, details) {
|
function launchAuthApplyTerminalFallback(fromPrecheck, details) {
|
||||||
authApplyTerminalFallbackFromPrecheck = fromPrecheck;
|
authApplyTerminalFallbackFromPrecheck = fromPrecheck;
|
||||||
if (details && details !== "")
|
if (details && details !== "")
|
||||||
ToastService.showInfo(I18n.tr("Authentication changes need sudo. Opening terminal so you can use password or fingerprint."), details, "", "auth-sync");
|
toastRequested(0, I18n.tr("Authentication changes need sudo. Opening terminal so you can use password or fingerprint."), details, "", "auth-sync");
|
||||||
authApplyTerminalFallbackStderr = "";
|
authApplyTerminalFallbackStderr = "";
|
||||||
authApplyTerminalFallbackProcess.running = true;
|
authApplyTerminalFallbackProcess.running = true;
|
||||||
}
|
}
|
||||||
@@ -364,21 +366,21 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deferGreeterAutoLoginSyncToPill(details) {
|
function deferGreeterAutoLoginSyncToPill(details) {
|
||||||
ToastService.dismissCategory("greeter-autologin-sync");
|
toastCategoryDismissed("greeter-autologin-sync");
|
||||||
if (settingsRoot)
|
if (settingsRoot)
|
||||||
settingsRoot.set("greeterSyncPending", true);
|
settingsRoot.set("greeterSyncPending", true);
|
||||||
ToastService.showWarning(I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
|
toastRequested(1, I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
|
||||||
finishGreeterAutoLoginSync();
|
finishGreeterAutoLoginSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
function greeterAutoLoginSyncSuccessToast(details) {
|
function greeterAutoLoginSyncSuccessToast(details) {
|
||||||
const enabling = settingsRoot && settingsRoot.greeterAutoLogin;
|
const enabling = settingsRoot && settingsRoot.greeterAutoLogin;
|
||||||
// Clear the sticky in-progress toast, then confirm with an auto-dismissing toast.
|
// Clear the sticky in-progress toast, then confirm with an auto-dismissing toast.
|
||||||
ToastService.dismissCategory("greeter-autologin-sync");
|
toastCategoryDismissed("greeter-autologin-sync");
|
||||||
if (enabling) {
|
if (enabling) {
|
||||||
ToastService.showWarning(I18n.tr("Auto-login enabled"), I18n.tr("You'll skip the greeter password after the next reboot. The lock screen and signing out still require your password.") + (details ? "\n\n" + details : ""));
|
toastRequested(1, I18n.tr("Auto-login enabled"), I18n.tr("You'll skip the greeter password after the next reboot. The lock screen and signing out still require your password.") + (details ? "\n\n" + details : ""), "", "");
|
||||||
} else {
|
} else {
|
||||||
ToastService.showInfo(I18n.tr("Auto-login disabled"), I18n.tr("You'll enter your password at the greeter after the next reboot.") + (details ? "\n\n" + details : ""));
|
toastRequested(0, I18n.tr("Auto-login disabled"), I18n.tr("You'll enter your password at the greeter after the next reboot.") + (details ? "\n\n" + details : ""), "", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +577,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
const enabling = root.settingsRoot && root.settingsRoot.greeterAutoLogin;
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showWarning(enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms-greeter sync --autologin", "greeter-autologin-sync");
|
root.toastRequested(1, enabling ? I18n.tr("Applying auto-login on startup...") : I18n.tr("Disabling auto-login on startup..."), "", "dms-greeter sync --autologin", "greeter-autologin-sync");
|
||||||
root.greeterAutoLoginSyncProcess.running = true;
|
root.greeterAutoLoginSyncProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -604,7 +606,7 @@ Singleton {
|
|||||||
let details = out;
|
let details = out;
|
||||||
if (err !== "")
|
if (err !== "")
|
||||||
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
|
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
|
||||||
ToastService.showInfo(I18n.tr("Authentication changes applied"), details, "", "auth-sync");
|
root.toastRequested(0, I18n.tr("Authentication changes applied"), details, "", "auth-sync");
|
||||||
root.detectAuthCapabilities();
|
root.detectAuthCapabilities();
|
||||||
root.finishAuthApply();
|
root.finishAuthApply();
|
||||||
return;
|
return;
|
||||||
@@ -615,7 +617,7 @@ Singleton {
|
|||||||
details = out;
|
details = out;
|
||||||
if (err !== "")
|
if (err !== "")
|
||||||
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
|
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
|
||||||
ToastService.showWarning(I18n.tr("Background authentication sync failed. Trying terminal mode."), details, "", "auth-sync");
|
root.toastRequested(1, I18n.tr("Background authentication sync failed. Trying terminal mode."), details, "", "auth-sync");
|
||||||
root.launchAuthApplyTerminalFallback(false, "");
|
root.launchAuthApplyTerminalFallback(false, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -631,7 +633,7 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
const err = (root.authApplySudoProbeStderr || "").trim();
|
const err = (root.authApplySudoProbeStderr || "").trim();
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
ToastService.showInfo(I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
|
root.toastRequested(0, I18n.tr("Applying authentication changes..."), "", "", "auth-sync");
|
||||||
root.authApplyProcess.running = true;
|
root.authApplyProcess.running = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -651,10 +653,10 @@ Singleton {
|
|||||||
onExited: exitCode => {
|
onExited: exitCode => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication there; it will close automatically when done.");
|
const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication there; it will close automatically when done.");
|
||||||
ToastService.showInfo(message, "", "", "auth-sync");
|
root.toastRequested(0, message, "", "", "auth-sync");
|
||||||
} else {
|
} else {
|
||||||
let details = (root.authApplyTerminalFallbackStderr || "").trim();
|
let details = (root.authApplyTerminalFallbackStderr || "").trim();
|
||||||
ToastService.showError(I18n.tr("Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.") + " (exit " + exitCode + ")", details, "", "auth-sync");
|
root.toastRequested(2, I18n.tr("Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.") + " (exit " + exitCode + ")", details, "", "auth-sync");
|
||||||
}
|
}
|
||||||
root.finishAuthApply();
|
root.finishAuthApply();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ var SPEC = {
|
|||||||
niriOverviewLastMode: { def: "apps" },
|
niriOverviewLastMode: { def: "apps" },
|
||||||
|
|
||||||
settingsSidebarExpandedIds: { def: "," },
|
settingsSidebarExpandedIds: { def: "," },
|
||||||
settingsSidebarCollapsedIds: { def: "," }
|
settingsSidebarCollapsedIds: { def: "," },
|
||||||
|
showConfigReloadToast: { def: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
|
|||||||
@@ -75,26 +75,3 @@ function migrateToVersion(obj, targetVersion, settingsData) {
|
|||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup(fileText) {
|
|
||||||
var getValidKeys = SpecModule.getValidKeys;
|
|
||||||
if (!fileText || !fileText.trim()) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var session = JSON.parse(fileText);
|
|
||||||
var validKeys = getValidKeys();
|
|
||||||
var needsSave = false;
|
|
||||||
|
|
||||||
for (var key in session) {
|
|
||||||
if (validKeys.indexOf(key) < 0) {
|
|
||||||
delete session[key];
|
|
||||||
needsSave = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return needsSave ? JSON.stringify(session, null, 2) : null;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("SessionData: Failed to cleanup unused keys:", e.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ var SPEC = {
|
|||||||
mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
||||||
mangoTrackpadNaturalScrolling: { def: true, onChange: "updateCompositorCursor" },
|
mangoTrackpadNaturalScrolling: { def: true, onChange: "updateCompositorCursor" },
|
||||||
|
mouseAccelProfile: { def: "default", onChange: "updateCompositorInput" },
|
||||||
|
mouseAccelSpeed: { def: 0.0, onChange: "updateCompositorInput" },
|
||||||
|
mouseLeftHanded: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
mouseMiddleEmulation: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
mouseNaturalScroll: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
mouseScrollFactor: { def: 1.0, onChange: "updateCompositorInput" },
|
||||||
|
mouseScrollMethod: { def: "default", onChange: "updateCompositorInput" },
|
||||||
|
touchpadAccelProfile: { def: "default", onChange: "updateCompositorInput" },
|
||||||
|
touchpadAccelSpeed: { def: 0.0, onChange: "updateCompositorInput" },
|
||||||
|
touchpadDisableOnExternalMouse: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
touchpadDisableWhileTyping: { def: true, onChange: "updateCompositorInput" },
|
||||||
|
touchpadDragLock: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
touchpadMiddleEmulation: { def: false, onChange: "updateCompositorInput" },
|
||||||
|
touchpadNaturalScroll: { def: true, onChange: "updateCompositorInput" },
|
||||||
|
touchpadScrollFactor: { def: 1.0, onChange: "updateCompositorInput" },
|
||||||
|
touchpadScrollMethod: { def: "default", onChange: "updateCompositorInput" },
|
||||||
|
touchpadTapAndDrag: { def: true, onChange: "updateCompositorInput" },
|
||||||
|
touchpadTapToClick: { def: true, onChange: "updateCompositorInput" },
|
||||||
|
|
||||||
firstDayOfWeek: { def: -1 },
|
firstDayOfWeek: { def: -1 },
|
||||||
showWeekNumber: { def: false },
|
showWeekNumber: { def: false },
|
||||||
@@ -355,11 +373,6 @@ var SPEC = {
|
|||||||
fadeToDpmsEnabled: { def: true },
|
fadeToDpmsEnabled: { def: true },
|
||||||
fadeToDpmsGracePeriod: { def: 5 },
|
fadeToDpmsGracePeriod: { def: 5 },
|
||||||
launchPrefix: { def: "" },
|
launchPrefix: { def: "" },
|
||||||
brightnessDevicePins: { def: {} },
|
|
||||||
wifiNetworkPins: { def: {} },
|
|
||||||
bluetoothDevicePins: { def: {} },
|
|
||||||
audioInputDevicePins: { def: {} },
|
|
||||||
audioOutputDevicePins: { def: {} },
|
|
||||||
|
|
||||||
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||||
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||||
@@ -477,6 +490,7 @@ var SPEC = {
|
|||||||
notificationTimeoutLow: { def: 5000 },
|
notificationTimeoutLow: { def: 5000 },
|
||||||
notificationTimeoutNormal: { def: 5000 },
|
notificationTimeoutNormal: { def: 5000 },
|
||||||
notificationTimeoutCritical: { def: 0 },
|
notificationTimeoutCritical: { def: 0 },
|
||||||
|
notificationIgnoreAppTimeout: { def: false },
|
||||||
notificationCompactMode: { def: false },
|
notificationCompactMode: { def: false },
|
||||||
notificationShowTimeoutBar: { def: false },
|
notificationShowTimeoutBar: { def: false },
|
||||||
notificationDedupeEnabled: { def: true },
|
notificationDedupeEnabled: { def: true },
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
.import "./SettingsSpec.js" as SpecModule
|
.import "./SettingsSpec.js" as SpecModule
|
||||||
|
|
||||||
|
var PIN_KEYS = ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"];
|
||||||
|
|
||||||
|
function extractPins(obj) {
|
||||||
|
if (!obj) return null;
|
||||||
|
|
||||||
|
var pins = null;
|
||||||
|
for (var i = 0; i < PIN_KEYS.length; i++) {
|
||||||
|
var value = obj[PIN_KEYS[i]];
|
||||||
|
if (!value || Object.keys(value).length === 0) continue;
|
||||||
|
if (!pins) pins = {};
|
||||||
|
pins[PIN_KEYS[i]] = value;
|
||||||
|
}
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
|
||||||
function parse(root, jsonObj) {
|
function parse(root, jsonObj) {
|
||||||
var SPEC = SpecModule.SPEC;
|
var SPEC = SpecModule.SPEC;
|
||||||
|
|
||||||
@@ -263,28 +278,16 @@ function migrateToVersion(obj, targetVersion) {
|
|||||||
settings.configVersion = 12;
|
settings.configVersion = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings;
|
if (currentVersion < 13) {
|
||||||
}
|
console.info("Migrating settings from version", currentVersion, "to version 13");
|
||||||
|
console.info("Moving device and network pins to cache.json");
|
||||||
|
|
||||||
function cleanup(fileText) {
|
for (var p = 0; p < PIN_KEYS.length; p++) {
|
||||||
var getValidKeys = SpecModule.getValidKeys;
|
delete settings[PIN_KEYS[p]];
|
||||||
if (!fileText || !fileText.trim()) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var settings = JSON.parse(fileText);
|
|
||||||
var validKeys = getValidKeys();
|
|
||||||
var needsSave = false;
|
|
||||||
|
|
||||||
for (var key in settings) {
|
|
||||||
if (validKeys.indexOf(key) < 0) {
|
|
||||||
delete settings[key];
|
|
||||||
needsSave = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return needsSave ? JSON.stringify(settings, null, 2) : null;
|
settings.configVersion = 13;
|
||||||
} catch (e) {
|
|
||||||
console.warn("SettingsData: Failed to cleanup unused keys:", e.message);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,11 +122,6 @@ const times = [
|
|||||||
|
|
||||||
// adds a custom time to the times config
|
// adds a custom time to the times config
|
||||||
|
|
||||||
function addTime(angle, riseName, setName) {
|
|
||||||
times.push([angle, riseName, setName]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// calculations for sun times
|
// calculations for sun times
|
||||||
|
|
||||||
const J0 = 0.0009;
|
const J0 = 0.0009;
|
||||||
@@ -246,63 +241,3 @@ function getMoonIllumination(date) {
|
|||||||
angle
|
angle
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function hoursLater(date, h) {
|
|
||||||
return new Date(date.valueOf() + h * dayMs / 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
|
||||||
|
|
||||||
function getMoonTimes(date, lat, lng, inUTC) {
|
|
||||||
const t = new Date(date);
|
|
||||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
|
||||||
else t.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const hc = 0.133 * rad;
|
|
||||||
let h0 = getMoonPosition(t, lat, lng).altitude - hc,
|
|
||||||
rise, set, ye;
|
|
||||||
|
|
||||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
|
||||||
for (let i = 1; i <= 24; i += 2) {
|
|
||||||
const h1 = getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
|
||||||
const h2 = getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
|
||||||
const a = (h0 + h2) / 2 - h1;
|
|
||||||
const b = (h2 - h0) / 2;
|
|
||||||
const xe = -b / (2 * a);
|
|
||||||
const d = b * b - 4 * a * h1;
|
|
||||||
let roots = 0, x1 = 0, x2 = 0;
|
|
||||||
ye = (a * xe + b) * xe + h1;
|
|
||||||
|
|
||||||
if (d >= 0) {
|
|
||||||
const dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
|
||||||
x1 = xe - dx;
|
|
||||||
x2 = xe + dx;
|
|
||||||
if (Math.abs(x1) <= 1) roots++;
|
|
||||||
if (Math.abs(x2) <= 1) roots++;
|
|
||||||
if (x1 < -1) x1 = x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roots === 1) {
|
|
||||||
if (h0 < 0) rise = i + x1;
|
|
||||||
else set = i + x1;
|
|
||||||
|
|
||||||
} else if (roots === 2) {
|
|
||||||
rise = i + (ye < 0 ? x2 : x1);
|
|
||||||
set = i + (ye < 0 ? x1 : x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rise && set) break;
|
|
||||||
|
|
||||||
h0 = h2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {};
|
|
||||||
|
|
||||||
if (rise) result.rise = hoursLater(t, rise);
|
|
||||||
if (set) result.set = hoursLater(t, set);
|
|
||||||
|
|
||||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("DMSShell")
|
readonly property var log: Log.scoped("DMSShell")
|
||||||
readonly property var _sessionsServiceRef: SessionsService
|
readonly property var _sessionsServiceRef: SessionsService
|
||||||
|
// Keep the optional integration service alive
|
||||||
|
readonly property var _dankConnectServiceRef: DankConnectService
|
||||||
|
|
||||||
property var core: null
|
property var core: null
|
||||||
|
|
||||||
@@ -233,6 +235,11 @@ Item {
|
|||||||
PolkitService.polkitAvailable;
|
PolkitService.polkitAvailable;
|
||||||
DisplayConfigState.hasOutputBackend;
|
DisplayConfigState.hasOutputBackend;
|
||||||
PortalService.systemColorScheme;
|
PortalService.systemColorScheme;
|
||||||
|
IconThemeService.revision;
|
||||||
|
DesktopService.isSystemd;
|
||||||
|
TrashService.count;
|
||||||
|
WallpaperCyclingService.cyclingActive;
|
||||||
|
ThemeAutoService.active;
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -314,6 +321,17 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: BarWidgetService
|
||||||
|
property: "dockContextMenu"
|
||||||
|
value: dockContextMenuLoader.item
|
||||||
|
}
|
||||||
|
Binding {
|
||||||
|
target: BarWidgetService
|
||||||
|
property: "dockTrashContextMenu"
|
||||||
|
value: dockTrashContextMenuLoader.item
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onDockShowTrashChanged() {
|
function onDockShowTrashChanged() {
|
||||||
|
|||||||
+58
-27
@@ -285,28 +285,66 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _resolvePosition(position) {
|
||||||
|
switch ((position || "").toLowerCase()) {
|
||||||
|
case "left":
|
||||||
|
return "left";
|
||||||
|
case "center":
|
||||||
|
return "center";
|
||||||
|
case "right":
|
||||||
|
return "right";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dashBar(position) {
|
||||||
|
if (position)
|
||||||
|
return root.getPreferredBar();
|
||||||
|
return root.getPreferredBar("clockButtonRef") || root.getPreferredBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _openDash(tab, position) {
|
||||||
|
const bar = _dashBar(position);
|
||||||
|
if (!bar)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const tabId = _resolveTabId(tab);
|
||||||
|
const dash = root.dankDashPopoutLoader.item;
|
||||||
|
if (dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name) {
|
||||||
|
if (position && bar.positionDash)
|
||||||
|
bar.positionDash(dash, position);
|
||||||
|
dash.requestTab(tabId);
|
||||||
|
if (dash.updateSurfacePosition)
|
||||||
|
dash.updateSurfacePosition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bar.triggerDashTab(tabId, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _toggleDash(tab, position) {
|
||||||
|
if (root.dankDashPopoutLoader.item?.dashVisible) {
|
||||||
|
root.dankDashPopoutLoader.item.dashVisible = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bar = _dashBar(position);
|
||||||
|
if (!bar)
|
||||||
|
return false;
|
||||||
|
return bar.triggerDashTab(_resolveTabId(tab), position);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveTabIndex(tab: string): int {
|
function resolveTabIndex(tab: string): int {
|
||||||
return SettingsData.dashTabIndexForId(_resolveTabId(tab));
|
return SettingsData.dashTabIndexForId(_resolveTabId(tab));
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(tab: string): string {
|
function open(tab: string): string {
|
||||||
const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar();
|
return _openDash(tab, "") ? "DASH_OPEN_SUCCESS" : "DASH_OPEN_FAILED";
|
||||||
if (!bar)
|
}
|
||||||
return "DASH_OPEN_FAILED";
|
|
||||||
|
|
||||||
const tabId = _resolveTabId(tab);
|
function openAt(tab: string, position: string): string {
|
||||||
const dash = root.dankDashPopoutLoader.item;
|
return _openDash(tab, _resolvePosition(position)) ? "DASH_OPEN_SUCCESS" : "DASH_OPEN_FAILED";
|
||||||
if (dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name) {
|
|
||||||
dash.requestTab(tabId);
|
|
||||||
if (dash.updateSurfacePosition)
|
|
||||||
dash.updateSurfacePosition();
|
|
||||||
return "DASH_OPEN_SUCCESS";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bar.triggerDashTab(tabId))
|
|
||||||
return "DASH_OPEN_FAILED";
|
|
||||||
|
|
||||||
return "DASH_OPEN_SUCCESS";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(): string {
|
function close(): string {
|
||||||
@@ -318,18 +356,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle(tab: string): string {
|
function toggle(tab: string): string {
|
||||||
if (root.dankDashPopoutLoader.item?.dashVisible) {
|
return _toggleDash(tab, "") ? "DASH_TOGGLE_SUCCESS" : "DASH_TOGGLE_FAILED";
|
||||||
root.dankDashPopoutLoader.item.dashVisible = false;
|
}
|
||||||
return "DASH_TOGGLE_SUCCESS";
|
|
||||||
}
|
|
||||||
|
|
||||||
const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar();
|
function toggleAt(tab: string, position: string): string {
|
||||||
if (bar) {
|
return _toggleDash(tab, _resolvePosition(position)) ? "DASH_TOGGLE_SUCCESS" : "DASH_TOGGLE_FAILED";
|
||||||
if (!bar.triggerDashTab(_resolveTabId(tab)))
|
|
||||||
return "DASH_TOGGLE_FAILED";
|
|
||||||
return "DASH_TOGGLE_SUCCESS";
|
|
||||||
}
|
|
||||||
return "DASH_TOGGLE_FAILED";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: "dash"
|
target: "dash"
|
||||||
|
|||||||
@@ -314,26 +314,20 @@ Item {
|
|||||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
|
|
||||||
function _frameEdgeInset(side) {
|
|
||||||
if (!effectiveScreen)
|
|
||||||
return 0;
|
|
||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real _connectedAlignedX: {
|
readonly property real _connectedAlignedX: {
|
||||||
switch (resolvedConnectedBarSide) {
|
switch (resolvedConnectedBarSide) {
|
||||||
case "top":
|
case "top":
|
||||||
case "bottom":
|
case "bottom":
|
||||||
{
|
{
|
||||||
const insetL = _frameEdgeInset("left");
|
const insetL = SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
|
||||||
const insetR = _frameEdgeInset("right");
|
const insetR = SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
|
||||||
const usable = Math.max(0, screenWidth - insetL - insetR);
|
const usable = Math.max(0, screenWidth - insetL - insetR);
|
||||||
return insetL + Math.max(0, (usable - alignedWidth) / 2);
|
return insetL + Math.max(0, (usable - alignedWidth) / 2);
|
||||||
}
|
}
|
||||||
case "left":
|
case "left":
|
||||||
return _frameEdgeInset("left");
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
|
||||||
case "right":
|
case "right":
|
||||||
return screenWidth - alignedWidth - _frameEdgeInset("right");
|
return screenWidth - alignedWidth - SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -341,14 +335,14 @@ Item {
|
|||||||
readonly property real _connectedAlignedY: {
|
readonly property real _connectedAlignedY: {
|
||||||
switch (resolvedConnectedBarSide) {
|
switch (resolvedConnectedBarSide) {
|
||||||
case "top":
|
case "top":
|
||||||
return _frameEdgeInset("top");
|
return SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
|
||||||
case "bottom":
|
case "bottom":
|
||||||
return screenHeight - alignedHeight - _frameEdgeInset("bottom");
|
return screenHeight - alignedHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
|
||||||
case "left":
|
case "left":
|
||||||
case "right":
|
case "right":
|
||||||
{
|
{
|
||||||
const insetT = _frameEdgeInset("top");
|
const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
|
||||||
const insetB = _frameEdgeInset("bottom");
|
const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
|
||||||
const usable = Math.max(0, screenHeight - insetT - insetB);
|
const usable = Math.max(0, screenHeight - insetT - insetB);
|
||||||
return insetT + Math.max(0, (usable - alignedHeight) / 2);
|
return insetT + Math.max(0, (usable - alignedHeight) / 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,12 +105,6 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
|
readonly property bool _dockBlocksEmergence: frameOwnsConnectedChrome && _dockOccupiesSide(resolvedConnectedBarSide)
|
||||||
|
|
||||||
function _frameEdgeInset(side) {
|
|
||||||
if (!effectiveScreen)
|
|
||||||
return 0;
|
|
||||||
return SettingsData.frameEdgeInsetForSide(effectiveScreen, side);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var _connectedModalPos: {
|
readonly property var _connectedModalPos: {
|
||||||
const fallback = {
|
const fallback = {
|
||||||
"x": (screenWidth - modalWidth) / 2,
|
"x": (screenWidth - modalWidth) / 2,
|
||||||
@@ -120,10 +114,10 @@ Item {
|
|||||||
case "top":
|
case "top":
|
||||||
case "bottom":
|
case "bottom":
|
||||||
{
|
{
|
||||||
const insetL = _frameEdgeInset("left");
|
const insetL = SettingsData.frameEdgeInsetForSide(effectiveScreen, "left");
|
||||||
const insetR = _frameEdgeInset("right");
|
const insetR = SettingsData.frameEdgeInsetForSide(effectiveScreen, "right");
|
||||||
const insetT = _frameEdgeInset("top");
|
const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
|
||||||
const insetB = _frameEdgeInset("bottom");
|
const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
|
||||||
const usable = Math.max(0, screenWidth - insetL - insetR);
|
const usable = Math.max(0, screenWidth - insetL - insetR);
|
||||||
const usableH = Math.max(0, screenHeight - insetT - insetB);
|
const usableH = Math.max(0, screenHeight - insetT - insetB);
|
||||||
return {
|
return {
|
||||||
@@ -134,11 +128,11 @@ Item {
|
|||||||
case "left":
|
case "left":
|
||||||
case "right":
|
case "right":
|
||||||
{
|
{
|
||||||
const insetT = _frameEdgeInset("top");
|
const insetT = SettingsData.frameEdgeInsetForSide(effectiveScreen, "top");
|
||||||
const insetB = _frameEdgeInset("bottom");
|
const insetB = SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom");
|
||||||
const usable = Math.max(0, screenHeight - insetT - insetB);
|
const usable = Math.max(0, screenHeight - insetT - insetB);
|
||||||
return {
|
return {
|
||||||
"x": resolvedConnectedBarSide === "left" ? _frameEdgeInset("left") : screenWidth - modalWidth - _frameEdgeInset("right"),
|
"x": resolvedConnectedBarSide === "left" ? SettingsData.frameEdgeInsetForSide(effectiveScreen, "left") : screenWidth - modalWidth - SettingsData.frameEdgeInsetForSide(effectiveScreen, "right"),
|
||||||
"y": insetT + Math.max(0, (usable - modalHeight) / 2)
|
"y": insetT + Math.max(0, (usable - modalHeight) / 2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -188,16 +182,16 @@ Item {
|
|||||||
readonly property real _connectedChromeY: {
|
readonly property real _connectedChromeY: {
|
||||||
if (!launcherArcExtenderActive)
|
if (!launcherArcExtenderActive)
|
||||||
return alignedY;
|
return alignedY;
|
||||||
return resolvedConnectedBarSide === "top" ? Theme.snap(_frameEdgeInset("top"), dpr) : alignedY;
|
return resolvedConnectedBarSide === "top" ? Theme.snap(SettingsData.frameEdgeInsetForSide(effectiveScreen, "top"), dpr) : alignedY;
|
||||||
}
|
}
|
||||||
readonly property real _connectedChromeWidth: alignedWidth
|
readonly property real _connectedChromeWidth: alignedWidth
|
||||||
readonly property real _connectedChromeHeight: {
|
readonly property real _connectedChromeHeight: {
|
||||||
if (!launcherArcExtenderActive)
|
if (!launcherArcExtenderActive)
|
||||||
return alignedHeight;
|
return alignedHeight;
|
||||||
if (resolvedConnectedBarSide === "top")
|
if (resolvedConnectedBarSide === "top")
|
||||||
return Theme.snap(Math.max(alignedHeight, alignedY + alignedHeight - _frameEdgeInset("top")), dpr);
|
return Theme.snap(Math.max(alignedHeight, alignedY + alignedHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "top")), dpr);
|
||||||
if (resolvedConnectedBarSide === "bottom")
|
if (resolvedConnectedBarSide === "bottom")
|
||||||
return Theme.snap(Math.max(alignedHeight, screenHeight - _frameEdgeInset("bottom") - alignedY), dpr);
|
return Theme.snap(Math.max(alignedHeight, screenHeight - SettingsData.frameEdgeInsetForSide(effectiveScreen, "bottom") - alignedY), dpr);
|
||||||
return alignedHeight;
|
return alignedHeight;
|
||||||
}
|
}
|
||||||
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
readonly property real contentSurfaceHeight: launcherArcExtenderActive ? _connectedChromeHeight : alignedHeight
|
||||||
|
|||||||
@@ -89,15 +89,6 @@ function fuzzyScore(text, query) {
|
|||||||
return bestScore
|
return bestScore
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimeBucketWeight(daysSinceUsed) {
|
|
||||||
for (var i = 0; i < TimeBuckets.length; i++) {
|
|
||||||
if (daysSinceUsed <= TimeBuckets[i].maxDays) {
|
|
||||||
return TimeBuckets[i].weight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateTextScore(name, query) {
|
function calculateTextScore(name, query) {
|
||||||
if (name === query) return Weights.exactMatch
|
if (name === query) return Weights.exactMatch
|
||||||
if (name.startsWith(query)) return Weights.prefixMatch
|
if (name.startsWith(query)) return Weights.prefixMatch
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ DankModal {
|
|||||||
NotificationService.dismissAllPopups();
|
NotificationService.dismissAllPopups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dismissLastNotification() {
|
||||||
|
NotificationService.dismissLastNotification();
|
||||||
|
}
|
||||||
|
|
||||||
modalWidth: Math.min(500, screenWidth - 48)
|
modalWidth: Math.min(500, screenWidth - 48)
|
||||||
modalHeight: Math.min(700, screenHeight * 0.85)
|
modalHeight: Math.min(700, screenHeight * 0.85)
|
||||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
@@ -190,6 +194,11 @@ DankModal {
|
|||||||
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
|
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dismiss(): string {
|
||||||
|
notificationModal.dismissLastNotification();
|
||||||
|
return "NOTIFICATION_DISMISS_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
target: "notifications"
|
target: "notifications"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ DankModal {
|
|||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPowerProfileDescription(profile) {
|
||||||
|
switch (profile) {
|
||||||
|
case 0:
|
||||||
|
return I18n.tr("Extend battery life", "power profile description");
|
||||||
|
case 1:
|
||||||
|
return I18n.tr("Balance power and performance", "power profile description");
|
||||||
|
case 2:
|
||||||
|
return I18n.tr("Prioritize performance", "power profile description");
|
||||||
|
default:
|
||||||
|
return I18n.tr("Custom power profile", "power profile description");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hideDialog() {
|
function hideDialog() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@@ -244,7 +257,7 @@ DankModal {
|
|||||||
|
|
||||||
// Selected power profile description
|
// Selected power profile description
|
||||||
StyledText {
|
StyledText {
|
||||||
text: (root.selectedIndex >= 0 && root.selectedIndex < root.profileModel.length) ? Theme.getPowerProfileDescription(root.profileModel[root.selectedIndex]) : ""
|
text: (root.selectedIndex >= 0 && root.selectedIndex < root.profileModel.length) ? root.getPowerProfileDescription(root.profileModel[root.selectedIndex]) : ""
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceTextMedium
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import qs.Common
|
|||||||
import qs.Modules.ProcessList
|
import qs.Modules.ProcessList
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../Common/Format.js" as Format
|
||||||
|
|
||||||
FloatingWindow {
|
FloatingWindow {
|
||||||
id: processListModal
|
id: processListModal
|
||||||
@@ -67,16 +68,6 @@ FloatingWindow {
|
|||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
|
||||||
if (bytes < 1024)
|
|
||||||
return bytes.toFixed(0) + " B/s";
|
|
||||||
if (bytes < 1024 * 1024)
|
|
||||||
return (bytes / 1024).toFixed(1) + " KB/s";
|
|
||||||
if (bytes < 1024 * 1024 * 1024)
|
|
||||||
return (bytes / (1024 * 1024)).toFixed(1) + " MB/s";
|
|
||||||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextTab() {
|
function nextTab() {
|
||||||
currentTab = (currentTab + 1) % 4;
|
currentTab = (currentTab + 1) % 4;
|
||||||
}
|
}
|
||||||
@@ -541,7 +532,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "↓" + formatBytes(DgopService.networkRxRate) + " ↑" + formatBytes(DgopService.networkTxRate)
|
text: "↓" + Format.formatRate(DgopService.networkRxRate) + " ↑" + Format.formatRate(DgopService.networkTxRate)
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.family: SettingsData.monoFontFamily
|
font.family: SettingsData.monoFontFamily
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -559,7 +550,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "↓" + formatBytes(DgopService.diskReadRate) + " ↑" + formatBytes(DgopService.diskWriteRate)
|
text: "↓" + Format.formatRate(DgopService.diskReadRate) + " ↑" + Format.formatRate(DgopService.diskWriteRate)
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.family: SettingsData.monoFontFamily
|
font.family: SettingsData.monoFontFamily
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
|
|||||||
@@ -718,5 +718,20 @@ FocusScope {
|
|||||||
Qt.callLater(() => item.forceActiveFocus());
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: mouseTouchpadLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.currentIndex === 44
|
||||||
|
visible: active
|
||||||
|
focus: active
|
||||||
|
|
||||||
|
sourceComponent: MouseTouchpadTab {}
|
||||||
|
|
||||||
|
onActiveChanged: {
|
||||||
|
if (active && item)
|
||||||
|
Qt.callLater(() => item.forceActiveFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ FloatingWindow {
|
|||||||
signal closingModal
|
signal closingModal
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
|
if (visible && !backingWindowVisible) {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +52,11 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
visible = !visible;
|
if (visible && backingWindowVisible) {
|
||||||
|
hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTabIndex(tabIndex: int) {
|
function setTabIndex(tabIndex: int) {
|
||||||
@@ -61,13 +68,12 @@ FloatingWindow {
|
|||||||
|
|
||||||
function showWithTab(tabIndex: int) {
|
function showWithTab(tabIndex: int) {
|
||||||
setTabIndex(tabIndex);
|
setTabIndex(tabIndex);
|
||||||
visible = true;
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithTabName(tabName: string) {
|
function showWithTabName(tabName: string) {
|
||||||
var idx = sidebar.resolveTabIndex(tabName);
|
setTabIndex(sidebar.resolveTabIndex(tabName));
|
||||||
setTabIndex(idx);
|
show();
|
||||||
visible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTabIndex(tabName: string): int {
|
function resolveTabIndex(tabName: string): int {
|
||||||
|
|||||||
@@ -319,6 +319,13 @@ Rectangle {
|
|||||||
"icon": "headphones",
|
"icon": "headphones",
|
||||||
"tabIndex": 29
|
"tabIndex": 29
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mouse_touchpad",
|
||||||
|
"text": I18n.tr("Mouse & Touchpad"),
|
||||||
|
"icon": "mouse",
|
||||||
|
"tabIndex": 44,
|
||||||
|
"niriOnly": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "locale",
|
"id": "locale",
|
||||||
"text": I18n.tr("Locale"),
|
"text": I18n.tr("Locale"),
|
||||||
|
|||||||
@@ -25,29 +25,6 @@ Item {
|
|||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
|
|
||||||
function getFillMode(modeName) {
|
|
||||||
switch (modeName) {
|
|
||||||
case "Stretch":
|
|
||||||
return Image.Stretch;
|
|
||||||
case "Fit":
|
|
||||||
case "PreserveAspectFit":
|
|
||||||
return Image.PreserveAspectFit;
|
|
||||||
case "Fill":
|
|
||||||
case "PreserveAspectCrop":
|
|
||||||
return Image.PreserveAspectCrop;
|
|
||||||
case "Tile":
|
|
||||||
return Image.Tile;
|
|
||||||
case "TileVertically":
|
|
||||||
return Image.TileVertically;
|
|
||||||
case "TileHorizontally":
|
|
||||||
return Image.TileHorizontally;
|
|
||||||
case "Pad":
|
|
||||||
return Image.Pad;
|
|
||||||
default:
|
|
||||||
return Image.PreserveAspectCrop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (initialSource && initialSource !== wallpaperSource && !(CompositorService.isNiri && SessionData.isSwitchingMode)) {
|
if (initialSource && initialSource !== wallpaperSource && !(CompositorService.isNiri && SessionData.isSwitchingMode)) {
|
||||||
currentWallpaper.source = initialSource;
|
currentWallpaper.source = initialSource;
|
||||||
@@ -144,7 +121,7 @@ Item {
|
|||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: root.blurTextureSize
|
sourceSize: root.blurTextureSize
|
||||||
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
|
fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Image.Error) {
|
if (status === Image.Error) {
|
||||||
@@ -166,7 +143,7 @@ Item {
|
|||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: root.blurTextureSize
|
sourceSize: root.blurTextureSize
|
||||||
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
|
fillMode: Theme.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Image.Error) {
|
if (status === Image.Error) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Quickshell
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../Common/Format.js" as Format
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -188,16 +189,6 @@ Item {
|
|||||||
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
|
return DgopService.availableGpus.find(g => g.pciId === selectedGpuPciId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
|
||||||
if (bytes < 1024)
|
|
||||||
return bytes.toFixed(0) + "B";
|
|
||||||
if (bytes < 1024 * 1024)
|
|
||||||
return (bytes / 1024).toFixed(0) + "K";
|
|
||||||
if (bytes < 1024 * 1024 * 1024)
|
|
||||||
return (bytes / (1024 * 1024)).toFixed(1) + "M";
|
|
||||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + "G";
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMemKB(kb) {
|
function formatMemKB(kb) {
|
||||||
if (kb < 1024)
|
if (kb < 1024)
|
||||||
return kb.toFixed(0) + "K";
|
return kb.toFixed(0) + "K";
|
||||||
@@ -206,26 +197,18 @@ Item {
|
|||||||
return (kb / (1024 * 1024)).toFixed(1) + "G";
|
return (kb / (1024 * 1024)).toFixed(1) + "G";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToHistory(arr, val) {
|
|
||||||
var newArr = arr.slice();
|
|
||||||
newArr.push(val);
|
|
||||||
if (newArr.length > historySize)
|
|
||||||
newArr.shift();
|
|
||||||
return newArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleData() {
|
function sampleData() {
|
||||||
if (showCpuGraph)
|
if (showCpuGraph)
|
||||||
cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
|
cpuHistory = Format.addToHistory(cpuHistory, DgopService.cpuUsage, historySize);
|
||||||
if (showMemoryGraph)
|
if (showMemoryGraph)
|
||||||
memHistory = addToHistory(memHistory, DgopService.memoryUsage);
|
memHistory = Format.addToHistory(memHistory, DgopService.memoryUsage, historySize);
|
||||||
if (showNetworkGraph) {
|
if (showNetworkGraph) {
|
||||||
netRxHistory = addToHistory(netRxHistory, DgopService.networkRxRate);
|
netRxHistory = Format.addToHistory(netRxHistory, DgopService.networkRxRate, historySize);
|
||||||
netTxHistory = addToHistory(netTxHistory, DgopService.networkTxRate);
|
netTxHistory = Format.addToHistory(netTxHistory, DgopService.networkTxRate, historySize);
|
||||||
}
|
}
|
||||||
if (showDisk) {
|
if (showDisk) {
|
||||||
diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
|
diskReadHistory = Format.addToHistory(diskReadHistory, DgopService.diskReadRate, historySize);
|
||||||
diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
|
diskWriteHistory = Format.addToHistory(diskWriteHistory, DgopService.diskWriteRate, historySize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +502,7 @@ Item {
|
|||||||
color: root.accentColor
|
color: root.accentColor
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
text: root.formatBytes(DgopService.networkRxRate) + "/s"
|
text: Format.formatBytes(DgopService.networkRxRate) + "/s"
|
||||||
isMonospace: true
|
isMonospace: true
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
@@ -533,7 +516,7 @@ Item {
|
|||||||
color: root.dimColor
|
color: root.dimColor
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
text: root.formatBytes(DgopService.networkTxRate) + "/s"
|
text: Format.formatBytes(DgopService.networkTxRate) + "/s"
|
||||||
isMonospace: true
|
isMonospace: true
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
@@ -552,7 +535,7 @@ Item {
|
|||||||
color: root.accentColor
|
color: root.accentColor
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
text: root.formatBytes(DgopService.diskReadRate) + "/s"
|
text: Format.formatBytes(DgopService.diskReadRate) + "/s"
|
||||||
isMonospace: true
|
isMonospace: true
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
@@ -566,7 +549,7 @@ Item {
|
|||||||
color: root.dimColor
|
color: root.dimColor
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
text: root.formatBytes(DgopService.diskWriteRate) + "/s"
|
text: Format.formatBytes(DgopService.diskWriteRate) + "/s"
|
||||||
isMonospace: true
|
isMonospace: true
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: root.textColor
|
color: root.textColor
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Modules.ControlCenter.Details
|
||||||
import qs.Modules.Plugins
|
import qs.Modules.Plugins
|
||||||
|
|
||||||
PluginComponent {
|
PluginComponent {
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
LayoutMirroring.enabled: I18n.isRtl
|
|
||||||
LayoutMirroring.childrenInherit: true
|
|
||||||
|
|
||||||
property string iconName: ""
|
|
||||||
property string text: ""
|
|
||||||
property string secondaryText: ""
|
|
||||||
property bool isActive: false
|
|
||||||
property int widgetIndex: 0
|
|
||||||
property var widgetData: null
|
|
||||||
property bool editMode: false
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
|
|
||||||
width: parent ? parent.width : 200
|
|
||||||
height: 60
|
|
||||||
radius: {
|
|
||||||
if (Theme.cornerRadius === 0)
|
|
||||||
return 0;
|
|
||||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
|
||||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
|
||||||
readonly property color _tileRingActive: Theme.ccTileRing
|
|
||||||
|
|
||||||
color: isActive ? _tileBgActive : _tileBgInactive
|
|
||||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
|
||||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
|
||||||
opacity: enabled ? 1.0 : 0.6
|
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: mouseArea.containsMouse ? hoverTint(root.color) : Theme.withAlpha(hoverTint(root.color), 0)
|
|
||||||
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: Theme.spacingL + 2
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.iconName
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: isActive ? Theme.ccTileActiveText : Theme.ccTileInactiveIcon
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width - Theme.iconSize - parent.spacing
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingXXS
|
|
||||||
|
|
||||||
Typography {
|
|
||||||
width: parent.width
|
|
||||||
text: root.text
|
|
||||||
style: Typography.Style.Body
|
|
||||||
color: isActive ? Theme.ccTileActiveText : Theme.surfaceText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
Typography {
|
|
||||||
width: parent.width
|
|
||||||
text: root.secondaryText
|
|
||||||
style: Typography.Style.Caption
|
|
||||||
color: isActive ? Theme.ccTileActiveText : Theme.surfaceVariantText
|
|
||||||
visible: text.length > 0
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankRipple {
|
|
||||||
id: ripple
|
|
||||||
cornerRadius: root.radius
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: root.enabled
|
|
||||||
onPressed: mouse => ripple.trigger(mouse.x, mouse.y)
|
|
||||||
onClicked: root.clicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on radius {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Modules.ControlCenter.Details
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string expandedSection: ""
|
|
||||||
property var expandedWidgetData: null
|
|
||||||
|
|
||||||
height: active ? 250 : 0
|
|
||||||
visible: active
|
|
||||||
|
|
||||||
readonly property bool active: expandedSection !== ""
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: Theme.spacingS
|
|
||||||
sourceComponent: {
|
|
||||||
if (!root.active)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (expandedSection.startsWith("diskUsage_")) {
|
|
||||||
return diskUsageDetailComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (expandedSection) {
|
|
||||||
case "wifi":
|
|
||||||
return networkDetailComponent;
|
|
||||||
case "bluetooth":
|
|
||||||
return bluetoothDetailComponent;
|
|
||||||
case "audioOutput":
|
|
||||||
return audioOutputDetailComponent;
|
|
||||||
case "audioInput":
|
|
||||||
return audioInputDetailComponent;
|
|
||||||
case "battery":
|
|
||||||
return batteryDetailComponent;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: networkDetailComponent
|
|
||||||
NetworkDetail {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: bluetoothDetailComponent
|
|
||||||
BluetoothDetail {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: audioOutputDetailComponent
|
|
||||||
AudioOutputDetail {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: audioInputDetailComponent
|
|
||||||
AudioInputDetail {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: batteryDetailComponent
|
|
||||||
BatteryDetail {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: diskUsageDetailComponent
|
|
||||||
DiskUsageDetail {
|
|
||||||
currentMountPath: root.expandedWidgetData?.mountPath || "/"
|
|
||||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
|
||||||
|
|
||||||
onMountPathChanged: newMountPath => {
|
|
||||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
|
||||||
const widgets = SettingsData.controlCenterWidgets || [];
|
|
||||||
const newWidgets = widgets.map(w => {
|
|
||||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
|
||||||
const updatedWidget = Object.assign({}, w);
|
|
||||||
updatedWidget.mountPath = newMountPath;
|
|
||||||
return updatedWidget;
|
|
||||||
}
|
|
||||||
return w;
|
|
||||||
});
|
|
||||||
SettingsData.set("controlCenterWidgets", newWidgets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
LayoutMirroring.enabled: I18n.isRtl
|
|
||||||
LayoutMirroring.childrenInherit: true
|
|
||||||
|
|
||||||
property string iconName: ""
|
|
||||||
property string text: ""
|
|
||||||
|
|
||||||
signal pressed
|
|
||||||
|
|
||||||
height: 34
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: mouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceVariant, 0.5)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.iconName
|
|
||||||
size: Theme.fontSizeSmall
|
|
||||||
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Typography {
|
|
||||||
text: root.text
|
|
||||||
style: Typography.Style.Button
|
|
||||||
color: mouseArea.containsMouse ? Theme.primary : Theme.surfaceText
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DankRipple {
|
|
||||||
id: ripple
|
|
||||||
cornerRadius: root.radius
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onPressed: mouse => {
|
|
||||||
ripple.trigger(mouse.x, mouse.y);
|
|
||||||
root.pressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import Quickshell.Services.Pipewire
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../../Common/QmlUtils.js" as QmlUtils
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -150,17 +151,9 @@ Rectangle {
|
|||||||
|
|
||||||
property int maxPinnedInputs: 3
|
property int maxPinnedInputs: 3
|
||||||
|
|
||||||
function normalizePinList(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value.filter(v => v);
|
|
||||||
if (typeof value === "string" && value.length > 0)
|
|
||||||
return [value];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPinnedInputs() {
|
function getPinnedInputs() {
|
||||||
const pins = SettingsData.audioInputDevicePins || {};
|
const pins = CacheData.audioInputDevicePins || {};
|
||||||
return normalizePinList(pins["preferredInput"]);
|
return QmlUtils.normalizePinList(pins["preferredInput"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -315,8 +308,8 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.audioInputDevicePins || {}));
|
||||||
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
|
let pinnedList = QmlUtils.normalizePinList(pins["preferredInput"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||||
|
|
||||||
if (pinIndex !== -1) {
|
if (pinIndex !== -1) {
|
||||||
@@ -332,7 +325,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredInput"];
|
delete pins["preferredInput"];
|
||||||
|
|
||||||
SettingsData.set("audioInputDevicePins", pins);
|
CacheData.set("audioInputDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Quickshell.Services.Pipewire
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../../Common/QmlUtils.js" as QmlUtils
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -160,17 +161,9 @@ Rectangle {
|
|||||||
|
|
||||||
property int maxPinnedOutputs: 3
|
property int maxPinnedOutputs: 3
|
||||||
|
|
||||||
function normalizePinList(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value.filter(v => v);
|
|
||||||
if (typeof value === "string" && value.length > 0)
|
|
||||||
return [value];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPinnedOutputs() {
|
function getPinnedOutputs() {
|
||||||
const pins = SettingsData.audioOutputDevicePins || {};
|
const pins = CacheData.audioOutputDevicePins || {};
|
||||||
return normalizePinList(pins["preferredOutput"]);
|
return QmlUtils.normalizePinList(pins["preferredOutput"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -324,8 +317,8 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.audioOutputDevicePins || {}));
|
||||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
|
let pinnedList = QmlUtils.normalizePinList(pins["preferredOutput"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||||
|
|
||||||
if (pinIndex !== -1) {
|
if (pinIndex !== -1) {
|
||||||
@@ -341,7 +334,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredOutput"];
|
delete pins["preferredOutput"];
|
||||||
|
|
||||||
SettingsData.set("audioOutputDevicePins", pins);
|
CacheData.set("audioOutputDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Quickshell.Bluetooth
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../../Common/QmlUtils.js" as QmlUtils
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -95,17 +96,9 @@ Rectangle {
|
|||||||
BluetoothService.updateDeviceCodec(deviceAddress, codecName);
|
BluetoothService.updateDeviceCodec(deviceAddress, codecName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePinList(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value.filter(v => v);
|
|
||||||
if (typeof value === "string" && value.length > 0)
|
|
||||||
return [value];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPinnedDevices() {
|
function getPinnedDevices() {
|
||||||
const pins = SettingsData.bluetoothDevicePins || {};
|
const pins = CacheData.bluetoothDevicePins || {};
|
||||||
return normalizePinList(pins["preferredDevice"]);
|
return QmlUtils.normalizePinList(pins["preferredDevice"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -395,8 +388,8 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.bluetoothDevicePins || {}));
|
||||||
let pinnedList = root.normalizePinList(pins["preferredDevice"]);
|
let pinnedList = QmlUtils.normalizePinList(pins["preferredDevice"]);
|
||||||
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
|
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
|
||||||
|
|
||||||
if (pinIndex !== -1) {
|
if (pinIndex !== -1) {
|
||||||
@@ -413,7 +406,7 @@ Rectangle {
|
|||||||
delete pins["preferredDevice"];
|
delete pins["preferredDevice"];
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsData.set("bluetoothDevicePins", pins);
|
CacheData.set("bluetoothDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Rectangle {
|
|||||||
|
|
||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (pinKey.length > 0) {
|
if (pinKey.length > 0) {
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const pinnedDevice = pins[pinKey];
|
const pinnedDevice = pins[pinKey];
|
||||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||||
const found = devices.find(d => d.name === pinnedDevice);
|
const found = devices.find(d => d.name === pinnedDevice);
|
||||||
@@ -85,12 +85,12 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (pinKey.length > 0) {
|
if (pinKey.length > 0) {
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const existing = pins[pinKey];
|
const existing = pins[pinKey];
|
||||||
if (existing && existing !== deviceName) {
|
if (existing && existing !== deviceName) {
|
||||||
const next = JSON.parse(JSON.stringify(pins));
|
const next = JSON.parse(JSON.stringify(pins));
|
||||||
delete next[pinKey];
|
delete next[pinKey];
|
||||||
SettingsData.set("brightnessDevicePins", next);
|
CacheData.set("brightnessDevicePins", next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.currentDeviceName = deviceName;
|
root.currentDeviceName = deviceName;
|
||||||
@@ -106,7 +106,7 @@ Rectangle {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey || !deviceName)
|
if (!pinKey || !deviceName)
|
||||||
return false;
|
return false;
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
return pins[pinKey] === deviceName;
|
return pins[pinKey] === deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +114,13 @@ Rectangle {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey || !deviceName)
|
if (!pinKey || !deviceName)
|
||||||
return;
|
return;
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.brightnessDevicePins || {}));
|
||||||
if (pins[pinKey] === deviceName) {
|
if (pins[pinKey] === deviceName) {
|
||||||
delete pins[pinKey];
|
delete pins[pinKey];
|
||||||
} else {
|
} else {
|
||||||
pins[pinKey] = deviceName;
|
pins[pinKey] = deviceName;
|
||||||
}
|
}
|
||||||
SettingsData.set("brightnessDevicePins", pins);
|
CacheData.set("brightnessDevicePins", pins);
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
@@ -269,7 +269,7 @@ Rectangle {
|
|||||||
|
|
||||||
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
|
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
|
||||||
readonly property bool devicePinnedHere: {
|
readonly property bool devicePinnedHere: {
|
||||||
SettingsData.brightnessDevicePins;
|
CacheData.brightnessDevicePins;
|
||||||
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
|
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../../Common/Format.js" as Format
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -23,33 +24,8 @@ Rectangle {
|
|||||||
onTriggered: root.nowMs = Date.now()
|
onTriggered: root.nowMs = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
function _pad2(n) {
|
|
||||||
return n < 10 ? "0" + n : "" + n;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUntil(ts) {
|
|
||||||
if (!ts)
|
|
||||||
return "";
|
|
||||||
const d = new Date(ts);
|
|
||||||
const use24h = (typeof SettingsData !== "undefined") ? SettingsData.use24HourClock : true;
|
|
||||||
if (use24h)
|
|
||||||
return _pad2(d.getHours()) + ":" + _pad2(d.getMinutes());
|
|
||||||
const suffix = d.getHours() >= 12 ? "PM" : "AM";
|
|
||||||
const h12 = ((d.getHours() + 11) % 12) + 1;
|
|
||||||
return h12 + ":" + _pad2(d.getMinutes()) + " " + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatRemaining(ms) {
|
function formatRemaining(ms) {
|
||||||
if (ms <= 0)
|
return Format.formatRemaining(ms, "", I18n.tr("%1 min left"), I18n.tr("%1 h left"), I18n.tr("%1 h %2 m left"));
|
||||||
return "";
|
|
||||||
const totalMinutes = Math.ceil(ms / 60000);
|
|
||||||
if (totalMinutes < 60)
|
|
||||||
return I18n.tr("%1 min left").arg(totalMinutes);
|
|
||||||
const hours = Math.floor(totalMinutes / 60);
|
|
||||||
const mins = totalMinutes - hours * 60;
|
|
||||||
if (mins === 0)
|
|
||||||
return I18n.tr("%1 h left").arg(hours);
|
|
||||||
return I18n.tr("%1 h %2 m left").arg(hours).arg(mins);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function minutesUntilTomorrowMorning() {
|
function minutesUntilTomorrowMorning() {
|
||||||
@@ -125,7 +101,7 @@ Rectangle {
|
|||||||
if (SessionData.doNotDisturbUntil <= 0)
|
if (SessionData.doNotDisturbUntil <= 0)
|
||||||
return I18n.tr("On indefinitely");
|
return I18n.tr("On indefinitely");
|
||||||
const remaining = Math.max(0, SessionData.doNotDisturbUntil - root.nowMs);
|
const remaining = Math.max(0, SessionData.doNotDisturbUntil - root.nowMs);
|
||||||
return root.formatRemaining(remaining) + " · " + I18n.tr("until %1").arg(root.formatUntil(SessionData.doNotDisturbUntil));
|
return root.formatRemaining(remaining) + " · " + I18n.tr("until %1").arg(Format.formatUntil(SessionData.doNotDisturbUntil, SettingsData.use24HourClock));
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modals
|
import qs.Modals
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
|
import "../../../Common/QmlUtils.js" as QmlUtils
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -66,17 +67,9 @@ Rectangle {
|
|||||||
PopoutService.openSettingsWithTab("network_wifi");
|
PopoutService.openSettingsWithTab("network_wifi");
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePinList(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value.filter(v => v);
|
|
||||||
if (typeof value === "string" && value.length > 0)
|
|
||||||
return [value];
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPinnedNetworks() {
|
function getPinnedNetworks() {
|
||||||
const pins = SettingsData.wifiNetworkPins || {};
|
const pins = CacheData.wifiNetworkPins || {};
|
||||||
return normalizePinList(pins["preferredWifi"]);
|
return QmlUtils.normalizePinList(pins["preferredWifi"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
property int currentPreferenceIndex: {
|
property int currentPreferenceIndex: {
|
||||||
@@ -907,8 +900,8 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
|
||||||
let pinnedList = root.normalizePinList(pins["preferredWifi"]);
|
let pinnedList = QmlUtils.normalizePinList(pins["preferredWifi"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.ssid);
|
const pinIndex = pinnedList.indexOf(modelData.ssid);
|
||||||
|
|
||||||
if (pinIndex !== -1) {
|
if (pinIndex !== -1) {
|
||||||
@@ -924,7 +917,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredWifi"];
|
delete pins["preferredWifi"];
|
||||||
|
|
||||||
SettingsData.set("wifiNetworkPins", pins);
|
CacheData.set("wifiNetworkPins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Row {
|
|||||||
if (screenName && screenName.length > 0) {
|
if (screenName && screenName.length > 0) {
|
||||||
const screen = Quickshell.screens.find(s => s.name === screenName);
|
const screen = Quickshell.screens.find(s => s.name === screenName);
|
||||||
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
|
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const pinnedDevice = pins[pinKey];
|
const pinnedDevice = pins[pinKey];
|
||||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import qs.Common
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
LayoutMirroring.enabled: I18n.isRtl
|
|
||||||
LayoutMirroring.childrenInherit: true
|
|
||||||
|
|
||||||
property string iconName: ""
|
|
||||||
property color iconColor: Theme.surfaceText
|
|
||||||
property string labelText: ""
|
|
||||||
property real value: 0.0
|
|
||||||
property real maximumValue: 1.0
|
|
||||||
property real minimumValue: 0.0
|
|
||||||
|
|
||||||
signal sliderValueChanged(real value)
|
|
||||||
|
|
||||||
width: parent ? parent.width : 200
|
|
||||||
height: 60
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.nestedSurface
|
|
||||||
border.color: Theme.outlineMedium
|
|
||||||
border.width: Theme.layerOutlineWidth
|
|
||||||
opacity: enabled ? 1.0 : 0.6
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.right: sliderContainer.left
|
|
||||||
anchors.rightMargin: Theme.spacingS
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: root.iconName
|
|
||||||
size: Theme.iconSize
|
|
||||||
color: root.iconColor
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: root.labelText
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: sliderContainer
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
width: 120
|
|
||||||
height: parent.height - Theme.spacingS * 2
|
|
||||||
|
|
||||||
DankSlider {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
enabled: root.enabled
|
|
||||||
minimum: Math.round(root.minimumValue * 100)
|
|
||||||
maximum: Math.round(root.maximumValue * 100)
|
|
||||||
value: Math.round(root.value * 100)
|
|
||||||
trackColor: Theme.ccSliderTrackColor
|
|
||||||
trackOpacity: Theme.ccSliderTrackOpacity
|
|
||||||
onSliderValueChanged: root.sliderValueChanged(newValue / 100.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,11 +25,6 @@ Rectangle {
|
|||||||
height: 60
|
height: 60
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _containerBg: Theme.ccPillInactiveBg
|
readonly property color _containerBg: Theme.ccPillInactiveBg
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
@@ -61,7 +56,7 @@ Rectangle {
|
|||||||
radius: root.radius
|
radius: root.radius
|
||||||
z: 0
|
z: 0
|
||||||
visible: false
|
visible: false
|
||||||
color: hoverTint(_containerBg)
|
color: Theme.hoverTint(_containerBg)
|
||||||
opacity: 0.08
|
opacity: 0.08
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
@@ -98,7 +93,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: _tileRadius
|
radius: _tileRadius
|
||||||
color: hoverTint(iconTile.color)
|
color: Theme.hoverTint(iconTile.color)
|
||||||
opacity: tileMouse.pressed ? 0.3 : (tileMouse.containsMouse ? 0.2 : 0.0)
|
opacity: tileMouse.pressed ? 0.3 : (tileMouse.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string title: ""
|
|
||||||
property Component content: null
|
|
||||||
property bool isVisible: true
|
|
||||||
property int contentHeight: 300
|
|
||||||
|
|
||||||
width: parent ? parent.width : 400
|
|
||||||
implicitHeight: isVisible ? contentHeight : 0
|
|
||||||
height: implicitHeight
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: contentLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
sourceComponent: root.content
|
|
||||||
asynchronous: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,11 +22,6 @@ Rectangle {
|
|||||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||||
readonly property color _tileRingActive: Theme.ccTileRing
|
readonly property color _tileRingActive: Theme.ccTileRing
|
||||||
@@ -47,7 +42,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: hoverTint(root.color)
|
color: Theme.hoverTint(root.color)
|
||||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ Rectangle {
|
|||||||
height: 48
|
height: 48
|
||||||
radius: Theme.cornerRadius === 0 ? 0 : Theme.cornerRadius
|
radius: Theme.cornerRadius === 0 ? 0 : Theme.cornerRadius
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
border.color: Theme.ccTileRing
|
border.color: Theme.ccTileRing
|
||||||
border.width: 1
|
border.width: 1
|
||||||
@@ -29,7 +24,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: hoverTint(root.color)
|
color: Theme.hoverTint(root.color)
|
||||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ Rectangle {
|
|||||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||||
readonly property color _tileRingActive: Theme.ccTileRing
|
readonly property color _tileRingActive: Theme.ccTileRing
|
||||||
@@ -50,7 +45,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: hoverTint(root.color)
|
color: Theme.hoverTint(root.color)
|
||||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -34,11 +34,6 @@ Rectangle {
|
|||||||
height: 48
|
height: 48
|
||||||
radius: Theme.cornerRadius + 4
|
radius: Theme.cornerRadius + 4
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _tileBg: Theme.ccPillInactiveBg
|
readonly property color _tileBg: Theme.ccPillInactiveBg
|
||||||
|
|
||||||
color: mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBg
|
color: mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBg
|
||||||
@@ -50,7 +45,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: hoverTint(root.color)
|
color: Theme.hoverTint(root.color)
|
||||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ Rectangle {
|
|||||||
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
return isActive ? Theme.cornerRadius : Theme.cornerRadius + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||||
readonly property color _tileRingActive: Theme.ccTileRing
|
readonly property color _tileRingActive: Theme.ccTileRing
|
||||||
@@ -45,7 +40,7 @@ Rectangle {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: hoverTint(root.color)
|
color: Theme.hoverTint(root.color)
|
||||||
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
opacity: mouseArea.pressed ? 0.3 : (mouseArea.containsMouse ? 0.2 : 0.0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|||||||
@@ -39,17 +39,12 @@ Rectangle {
|
|||||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||||
opacity: enabled ? 1.0 : 0.6
|
opacity: enabled ? 1.0 : 0.6
|
||||||
|
|
||||||
function hoverTint(base) {
|
|
||||||
const factor = 1.2;
|
|
||||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color _containerBg: Theme.ccPillInactiveBg
|
readonly property color _containerBg: Theme.ccPillInactiveBg
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: mouseArea.containsMouse ? hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
|
color: mouseArea.containsMouse ? Theme.hoverTint(_containerBg) : Theme.withAlpha(_containerBg, 0)
|
||||||
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
opacity: mouseArea.containsMouse ? 0.08 : 0.0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ Item {
|
|||||||
property var centerWidgets: []
|
property var centerWidgets: []
|
||||||
property int totalWidgets: 0
|
property int totalWidgets: 0
|
||||||
property real totalSize: 0
|
property real totalSize: 0
|
||||||
|
property real contentStart: 0
|
||||||
|
property real contentSize: 0
|
||||||
|
|
||||||
function updateLayout() {
|
function updateLayout() {
|
||||||
if (SettingsData.centeringMode === "geometric") {
|
if (SettingsData.centeringMode === "geometric") {
|
||||||
@@ -36,6 +38,25 @@ Item {
|
|||||||
} else {
|
} else {
|
||||||
applyIndexLayout();
|
applyIndexLayout();
|
||||||
}
|
}
|
||||||
|
updateContentExtent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContentExtent() {
|
||||||
|
if (centerWidgets.length === 0) {
|
||||||
|
contentStart = 0;
|
||||||
|
contentSize = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let start = Infinity;
|
||||||
|
let end = -Infinity;
|
||||||
|
for (const widget of centerWidgets) {
|
||||||
|
const pos = isVertical ? widget.y : widget.x;
|
||||||
|
const size = isVertical ? widget.height : widget.width;
|
||||||
|
start = Math.min(start, pos);
|
||||||
|
end = Math.max(end, pos + size);
|
||||||
|
}
|
||||||
|
contentStart = start;
|
||||||
|
contentSize = end - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyGeometricLayout() {
|
function applyGeometricLayout() {
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ Item {
|
|||||||
signal colorPickerRequested
|
signal colorPickerRequested
|
||||||
signal barReady(var barConfig)
|
signal barReady(var barConfig)
|
||||||
|
|
||||||
|
Component.onCompleted: BarWidgetService.registerDankBarItem(root.barConfig?.id, root)
|
||||||
|
Component.onDestruction: BarWidgetService.unregisterDankBarItem(root.barConfig?.id, root)
|
||||||
|
|
||||||
property alias barVariants: barVariants
|
property alias barVariants: barVariants
|
||||||
property var hyprlandOverviewLoader: null
|
property var hyprlandOverviewLoader: null
|
||||||
property bool systemTrayMenuOpen: false
|
property bool systemTrayMenuOpen: false
|
||||||
@@ -161,14 +164,18 @@ Item {
|
|||||||
id: barVariants
|
id: barVariants
|
||||||
model: {
|
model: {
|
||||||
const prefs = root.barConfig?.screenPreferences || ["all"];
|
const prefs = root.barConfig?.screenPreferences || ["all"];
|
||||||
if (prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
|
const wantsAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
||||||
return Quickshell.screens;
|
let base;
|
||||||
|
if (wantsAll) {
|
||||||
|
base = Quickshell.screens;
|
||||||
|
} else {
|
||||||
|
base = Quickshell.screens.filter(screen => SettingsData.isScreenInPreferences(screen, prefs));
|
||||||
|
if (base.length === 0 && root.barConfig?.showOnLastDisplay && Quickshell.screens.length === 1)
|
||||||
|
base = Quickshell.screens;
|
||||||
}
|
}
|
||||||
const filtered = Quickshell.screens.filter(screen => SettingsData.isScreenInPreferences(screen, prefs));
|
// Connected frame mode renders the bar inside the frame surface; skip the standalone window there
|
||||||
if (filtered.length === 0 && root.barConfig?.showOnLastDisplay && Quickshell.screens.length === 1) {
|
// unless this bar wants the overlay layer, which the frame surface cannot provide.
|
||||||
return Quickshell.screens;
|
return base.filter(screen => !CompositorService.frameHostsBarForConfig(screen, root.barConfig));
|
||||||
}
|
|
||||||
return filtered;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: DankBarWindow {
|
delegate: DankBarWindow {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -38,37 +38,43 @@ Item {
|
|||||||
readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1)
|
readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1)
|
||||||
readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin
|
readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin
|
||||||
readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw
|
readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw
|
||||||
// Connected-frame Bar Inset Padding: absolute free-end inset (auto < 0 = frameThickness, 0 = edge-to-edge).
|
// Hosted bars span their edge fully; frameBarContentGap is the free-end gap measured from the
|
||||||
// FrameExclusions already moves a free bar end inward by frameThickness so use frame inset to mangage the gap
|
// screen edge (auto = frameThickness, aligning widgets with the interior cutout).
|
||||||
readonly property real _frameInsetResolved: SettingsData.frameBarInsetPadding < 0 ? SettingsData.frameThickness : SettingsData.frameBarInsetPadding
|
readonly property real _frameInsetResolved: SettingsData.frameBarContentGap
|
||||||
readonly property real _frameInsetExtra: Math.max(0, _frameInsetResolved - SettingsData.frameThickness)
|
readonly property real _frameInsetExtra: SettingsData.frameBarContentGapExtra
|
||||||
|
|
||||||
|
// Horizontal bars span the full width and own the corners; the perpendicular vertical bar
|
||||||
|
// tucks in below/above. Where they meet, inset the corner widget so it centres in the
|
||||||
|
// frameBarSize corner cell, aligning it with the vertical bar's widget column.
|
||||||
|
readonly property real _widgetThicknessValue: _hasBarWindow ? barWindow.widgetThickness : 30
|
||||||
|
readonly property real _cornerAlignInset: Math.max(_frameInsetExtra, (SettingsData.frameBarSize - _widgetThicknessValue) / 2)
|
||||||
|
|
||||||
readonly property real _leftMargin: {
|
readonly property real _leftMargin: {
|
||||||
if (_barIsVertical)
|
if (_barIsVertical)
|
||||||
return _edgeBaseMargin;
|
return _edgeBaseMargin;
|
||||||
if (_usesFrameBarChrome)
|
if (_usesFrameBarChrome)
|
||||||
return hasAdjacentLeftBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
|
return hasAdjacentLeftBarLive ? _cornerAlignInset : _frameInsetResolved;
|
||||||
return Math.max(0, _barInsetPadding);
|
return Math.max(0, _barInsetPadding);
|
||||||
}
|
}
|
||||||
readonly property real _rightMargin: {
|
readonly property real _rightMargin: {
|
||||||
if (_barIsVertical)
|
if (_barIsVertical)
|
||||||
return _edgeBaseMargin;
|
return _edgeBaseMargin;
|
||||||
if (_usesFrameBarChrome)
|
if (_usesFrameBarChrome)
|
||||||
return hasAdjacentRightBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
|
return hasAdjacentRightBarLive ? _cornerAlignInset : _frameInsetResolved;
|
||||||
return Math.max(0, _barInsetPadding);
|
return Math.max(0, _barInsetPadding);
|
||||||
}
|
}
|
||||||
readonly property real _topMargin: {
|
readonly property real _topMargin: {
|
||||||
if (!_barIsVertical)
|
if (!_barIsVertical)
|
||||||
return 0;
|
return 0;
|
||||||
if (_usesFrameBarChrome)
|
if (_usesFrameBarChrome)
|
||||||
return hasAdjacentTopBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
|
return hasAdjacentTopBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
|
||||||
return Math.max(0, _barInsetPadding);
|
return Math.max(0, _barInsetPadding);
|
||||||
}
|
}
|
||||||
readonly property real _bottomMargin: {
|
readonly property real _bottomMargin: {
|
||||||
if (!_barIsVertical)
|
if (!_barIsVertical)
|
||||||
return 0;
|
return 0;
|
||||||
if (_usesFrameBarChrome)
|
if (_usesFrameBarChrome)
|
||||||
return hasAdjacentBottomBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
|
return hasAdjacentBottomBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
|
||||||
return Math.max(0, _barInsetPadding);
|
return Math.max(0, _barInsetPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,22 +273,6 @@ Item {
|
|||||||
return ws.num !== -1 ? ws.num : ws.name;
|
return ws.num !== -1 ? ws.num : ws.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeSwayWorkspaceName(name) {
|
|
||||||
return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchSwayWorkspace(ws) {
|
|
||||||
if (!ws)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
if (ws.num !== undefined && ws.num !== -1) {
|
|
||||||
I3.dispatch(`workspace number ${ws.num}`);
|
|
||||||
} else if (ws.name) {
|
|
||||||
I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchWorkspace(direction) {
|
function switchWorkspace(direction) {
|
||||||
const realWorkspaces = getRealWorkspaces();
|
const realWorkspaces = getRealWorkspaces();
|
||||||
if (realWorkspaces.length < 2) {
|
if (realWorkspaces.length < 2) {
|
||||||
@@ -327,7 +317,7 @@ Item {
|
|||||||
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
||||||
|
|
||||||
if (nextIndex !== validIndex) {
|
if (nextIndex !== validIndex) {
|
||||||
dispatchSwayWorkspace(realWorkspaces[nextIndex]);
|
CompositorService.dispatchSwayWorkspace(realWorkspaces[nextIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,6 +667,7 @@ Item {
|
|||||||
LeftSection {
|
LeftSection {
|
||||||
id: hLeftSection
|
id: hLeftSection
|
||||||
objectName: "leftSection"
|
objectName: "leftSection"
|
||||||
|
edgeIsScreenEdge: !topBarContent.hasAdjacentLeftBarLive
|
||||||
overrideAxisLayout: true
|
overrideAxisLayout: true
|
||||||
forceVerticalLayout: false
|
forceVerticalLayout: false
|
||||||
anchors {
|
anchors {
|
||||||
@@ -710,6 +701,7 @@ Item {
|
|||||||
RightSection {
|
RightSection {
|
||||||
id: hRightSection
|
id: hRightSection
|
||||||
objectName: "rightSection"
|
objectName: "rightSection"
|
||||||
|
edgeIsScreenEdge: !topBarContent.hasAdjacentRightBarLive
|
||||||
overrideAxisLayout: true
|
overrideAxisLayout: true
|
||||||
forceVerticalLayout: false
|
forceVerticalLayout: false
|
||||||
anchors {
|
anchors {
|
||||||
@@ -782,6 +774,7 @@ Item {
|
|||||||
LeftSection {
|
LeftSection {
|
||||||
id: vLeftSection
|
id: vLeftSection
|
||||||
objectName: "leftSection"
|
objectName: "leftSection"
|
||||||
|
edgeIsScreenEdge: !topBarContent.hasAdjacentTopBarLive
|
||||||
overrideAxisLayout: true
|
overrideAxisLayout: true
|
||||||
forceVerticalLayout: true
|
forceVerticalLayout: true
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -850,6 +843,7 @@ Item {
|
|||||||
RightSection {
|
RightSection {
|
||||||
id: vRightSection
|
id: vRightSection
|
||||||
objectName: "rightSection"
|
objectName: "rightSection"
|
||||||
|
edgeIsScreenEdge: !topBarContent.hasAdjacentBottomBarLive
|
||||||
overrideAxisLayout: true
|
overrideAxisLayout: true
|
||||||
forceVerticalLayout: true
|
forceVerticalLayout: true
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -930,7 +930,8 @@ Item {
|
|||||||
if (_barHovered)
|
if (_barHovered)
|
||||||
return;
|
return;
|
||||||
const excludedBar = _barExitPending ? barWindow : null;
|
const excludedBar = _barExitPending ? barWindow : null;
|
||||||
if (cursorOverHoverChain(gx, gy, excludedBar))
|
const staleOverExitedBar = excludedBar?.containsGlobalPoint?.(gx, gy, 0) ?? false;
|
||||||
|
if (!staleOverExitedBar && cursorOverHoverChain(gx, gy, excludedBar))
|
||||||
return;
|
return;
|
||||||
_barExitPending = false;
|
_barExitPending = false;
|
||||||
closeHoverSurfaces();
|
closeHoverSurfaces();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,9 @@ Item {
|
|||||||
property real sectionAvailablePrimarySize: 0
|
property real sectionAvailablePrimarySize: 0
|
||||||
property bool overrideAxisLayout: false
|
property bool overrideAxisLayout: false
|
||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
// False when a perpendicular bar occupies this edge, so the first widget's click area
|
||||||
|
// doesn't extend past the bar into the neighbour (matters most in the frame surface).
|
||||||
|
property bool edgeIsScreenEdge: true
|
||||||
|
|
||||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||||
property alias widgetLayoutLoader: layoutLoader
|
property alias widgetLayoutLoader: layoutLoader
|
||||||
@@ -68,7 +71,7 @@ Item {
|
|||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === rowRepeater.count - 1
|
isLast: index === rowRepeater.count - 1
|
||||||
sectionSpacing: parent.rowSpacing
|
sectionSpacing: parent.rowSpacing
|
||||||
isLeftBarEdge: true
|
isLeftBarEdge: root.edgeIsScreenEdge
|
||||||
isRightBarEdge: false
|
isRightBarEdge: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +118,7 @@ Item {
|
|||||||
isFirst: index === 0
|
isFirst: index === 0
|
||||||
isLast: index === columnRepeater.count - 1
|
isLast: index === columnRepeater.count - 1
|
||||||
sectionSpacing: parent.columnSpacing
|
sectionSpacing: parent.columnSpacing
|
||||||
isTopBarEdge: true
|
isTopBarEdge: root.edgeIsScreenEdge
|
||||||
isBottomBarEdge: false
|
isBottomBarEdge: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import QtQuick.Layouts
|
|||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import qs.Modules.ControlCenter.Details
|
||||||
|
|
||||||
DankPopout {
|
DankPopout {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ Item {
|
|||||||
property real sectionAvailablePrimarySize: 0
|
property real sectionAvailablePrimarySize: 0
|
||||||
property bool overrideAxisLayout: false
|
property bool overrideAxisLayout: false
|
||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
// False when a perpendicular bar occupies this edge, so the last widget's click area
|
||||||
|
// doesn't extend past the bar into the neighbour (matters most in the frame surface).
|
||||||
|
property bool edgeIsScreenEdge: true
|
||||||
|
|
||||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||||
property alias widgetLayoutLoader: layoutLoader
|
property alias widgetLayoutLoader: layoutLoader
|
||||||
@@ -71,7 +74,7 @@ Item {
|
|||||||
isLast: index === rowRepeater.count - 1
|
isLast: index === rowRepeater.count - 1
|
||||||
sectionSpacing: parent.rowSpacing
|
sectionSpacing: parent.rowSpacing
|
||||||
isLeftBarEdge: false
|
isLeftBarEdge: false
|
||||||
isRightBarEdge: true
|
isRightBarEdge: root.edgeIsScreenEdge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +121,7 @@ Item {
|
|||||||
isLast: index === columnRepeater.count - 1
|
isLast: index === columnRepeater.count - 1
|
||||||
sectionSpacing: parent.columnSpacing
|
sectionSpacing: parent.columnSpacing
|
||||||
isTopBarEdge: false
|
isTopBarEdge: false
|
||||||
isBottomBarEdge: true
|
isBottomBarEdge: root.edgeIsScreenEdge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ BasePill {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey)
|
if (!pinKey)
|
||||||
return "";
|
return "";
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
return pins[pinKey] || "";
|
return pins[pinKey] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,11 @@ import qs.Common
|
|||||||
import qs.Modules.Plugins
|
import qs.Modules.Plugins
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
import "../../../Common/Format.js" as Format
|
||||||
|
|
||||||
BasePill {
|
BasePill {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
function formatNetworkSpeed(bytesPerSec) {
|
|
||||||
if (bytesPerSec < 1024) {
|
|
||||||
return bytesPerSec.toFixed(0) + " B/s";
|
|
||||||
} else if (bytesPerSec < 1024 * 1024) {
|
|
||||||
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
|
|
||||||
} else if (bytesPerSec < 1024 * 1024 * 1024) {
|
|
||||||
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
|
|
||||||
} else {
|
|
||||||
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
DgopService.addRef(["network"]);
|
DgopService.addRef(["network"]);
|
||||||
}
|
}
|
||||||
@@ -97,7 +86,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: DgopService.networkRxRate > 0 ? root.formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
|
text: DgopService.networkRxRate > 0 ? Format.formatRate(DgopService.networkRxRate, 1) : "0 B/s"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -126,7 +115,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: DgopService.networkTxRate > 0 ? root.formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
|
text: DgopService.networkTxRate > 0 ? Format.formatRate(DgopService.networkTxRate, 1) : "0 B/s"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -35,44 +35,42 @@ BasePill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: (rx, ry) => {
|
onRightClicked: {
|
||||||
const screen = root.parentScreen || Screen;
|
const screen = root.parentScreen || Screen;
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
const globalPos = root.visualContent.mapToItem(null, 0, 0);
|
|
||||||
const isVertical = root.axis?.isVertical ?? false;
|
const isVertical = root.axis?.isVertical ?? false;
|
||||||
const edge = root.axis?.edge ?? "top";
|
const edge = root.axis?.edge ?? "top";
|
||||||
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
|
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
|
||||||
const barOffset = root.barThickness + root.barSpacing + gap;
|
const barOffset = root.barThickness + root.barSpacing + gap;
|
||||||
|
const localPos = root.visualContent.mapToItem(null, root.visualContent.width / 2, root.visualContent.height / 2);
|
||||||
|
|
||||||
let anchorX;
|
let anchorX;
|
||||||
let anchorY;
|
let anchorY;
|
||||||
let anchorEdge;
|
|
||||||
if (isVertical) {
|
if (isVertical) {
|
||||||
anchorY = globalPos.y - (screen.y || 0) + root.visualContent.height / 2;
|
anchorX = edge === "left" ? barOffset : screen.width - barOffset;
|
||||||
if (edge === "left") {
|
anchorY = localPos.y;
|
||||||
anchorX = barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
} else {
|
|
||||||
anchorX = screen.width - barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
anchorX = globalPos.x - (screen.x || 0) + root.visualContent.width / 2;
|
anchorX = localPos.x;
|
||||||
if (edge === "bottom") {
|
anchorY = edge === "bottom" ? screen.height - barOffset : barOffset;
|
||||||
anchorY = screen.height - barOffset;
|
|
||||||
anchorEdge = "bottom";
|
|
||||||
} else {
|
|
||||||
anchorY = barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dndPopupLoader.active = true;
|
dndPopupLoader.active = true;
|
||||||
const popup = dndPopupLoader.item;
|
const popup = dndPopupLoader.item;
|
||||||
if (!popup)
|
if (!popup)
|
||||||
return;
|
return;
|
||||||
popup.showAt(anchorX, anchorY, screen, anchorEdge);
|
|
||||||
|
popup.showAt(anchorX, anchorY, isVertical, edge, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.MiddleButton
|
||||||
|
onPressed: mouse => {
|
||||||
|
root.triggerRipple(this, mouse.x, mouse.y);
|
||||||
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -228,22 +228,6 @@ Item {
|
|||||||
return ws.num !== -1 ? ws.num : ws.name;
|
return ws.num !== -1 ? ws.num : ws.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeSwayWorkspaceName(name) {
|
|
||||||
return String(name ?? "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatchSwayWorkspace(ws) {
|
|
||||||
if (!ws)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
if (ws.num !== undefined && ws.num !== -1) {
|
|
||||||
I3.dispatch(`workspace number ${ws.num}`);
|
|
||||||
} else if (ws.name) {
|
|
||||||
I3.dispatch(`workspace "${escapeSwayWorkspaceName(ws.name)}"`);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSwayActiveWorkspace() {
|
function getSwayActiveWorkspace() {
|
||||||
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
||||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||||
@@ -711,7 +695,7 @@ Item {
|
|||||||
case "sway":
|
case "sway":
|
||||||
case "scroll":
|
case "scroll":
|
||||||
case "miracle":
|
case "miracle":
|
||||||
dispatchSwayWorkspace(data);
|
CompositorService.dispatchSwayWorkspace(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -818,7 +802,7 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchSwayWorkspace(realWorkspaces[nextIndex]);
|
CompositorService.dispatchSwayWorkspace(realWorkspaces[nextIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1486,7 +1470,7 @@ Item {
|
|||||||
} else if (root.isMango && modelData?.tag !== undefined) {
|
} else if (root.isMango && modelData?.tag !== undefined) {
|
||||||
MangoService.switchToTag(root.screenName, modelData.tag);
|
MangoService.switchToTag(root.screenName, modelData.tag);
|
||||||
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num !== undefined) {
|
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num !== undefined) {
|
||||||
root.dispatchSwayWorkspace(modelData);
|
CompositorService.dispatchSwayWorkspace(modelData);
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
|
|||||||
@@ -415,11 +415,24 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onWheel: wheelEvent => {
|
onWheel: wheelEvent => {
|
||||||
if (SettingsData.audioDeviceScrollVolumeEnabled && wheelEvent.x >= deviceMouseArea.width / 2) {
|
if (!SettingsData.audioDeviceScrollVolumeEnabled || wheelEvent.x < deviceMouseArea.width / 2) {
|
||||||
AudioService.handleNodeVolumeWheel(modelData, wheelEvent);
|
|
||||||
} else {
|
|
||||||
wheelEvent.accepted = false;
|
wheelEvent.accepted = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!modelData?.audio)
|
||||||
|
return;
|
||||||
|
SessionData.suppressOSDTemporarily();
|
||||||
|
const delta = wheelEvent.angleDelta.y;
|
||||||
|
if (delta === 0)
|
||||||
|
return;
|
||||||
|
const current = Math.round(modelData.audio.volume * 100);
|
||||||
|
const maxVol = AudioService.getMaxVolumePercent(modelData);
|
||||||
|
const newVolume = delta > 0 ? Math.min(maxVol, current + AudioService.wheelVolumeStep) : Math.max(0, current - AudioService.wheelVolumeStep);
|
||||||
|
modelData.audio.muted = false;
|
||||||
|
modelData.audio.volume = newVolume / 100;
|
||||||
|
if (modelData === AudioService.sink)
|
||||||
|
AudioService.playVolumeChangeSoundIfEnabled();
|
||||||
|
wheelEvent.accepted = true;
|
||||||
}
|
}
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
|||||||
@@ -451,6 +451,9 @@ Item {
|
|||||||
height: width
|
height: width
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
activePlayer: root.activePlayer
|
activePlayer: root.activePlayer
|
||||||
|
artUrl: TrackArtService.resolvedArtUrl
|
||||||
|
accentColor: MediaAccentService.accent
|
||||||
|
cavaService: CavaService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,6 +525,10 @@ Item {
|
|||||||
height: 20
|
height: 20
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
activePlayer: root.activePlayer
|
activePlayer: root.activePlayer
|
||||||
|
stableLength: MprisController.activePlayerStableLength
|
||||||
|
accentColor: MediaAccentService.accent
|
||||||
|
accentTrackColor: MediaAccentService.accentTrack
|
||||||
|
accentSubtleColor: MediaAccentService.accentSubtle
|
||||||
isSeeking: root.isSeeking
|
isSeeking: root.isSeeking
|
||||||
onIsSeekingChanged: root.isSeeking = isSeeking
|
onIsSeekingChanged: root.isSeeking = isSeeking
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Card {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
readonly property var device: DankConnectService.primaryDevice
|
||||||
|
readonly property var pairingRequest: DankConnectService.pairingRequest
|
||||||
|
readonly property bool hasBattery: !!device && typeof device.batteryCharge === "number" && device.batteryCharge >= 0
|
||||||
|
|
||||||
|
function statusText() {
|
||||||
|
if (!DankConnectService.connected)
|
||||||
|
return I18n.tr("Disconnected");
|
||||||
|
if (!device)
|
||||||
|
return I18n.tr("No devices");
|
||||||
|
const connection = device.isReachable ? I18n.tr("Connected") : I18n.tr("Disconnected");
|
||||||
|
const pairing = device.isPaired ? I18n.tr("Paired") : I18n.tr("Not paired");
|
||||||
|
return connection + " • " + pairing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function batteryText() {
|
||||||
|
if (!hasBattery)
|
||||||
|
return "";
|
||||||
|
const charge = Math.round(device.batteryCharge) + "%";
|
||||||
|
return device.batteryCharging ? charge + " • " + I18n.tr("Charging") : charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportError(title, response) {
|
||||||
|
if (response?.error)
|
||||||
|
ToastService.showError(title, response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: contentRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: Math.max(180, contentRow.width - actionRow.width - trailingPanel.width - contentRow.spacing * 2)
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: "smartphone"
|
||||||
|
size: 36
|
||||||
|
color: root.device?.isReachable ? Theme.primary : Theme.surfaceTextSecondary
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: parent.width - 36 - parent.spacing
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.device?.name || "DankConnect"
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.statusText()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
visible: root.hasBattery
|
||||||
|
text: root.batteryText()
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionRow
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: implicitWidth
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "ring_volume"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Ring")
|
||||||
|
enabled: DankConnectService.canRing(root.device)
|
||||||
|
onClicked: DankConnectService.ringDevice(root.device.id, response => root.reportError(I18n.tr("Ring"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "network_ping"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Ping")
|
||||||
|
enabled: DankConnectService.canPing(root.device)
|
||||||
|
onClicked: DankConnectService.pingDevice(root.device.id, response => root.reportError(I18n.tr("Ping"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "content_paste_go"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Send Clipboard")
|
||||||
|
enabled: DankConnectService.canSendClipboard(root.device)
|
||||||
|
onClicked: DankConnectService.sendClipboard(root.device.id, response => root.reportError(I18n.tr("Send Clipboard"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
buttonSize: 34
|
||||||
|
iconSize: 17
|
||||||
|
iconName: "open_in_new"
|
||||||
|
iconColor: Theme.primary
|
||||||
|
tooltipText: I18n.tr("Open App")
|
||||||
|
onClicked: DankConnectService.openApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: trailingPanel
|
||||||
|
|
||||||
|
width: root.pairingRequest ? 270 : 52
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !root.pairingRequest && root.hasBattery
|
||||||
|
name: root.hasBattery ? Theme.getBatteryIcon(root.device.batteryCharge, root.device.batteryCharging, true) : "battery_std"
|
||||||
|
size: 28
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
visible: root.pairingRequest !== null
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Pairing request from %1").arg(root.pairingRequest?.name || I18n.tr("Device"))
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: root.pairingRequest?.verificationKey || I18n.tr("Unknown")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: Theme.primary
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Reject")
|
||||||
|
buttonHeight: 30
|
||||||
|
horizontalPadding: Theme.spacingM
|
||||||
|
onClicked: DankConnectService.rejectPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to reject pairing"), response))
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Accept")
|
||||||
|
buttonHeight: 30
|
||||||
|
horizontalPadding: Theme.spacingM
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.primaryText
|
||||||
|
onClicked: DankConnectService.acceptPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to accept pairing"), response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,9 @@ Card {
|
|||||||
height: 80
|
height: 80
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
activePlayer: root.activePlayer
|
activePlayer: root.activePlayer
|
||||||
|
artUrl: TrackArtService.resolvedArtUrl
|
||||||
|
accentColor: MediaAccentService.accent
|
||||||
|
cavaService: CavaService
|
||||||
albumSize: 76
|
albumSize: 76
|
||||||
animationScale: 1.05
|
animationScale: 1.05
|
||||||
}
|
}
|
||||||
@@ -114,6 +117,10 @@ Card {
|
|||||||
height: 20
|
height: 20
|
||||||
x: -2
|
x: -2
|
||||||
activePlayer: root.activePlayer
|
activePlayer: root.activePlayer
|
||||||
|
stableLength: MprisController.activePlayerStableLength
|
||||||
|
accentColor: MediaAccentService.accent
|
||||||
|
accentTrackColor: MediaAccentService.accentTrack
|
||||||
|
accentSubtleColor: MediaAccentService.accentSubtle
|
||||||
isSeeking: root.isSeeking
|
isSeeking: root.isSeeking
|
||||||
onIsSeekingChanged: root.isSeeking = isSeeking
|
onIsSeekingChanged: root.isSeeking = isSeeking
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
readonly property int overviewGridHeight: 410
|
||||||
|
readonly property int dankConnectCardHeight: 124
|
||||||
|
|
||||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
implicitHeight: 410
|
implicitHeight: overviewGridHeight + Theme.spacingM + dankConnectCardHeight
|
||||||
|
|
||||||
signal switchToWeatherTab
|
signal switchToWeatherTab
|
||||||
signal switchToMediaTab
|
signal switchToMediaTab
|
||||||
@@ -89,5 +92,12 @@ Item {
|
|||||||
|
|
||||||
onClicked: root.switchToMediaTab()
|
onClicked: root.switchToMediaTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankConnectOverviewCard {
|
||||||
|
x: 0
|
||||||
|
y: root.overviewGridHeight + Theme.spacingM
|
||||||
|
width: parent.width
|
||||||
|
height: root.dankConnectCardHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -10,7 +8,8 @@ import qs.Widgets
|
|||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
id: dockVariants
|
id: dockVariants
|
||||||
model: SettingsData.getFilteredScreens("dock")
|
// Connected frame mode renders the dock inside the frame surface; skip the standalone window there.
|
||||||
|
model: SettingsData.getFilteredScreens("dock").filter(screen => !CompositorService.frameHostsDockForScreen(screen))
|
||||||
|
|
||||||
property var contextMenu
|
property var contextMenu
|
||||||
property var trashContextMenu
|
property var trashContextMenu
|
||||||
@@ -18,864 +17,74 @@ Variants {
|
|||||||
delegate: PanelWindow {
|
delegate: PanelWindow {
|
||||||
id: dock
|
id: dock
|
||||||
|
|
||||||
WindowBlur {
|
|
||||||
targetWindow: dock
|
|
||||||
blurEnabled: dock.effectiveBlurEnabled && !dock.usesConnectedFrameChrome
|
|
||||||
blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
|
||||||
blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
|
||||||
blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0
|
|
||||||
blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0
|
|
||||||
blurRadius: dock.usesConnectedFrameChrome ? Theme.connectedCornerRadius : dock.surfaceRadius
|
|
||||||
}
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:dock"
|
|
||||||
WlrLayershell.layer: dock.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
|
||||||
|
|
||||||
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
|
||||||
bottom: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
|
||||||
left: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
|
||||||
right: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
property var modelData: item
|
property var modelData: item
|
||||||
property bool autoHide: SettingsData.dockAutoHide || SettingsData.dockSmartAutoHide
|
|
||||||
property real backgroundTransparency: SettingsData.dockTransparency
|
|
||||||
property bool groupByApp: SettingsData.dockGroupByApp
|
|
||||||
readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0
|
|
||||||
readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right"
|
|
||||||
readonly property bool frameDockExclusionActive: dockGeometry.frameExclusionActive
|
|
||||||
readonly property bool connectedBarActiveOnEdge: dockGeometry.connectedBarActiveOnEdge
|
|
||||||
readonly property real connectedJoinInset: dockGeometry.connectedJoinInset
|
|
||||||
readonly property real dockFrameInset: dockGeometry.frameInset
|
|
||||||
readonly property real surfaceRadius: usesConnectedFrameChrome ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
|
||||||
readonly property color surfaceColor: usesConnectedFrameChrome ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
|
||||||
readonly property color surfaceBorderColor: usesConnectedFrameChrome ? Theme.withAlpha(BlurService.borderColor, 0) : BlurService.borderColor
|
|
||||||
readonly property real surfaceBorderWidth: usesConnectedFrameChrome ? 0 : BlurService.borderWidth
|
|
||||||
readonly property real surfaceTopLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
|
||||||
readonly property real surfaceTopRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
|
||||||
readonly property real surfaceBottomLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
|
||||||
readonly property real surfaceBottomRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
|
||||||
readonly property real horizontalConnectorExtent: usesConnectedFrameChrome && !isVertical ? Theme.connectedCornerRadius : 0
|
|
||||||
readonly property real verticalConnectorExtent: usesConnectedFrameChrome && isVertical ? Theme.connectedCornerRadius : 0
|
|
||||||
|
|
||||||
readonly property int hasApps: dockApps.implicitWidth > 0 || dockApps.implicitHeight > 0
|
|
||||||
|
|
||||||
readonly property real widgetHeight: SettingsData.dockIconSize
|
|
||||||
readonly property real effectiveBarHeight: dockGeometry.visualThickness
|
|
||||||
function getBarHeight(barConfig) {
|
|
||||||
if (!barConfig)
|
|
||||||
return 0;
|
|
||||||
const innerPadding = barConfig.innerPadding ?? 4;
|
|
||||||
const widgetThickness = Math.max(20, 26 + innerPadding * 0.6);
|
|
||||||
const barThickness = Math.max(widgetThickness + innerPadding + 4, Theme.barHeight - 4 - (8 - innerPadding));
|
|
||||||
const spacing = barConfig.spacing ?? 4;
|
|
||||||
const bottomGap = barConfig.bottomGap ?? 0;
|
|
||||||
return barThickness + spacing + bottomGap;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real barSpacing: {
|
|
||||||
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
|
|
||||||
if (!defaultBar)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const barPos = defaultBar.position ?? SettingsData.Position.Top;
|
|
||||||
const barIsHorizontal = (barPos === SettingsData.Position.Top || barPos === SettingsData.Position.Bottom);
|
|
||||||
const barIsVertical = (barPos === SettingsData.Position.Left || barPos === SettingsData.Position.Right);
|
|
||||||
const samePosition = (SettingsData.dockPosition === barPos);
|
|
||||||
const dockIsHorizontal = !isVertical;
|
|
||||||
const dockIsVertical = isVertical;
|
|
||||||
|
|
||||||
if (!(defaultBar.visible ?? true))
|
|
||||||
return 0;
|
|
||||||
const spacing = defaultBar.spacing ?? 4;
|
|
||||||
const bottomGap = defaultBar.bottomGap ?? 0;
|
|
||||||
if (dockIsHorizontal && barIsHorizontal && samePosition) {
|
|
||||||
return spacing + effectiveBarHeight + bottomGap;
|
|
||||||
}
|
|
||||||
if (dockIsVertical && barIsVertical && samePosition) {
|
|
||||||
return spacing + effectiveBarHeight + bottomGap;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real adjacentTopBarHeight: {
|
|
||||||
if (!isVertical || autoHide)
|
|
||||||
return 0;
|
|
||||||
const screenName = dock.modelData?.name ?? "";
|
|
||||||
const topBar = SettingsData.barConfigs.find(bc => {
|
|
||||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
|
||||||
return false;
|
|
||||||
if (bc.position !== SettingsData.Position.Top && bc.position !== 0)
|
|
||||||
return false;
|
|
||||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
|
||||||
return onThisScreen;
|
|
||||||
});
|
|
||||||
return getBarHeight(topBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real adjacentLeftBarWidth: {
|
|
||||||
if (isVertical || autoHide)
|
|
||||||
return 0;
|
|
||||||
const screenName = dock.modelData?.name ?? "";
|
|
||||||
const leftBar = SettingsData.barConfigs.find(bc => {
|
|
||||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
|
||||||
return false;
|
|
||||||
if (bc.position !== SettingsData.Position.Left && bc.position !== 2)
|
|
||||||
return false;
|
|
||||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
|
||||||
return onThisScreen;
|
|
||||||
});
|
|
||||||
return getBarHeight(leftBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real dockMargin: SettingsData.dockMargin
|
|
||||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
|
||||||
readonly property real effectiveDockBottomGap: dockGeometry.visualOffset
|
|
||||||
readonly property real effectiveDockMargin: dockGeometry.effectiveMargin
|
|
||||||
readonly property real positionSpacing: barSpacing + effectiveDockBottomGap + effectiveDockMargin
|
|
||||||
readonly property real joinedEdgeMargin: dockGeometry.joinedEdgeMargin
|
|
||||||
readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1
|
|
||||||
function px(v) {
|
|
||||||
return Math.round(v * _dpr) / _dpr;
|
|
||||||
}
|
|
||||||
|
|
||||||
DockGeometry {
|
|
||||||
id: dockGeometry
|
|
||||||
|
|
||||||
screen: dock.screen || dock.modelData
|
|
||||||
edge: dock.connectedBarSide
|
|
||||||
dockVisible: dock.visible
|
|
||||||
autoHide: dock.autoHide
|
|
||||||
iconSize: dock.widgetHeight
|
|
||||||
spacing: SettingsData.dockSpacing
|
|
||||||
borderThickness: dock.borderThickness
|
|
||||||
offset: SettingsData.dockBottomGap
|
|
||||||
margin: SettingsData.dockMargin
|
|
||||||
barSpacing: dock.barSpacing
|
|
||||||
dpr: dock._dpr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dock window origin in screen-relative coordinates (FrameWindow space).
|
|
||||||
function _dockWindowOriginX() {
|
|
||||||
if (!dock.isVertical)
|
|
||||||
return 0;
|
|
||||||
if (SettingsData.dockPosition === SettingsData.Position.Right)
|
|
||||||
return (dock.screen ? dock.screen.width : 0) - dock.width;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
function _dockWindowOriginY() {
|
|
||||||
if (dock.isVertical)
|
|
||||||
return 0;
|
|
||||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom)
|
|
||||||
return (dock.screen ? dock.screen.height : 0) - dock.height;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property string _dockScreenName: dock.modelData ? dock.modelData.name : (dock.screen ? dock.screen.name : "")
|
|
||||||
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(dock._dockScreenName)
|
|
||||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(dock._dockScreenName) || SettingsData.dockUseOverlayLayer
|
|
||||||
|
|
||||||
function _syncDockChromeState() {
|
|
||||||
if (!dock._dockScreenName)
|
|
||||||
return;
|
|
||||||
if (!dock.usesConnectedFrameChrome) {
|
|
||||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps;
|
|
||||||
const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open"));
|
|
||||||
const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x;
|
|
||||||
const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y;
|
|
||||||
const bodyW = dock.hasApps ? dockBackground.width : 0;
|
|
||||||
const bodyH = dock.hasApps ? dockBackground.height : 0;
|
|
||||||
ConnectedModeState.setDockState(dock._dockScreenName, {
|
|
||||||
"kind": "dock",
|
|
||||||
"screenName": dock._dockScreenName,
|
|
||||||
"phase": phase,
|
|
||||||
"visible": presented,
|
|
||||||
"presented": presented,
|
|
||||||
"reveal": presented,
|
|
||||||
"barSide": dock.connectedBarSide,
|
|
||||||
"bodyRect": {
|
|
||||||
"x": bodyX,
|
|
||||||
"y": bodyY,
|
|
||||||
"width": bodyW,
|
|
||||||
"height": bodyH
|
|
||||||
},
|
|
||||||
"animationOffset": {
|
|
||||||
"x": dockSlide.x,
|
|
||||||
"y": dockSlide.y
|
|
||||||
},
|
|
||||||
"scale": 1,
|
|
||||||
"opacity": Theme.connectedSurfaceColor.a,
|
|
||||||
"bodyX": bodyX,
|
|
||||||
"bodyY": bodyY,
|
|
||||||
"bodyW": bodyW,
|
|
||||||
"bodyH": bodyH,
|
|
||||||
"slideX": dockSlide.x,
|
|
||||||
"slideY": dockSlide.y
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _syncDockSlide() {
|
|
||||||
if (!dock._dockScreenName || !dock.usesConnectedFrameChrome)
|
|
||||||
return;
|
|
||||||
ConnectedModeState.setDockSlide(dock._dockScreenName, dockSlide.x, dockSlide.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeferredAction {
|
|
||||||
id: dockSlideSync
|
|
||||||
enabled: dock.usesConnectedFrameChrome
|
|
||||||
onTriggered: dock._syncDockSlide()
|
|
||||||
}
|
|
||||||
|
|
||||||
function _queueSlideSync() {
|
|
||||||
if (!dock.usesConnectedFrameChrome)
|
|
||||||
return;
|
|
||||||
dockSlideSync.schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
DeferredAction {
|
|
||||||
id: dockChromeSync
|
|
||||||
onTriggered: dock._syncDockChromeState()
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
|
||||||
property bool revealSticky: false
|
|
||||||
|
|
||||||
readonly property bool shouldHideForWindows: {
|
|
||||||
if (!SettingsData.dockSmartAutoHide)
|
|
||||||
return false;
|
|
||||||
if (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const screenName = dock.modelData?.name ?? "";
|
|
||||||
const dockThickness = dockGeometry.motionThickness;
|
|
||||||
const screenWidth = dock.screen?.width ?? 0;
|
|
||||||
const screenHeight = dock.screen?.height ?? 0;
|
|
||||||
|
|
||||||
if (CompositorService.isNiri) {
|
|
||||||
NiriService.windows;
|
|
||||||
|
|
||||||
let currentWorkspaceId = null;
|
|
||||||
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
||||||
const ws = NiriService.allWorkspaces[i];
|
|
||||||
if (ws.output === screenName && ws.is_active) {
|
|
||||||
currentWorkspaceId = ws.id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentWorkspaceId === null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < NiriService.windows.length; i++) {
|
|
||||||
const win = NiriService.windows[i];
|
|
||||||
if (win.workspace_id !== currentWorkspaceId)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Get window position and size from layout data
|
|
||||||
const tilePos = win.layout?.tile_pos_in_workspace_view;
|
|
||||||
const winSize = win.layout?.window_size || win.layout?.tile_size;
|
|
||||||
|
|
||||||
if (tilePos && winSize) {
|
|
||||||
const winX = tilePos[0];
|
|
||||||
const winY = tilePos[1];
|
|
||||||
const winW = winSize[0];
|
|
||||||
const winH = winSize[1];
|
|
||||||
|
|
||||||
switch (SettingsData.dockPosition) {
|
|
||||||
case SettingsData.Position.Top:
|
|
||||||
if (winY < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Bottom:
|
|
||||||
if (winY + winH > screenHeight - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Left:
|
|
||||||
if (winX < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Right:
|
|
||||||
if (winX + winW > screenWidth - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (!win.is_floating) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CompositorService.isMango) {
|
|
||||||
MangoService.windows;
|
|
||||||
MangoService.outputs;
|
|
||||||
return CompositorService.mangoDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hyprland implementation (current workspace + visible special workspaces)
|
|
||||||
Hyprland.focusedWorkspace;
|
|
||||||
Hyprland.toplevels;
|
|
||||||
return CompositorService.hyprlandDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: revealHold
|
|
||||||
interval: 250
|
|
||||||
repeat: false
|
|
||||||
onTriggered: dock.revealSticky = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flip `reveal` false when a modal claims this edge; reuses the slide animation
|
|
||||||
readonly property bool _modalRetractActive: {
|
|
||||||
if (!dock._dockScreenName)
|
|
||||||
return false;
|
|
||||||
return ConnectedModeState.dockRetractActiveForSide(dock._dockScreenName, dock.connectedBarSide);
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool startupRevealDone: false
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: startupRevealTimer
|
|
||||||
interval: 200
|
|
||||||
running: true
|
|
||||||
onTriggered: dock.startupRevealDone = true
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool reveal: {
|
|
||||||
if (!startupRevealDone)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_modalRetractActive)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Smart auto-hide: show dock when no windows overlap, hide when they do
|
|
||||||
if (SettingsData.dockSmartAutoHide) {
|
|
||||||
if (shouldHideForWindows)
|
|
||||||
return dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
|
||||||
return true; // No overlapping windows - show dock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular auto-hide: always hide unless hovering
|
|
||||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
|
||||||
}
|
|
||||||
|
|
||||||
onContextMenuOpenChanged: {
|
|
||||||
if (!contextMenuOpen && autoHide && !dockMouseArea.containsMouse) {
|
|
||||||
revealSticky = true;
|
|
||||||
revealHold.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: dockChromeSync.schedule()
|
|
||||||
Component.onDestruction: {
|
|
||||||
dockChromeSync.cancel();
|
|
||||||
dockSlideSync.cancel();
|
|
||||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
|
||||||
}
|
|
||||||
|
|
||||||
onRevealChanged: dock._syncDockChromeState()
|
|
||||||
onWidthChanged: dock._syncDockChromeState()
|
|
||||||
onHeightChanged: dock._syncDockChromeState()
|
|
||||||
onVisibleChanged: dock._syncDockChromeState()
|
|
||||||
onHasAppsChanged: dock._syncDockChromeState()
|
|
||||||
onConnectedBarSideChanged: dock._syncDockChromeState()
|
|
||||||
onUsesConnectedFrameChromeChanged: dock._syncDockChromeState()
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onConnectedFrameModeActiveChanged() {
|
|
||||||
dockSlideSync.cancel();
|
|
||||||
dock._syncDockChromeState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onDockTransparencyChanged() {
|
|
||||||
dock.backgroundTransparency = SettingsData.dockTransparency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen: modelData
|
screen: modelData
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "dms:dock"
|
||||||
|
WlrLayershell.layer: body.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||||
|
bottom: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||||
|
left: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||||
|
right: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||||
|
}
|
||||||
|
|
||||||
visible: {
|
visible: {
|
||||||
if (CompositorService.isNiri && NiriService.inOverview) {
|
if (CompositorService.isNiri && NiriService.inOverview) {
|
||||||
return SettingsData.dockOpenOnOverview;
|
return SettingsData.dockOpenOnOverview;
|
||||||
}
|
}
|
||||||
return SettingsData.showDock;
|
return SettingsData.showDock;
|
||||||
}
|
}
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
readonly property real dockReserveZone: dockGeometry.reserveZone
|
implicitWidth: body.surfaceImplicitWidth
|
||||||
readonly property bool shouldReserveDockSpace: dockGeometry.shouldReserveSpace
|
implicitHeight: body.surfaceImplicitHeight
|
||||||
|
exclusiveZone: body.surfaceExclusiveZone
|
||||||
|
|
||||||
exclusiveZone: {
|
WindowBlur {
|
||||||
if (!dock.shouldReserveDockSpace)
|
targetWindow: dock
|
||||||
return -1;
|
blurEnabled: body.effectiveBlurEnabled && !body.usesConnectedFrameChrome
|
||||||
if (dock.frameDockExclusionActive)
|
blurX: body.blurX
|
||||||
return -1;
|
blurY: body.blurY
|
||||||
return dock.dockReserveZone;
|
blurWidth: body.blurWidth
|
||||||
}
|
blurHeight: body.blurHeight
|
||||||
|
blurRadius: body.blurRadius
|
||||||
property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35)
|
|
||||||
|
|
||||||
implicitWidth: isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
|
||||||
implicitHeight: !isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: maskItem
|
|
||||||
parent: dock.contentItem
|
|
||||||
visible: false
|
|
||||||
readonly property bool expanded: dock.reveal
|
|
||||||
x: {
|
|
||||||
const baseX = dockCore.x + dockMouseArea.x;
|
|
||||||
if (isVertical && SettingsData.dockPosition === SettingsData.Position.Right)
|
|
||||||
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
|
||||||
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
|
||||||
}
|
|
||||||
y: {
|
|
||||||
const baseY = dockCore.y + dockMouseArea.y;
|
|
||||||
if (!isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom)
|
|
||||||
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
|
||||||
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
|
||||||
}
|
|
||||||
width: dockMouseArea.width + (isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.horizontalConnectorExtent * 2 : 0)
|
|
||||||
height: dockMouseArea.height + (!isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.verticalConnectorExtent * 2 : 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: maskItem
|
item: body.inputMaskItem
|
||||||
|
}
|
||||||
|
|
||||||
|
DockBody {
|
||||||
|
id: body
|
||||||
|
anchors.fill: parent
|
||||||
|
hostWindow: dock
|
||||||
|
modelData: dock.modelData
|
||||||
|
contextMenu: dockVariants.contextMenu
|
||||||
|
trashContextMenu: dockVariants.trashContextMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: dockExclusion
|
id: dockExclusion
|
||||||
|
|
||||||
screen: dock.screen || dock.modelData
|
screen: dock.screen || dock.modelData
|
||||||
visible: dock.frameDockExclusionActive && dock.shouldReserveDockSpace
|
visible: body.frameDockExclusionActive && body.shouldReserveDockSpace
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
mask: Region {}
|
mask: Region {}
|
||||||
implicitWidth: dock.isVertical ? dock.dockReserveZone : 1
|
implicitWidth: body.isVertical ? body.dockReserveZone : 1
|
||||||
implicitHeight: dock.isVertical ? 1 : dock.dockReserveZone
|
implicitHeight: body.isVertical ? 1 : body.dockReserveZone
|
||||||
exclusiveZone: visible ? dock.dockReserveZone : -1
|
exclusiveZone: visible ? body.dockReserveZone : -1
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:dock-exclusion"
|
WlrLayershell.namespace: "dms:dock-exclusion"
|
||||||
WlrLayershell.layer: WlrLayer.Top
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
top: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
bottom: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||||
left: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
left: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||||
right: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
right: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property var hoveredButton: {
|
|
||||||
if (!dockApps.children[0]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const layoutItem = dockApps.children[0];
|
|
||||||
const flowLayout = layoutItem.children[0];
|
|
||||||
let repeater = null;
|
|
||||||
for (var i = 0; i < flowLayout.children.length; i++) {
|
|
||||||
const child = flowLayout.children[i];
|
|
||||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
|
||||||
repeater = child;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!repeater || !repeater.itemAt) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (var i = 0; i < repeater.count; i++) {
|
|
||||||
const item = repeater.itemAt(i);
|
|
||||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
|
||||||
return item.dockButton;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTooltip {
|
|
||||||
id: dockTooltip
|
|
||||||
targetScreen: dock.screen
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: tooltipRevealDelay
|
|
||||||
interval: 250
|
|
||||||
repeat: false
|
|
||||||
onTriggered: dock.showTooltipForHoveredButton()
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTooltipForHoveredButton() {
|
|
||||||
dockTooltip.hide();
|
|
||||||
if (!dock.hoveredButton || !dock.reveal || slideXAnimation.running || slideYAnimation.running)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const buttonLocalPos = dock.hoveredButton.mapToItem(null, 0, 0);
|
|
||||||
const tooltipText = dock.hoveredButton.tooltipText || "";
|
|
||||||
if (!tooltipText)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const screenHeight = dock.screen ? dock.screen.height : 0;
|
|
||||||
|
|
||||||
const gap = Theme.spacingS;
|
|
||||||
const bgMargin = dockGeometry.bodyEdgeMargin;
|
|
||||||
const btnW = dock.hoveredButton.width;
|
|
||||||
const btnH = dock.hoveredButton.height;
|
|
||||||
|
|
||||||
if (!dock.isVertical) {
|
|
||||||
const isBottom = SettingsData.dockPosition === SettingsData.Position.Bottom;
|
|
||||||
const tooltipX = buttonLocalPos.x + btnW / 2 + adjacentLeftBarWidth;
|
|
||||||
const tooltipHeight = 32;
|
|
||||||
const totalFromEdge = bgMargin + dockBackground.height + dock.borderThickness + gap;
|
|
||||||
const screenRelativeY = isBottom ? (screenHeight - totalFromEdge - tooltipHeight) : totalFromEdge;
|
|
||||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, false, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLeft = SettingsData.dockPosition === SettingsData.Position.Left;
|
|
||||||
const screenWidth = dock.screen ? dock.screen.width : 0;
|
|
||||||
const totalFromEdge = bgMargin + dockBackground.width + dock.borderThickness + gap;
|
|
||||||
const tooltipX = isLeft ? totalFromEdge : (screenWidth - totalFromEdge);
|
|
||||||
const screenRelativeY = buttonLocalPos.y + btnH / 2 + adjacentTopBarHeight;
|
|
||||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, isLeft, !isLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: dock
|
|
||||||
function onRevealChanged() {
|
|
||||||
if (!dock.reveal) {
|
|
||||||
tooltipRevealDelay.stop();
|
|
||||||
dockTooltip.hide();
|
|
||||||
} else {
|
|
||||||
tooltipRevealDelay.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHoveredButtonChanged() {
|
|
||||||
dock.showTooltipForHoveredButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dockCore
|
|
||||||
anchors.fill: parent
|
|
||||||
x: isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? animationHeadroom : 0
|
|
||||||
y: !isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? animationHeadroom : 0
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: dockMouseArea
|
|
||||||
function onContainsMouseChanged() {
|
|
||||||
if (dockMouseArea.containsMouse) {
|
|
||||||
dock.revealSticky = true;
|
|
||||||
revealHold.stop();
|
|
||||||
} else {
|
|
||||||
if (dock.autoHide && !dock.contextMenuOpen) {
|
|
||||||
revealHold.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dockMouseArea
|
|
||||||
property real currentScreen: modelData ? modelData : dock.screen
|
|
||||||
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
|
|
||||||
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
|
|
||||||
property real maxDockWidth: screenWidth * 0.98
|
|
||||||
property real maxDockHeight: screenHeight * 0.98
|
|
||||||
|
|
||||||
height: {
|
|
||||||
if (dock.isVertical) {
|
|
||||||
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
|
|
||||||
return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight);
|
|
||||||
}
|
|
||||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
|
||||||
}
|
|
||||||
width: {
|
|
||||||
if (dock.isVertical) {
|
|
||||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
|
||||||
}
|
|
||||||
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
|
|
||||||
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
|
|
||||||
}
|
|
||||||
anchors {
|
|
||||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
|
|
||||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
|
||||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
|
||||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
|
|
||||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
|
||||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
|
||||||
}
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dockContainer
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: false
|
|
||||||
opacity: dock.startupRevealDone ? 1 : 0
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
id: dockSlide
|
|
||||||
x: {
|
|
||||||
if (!dock.isVertical)
|
|
||||||
return 0;
|
|
||||||
if (dock.reveal)
|
|
||||||
return 0;
|
|
||||||
if (dock.usesConnectedFrameChrome) {
|
|
||||||
const retractDist = dockBackground.width + SettingsData.dockSpacing + 10;
|
|
||||||
return SettingsData.dockPosition === SettingsData.Position.Right ? retractDist : -retractDist;
|
|
||||||
}
|
|
||||||
const hideDistance = dockGeometry.motionThickness + 10;
|
|
||||||
if (SettingsData.dockPosition === SettingsData.Position.Right) {
|
|
||||||
return hideDistance;
|
|
||||||
} else {
|
|
||||||
return -hideDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
y: {
|
|
||||||
if (dock.isVertical)
|
|
||||||
return 0;
|
|
||||||
if (dock.reveal)
|
|
||||||
return 0;
|
|
||||||
if (dock.usesConnectedFrameChrome) {
|
|
||||||
const retractDist = dockBackground.height + SettingsData.dockSpacing + 10;
|
|
||||||
return SettingsData.dockPosition === SettingsData.Position.Bottom ? retractDist : -retractDist;
|
|
||||||
}
|
|
||||||
const hideDistance = dockGeometry.motionThickness + 10;
|
|
||||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
|
||||||
return hideDistance;
|
|
||||||
} else {
|
|
||||||
return -hideDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on x {
|
|
||||||
NumberAnimation {
|
|
||||||
id: slideXAnimation
|
|
||||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
|
||||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
|
||||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
|
||||||
onRunningChanged: if (!running)
|
|
||||||
dock._syncDockChromeState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on y {
|
|
||||||
NumberAnimation {
|
|
||||||
id: slideYAnimation
|
|
||||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
|
||||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
|
||||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
|
||||||
onRunningChanged: if (!running)
|
|
||||||
dock._syncDockChromeState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onXChanged: dock._queueSlideSync()
|
|
||||||
onYChanged: dock._queueSlideSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dockBackground
|
|
||||||
objectName: "dockBackground"
|
|
||||||
visible: dock.hasApps
|
|
||||||
anchors {
|
|
||||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined) : undefined
|
|
||||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
|
||||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
|
||||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined) : undefined
|
|
||||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
|
||||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
|
||||||
}
|
|
||||||
anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? dockGeometry.bodyEdgeMargin : 0
|
|
||||||
anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? dockGeometry.bodyEdgeMargin : 0
|
|
||||||
anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? dockGeometry.bodyEdgeMargin : 0
|
|
||||||
anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? dockGeometry.bodyEdgeMargin : 0
|
|
||||||
|
|
||||||
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
|
|
||||||
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
|
|
||||||
width: implicitWidth
|
|
||||||
height: implicitHeight
|
|
||||||
|
|
||||||
// Avoid an offscreen texture seam where the connected dock meets the frame.
|
|
||||||
layer.enabled: !usesConnectedFrameChrome
|
|
||||||
clip: false
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
|
||||||
color: dock.surfaceColor
|
|
||||||
topLeftRadius: dock.surfaceTopLeftRadius
|
|
||||||
topRightRadius: dock.surfaceTopRightRadius
|
|
||||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
|
||||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
|
||||||
color: "transparent"
|
|
||||||
topLeftRadius: dock.surfaceTopLeftRadius
|
|
||||||
topRightRadius: dock.surfaceTopRightRadius
|
|
||||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
|
||||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
|
||||||
border.color: dock.surfaceBorderColor
|
|
||||||
border.width: dock.surfaceBorderWidth
|
|
||||||
z: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync dockBackground geometry to ConnectedModeState
|
|
||||||
onXChanged: dock._syncDockChromeState()
|
|
||||||
onYChanged: dock._syncDockChromeState()
|
|
||||||
onWidthChanged: dock._syncDockChromeState()
|
|
||||||
onHeightChanged: dock._syncDockChromeState()
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: dockConnectedChrome
|
|
||||||
visible: Theme.isConnectedEffect && dock.reveal && dock.hasApps && !FrameTransitionState.effectiveConnectedFrameModeActive
|
|
||||||
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
|
||||||
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
|
||||||
readonly property real bodyRadius: dock.surfaceRadius
|
|
||||||
readonly property bool barTop: dock.connectedBarSide === "top"
|
|
||||||
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
|
||||||
readonly property bool barLeft: dock.connectedBarSide === "left"
|
|
||||||
readonly property bool barRight: dock.connectedBarSide === "right"
|
|
||||||
|
|
||||||
x: dockBackground.x - extraLeft
|
|
||||||
y: dockBackground.y - extraTop
|
|
||||||
width: dockBackground.width + extraLeft * 2
|
|
||||||
height: dockBackground.height + extraTop * 2
|
|
||||||
|
|
||||||
ShaderEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
|
|
||||||
|
|
||||||
property real widthPx: width
|
|
||||||
property real heightPx: height
|
|
||||||
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
|
|
||||||
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
|
|
||||||
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
|
|
||||||
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
|
|
||||||
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: dockBorderShape
|
|
||||||
x: dockBackground.x - borderThickness
|
|
||||||
y: dockBackground.y - borderThickness
|
|
||||||
width: dockBackground.width + borderThickness * 2
|
|
||||||
height: dockBackground.height + borderThickness * 2
|
|
||||||
visible: SettingsData.dockBorderEnabled && dock.hasApps && !usesConnectedFrameChrome
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
|
|
||||||
readonly property real borderThickness: Math.max(1, dock.borderThickness)
|
|
||||||
readonly property real i: borderThickness / 2
|
|
||||||
readonly property real cr: dock.surfaceRadius
|
|
||||||
readonly property real w: dockBackground.width
|
|
||||||
readonly property real h: dockBackground.height
|
|
||||||
|
|
||||||
readonly property color borderColor: {
|
|
||||||
const opacity = SettingsData.dockBorderOpacity;
|
|
||||||
switch (SettingsData.dockBorderColor) {
|
|
||||||
case "secondary":
|
|
||||||
return Theme.withAlpha(Theme.secondary, opacity);
|
|
||||||
case "primary":
|
|
||||||
return Theme.withAlpha(Theme.primary, opacity);
|
|
||||||
default:
|
|
||||||
return Theme.withAlpha(Theme.surfaceText, opacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
fillColor: "transparent"
|
|
||||||
strokeColor: dockBorderShape.borderColor
|
|
||||||
strokeWidth: dockBorderShape.borderThickness
|
|
||||||
joinStyle: ShapePath.RoundJoin
|
|
||||||
capStyle: ShapePath.FlatCap
|
|
||||||
|
|
||||||
PathSvg {
|
|
||||||
path: {
|
|
||||||
const bt = dockBorderShape.borderThickness;
|
|
||||||
const i = dockBorderShape.i;
|
|
||||||
const cr = dockBorderShape.cr + bt - i;
|
|
||||||
const w = dockBorderShape.w;
|
|
||||||
const h = dockBorderShape.h;
|
|
||||||
|
|
||||||
let d = `M ${i + cr} ${i}`;
|
|
||||||
d += ` L ${i + w + 2 * (bt - i) - cr} ${i}`;
|
|
||||||
if (cr > 0)
|
|
||||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i)} ${i + cr}`;
|
|
||||||
d += ` L ${i + w + 2 * (bt - i)} ${i + h + 2 * (bt - i) - cr}`;
|
|
||||||
if (cr > 0)
|
|
||||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i) - cr} ${i + h + 2 * (bt - i)}`;
|
|
||||||
d += ` L ${i + cr} ${i + h + 2 * (bt - i)}`;
|
|
||||||
if (cr > 0)
|
|
||||||
d += ` A ${cr} ${cr} 0 0 1 ${i} ${i + h + 2 * (bt - i) - cr}`;
|
|
||||||
d += ` L ${i} ${i + cr}`;
|
|
||||||
if (cr > 0)
|
|
||||||
d += ` A ${cr} ${cr} 0 0 1 ${i + cr} ${i}`;
|
|
||||||
d += " Z";
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DockApps {
|
|
||||||
id: dockApps
|
|
||||||
|
|
||||||
anchors.top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? dockBackground.top : undefined) : undefined
|
|
||||||
anchors.bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? dockBackground.bottom : undefined) : undefined
|
|
||||||
anchors.horizontalCenter: !dock.isVertical ? dockBackground.horizontalCenter : undefined
|
|
||||||
anchors.left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? dockBackground.left : undefined) : undefined
|
|
||||||
anchors.right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? dockBackground.right : undefined) : undefined
|
|
||||||
anchors.verticalCenter: dock.isVertical ? dockBackground.verticalCenter : undefined
|
|
||||||
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
|
||||||
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
|
||||||
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
|
||||||
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
|
||||||
|
|
||||||
contextMenu: dockVariants.contextMenu
|
|
||||||
trashContextMenu: dockVariants.trashContextMenu
|
|
||||||
groupByApp: dock.groupByApp
|
|
||||||
isVertical: dock.isVertical
|
|
||||||
dockScreen: dock.screen
|
|
||||||
iconSize: dock.widgetHeight
|
|
||||||
usesOverlayLayer: dock.usesOverlayLayer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -479,6 +479,8 @@ Item {
|
|||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: delegateItem
|
id: delegateItem
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
property var dockButton: {
|
property var dockButton: {
|
||||||
switch (itemData.type) {
|
switch (itemData.type) {
|
||||||
@@ -599,7 +601,7 @@ Item {
|
|||||||
height: delegateItem.height
|
height: delegateItem.height
|
||||||
actualIconSize: root.iconSize
|
actualIconSize: root.iconSize
|
||||||
dockApps: root
|
dockApps: root
|
||||||
index: model.index
|
index: delegateItem.index
|
||||||
}
|
}
|
||||||
|
|
||||||
DockTrashButton {
|
DockTrashButton {
|
||||||
@@ -624,7 +626,7 @@ Item {
|
|||||||
appData: itemData
|
appData: itemData
|
||||||
contextMenu: root.contextMenu
|
contextMenu: root.contextMenu
|
||||||
dockApps: root
|
dockApps: root
|
||||||
index: model.index
|
index: delegateItem.index
|
||||||
parentDockScreen: root.dockScreen
|
parentDockScreen: root.dockScreen
|
||||||
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||||
windowTitle: {
|
windowTitle: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user