mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-09 06:58:29 -04:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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")
|
||||||
|
}
|
||||||
@@ -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...a172b39841
Generated
+3
-3
@@ -3,11 +3,11 @@
|
|||||||
"dank-qml-common": {
|
"dank-qml-common": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784647395,
|
"lastModified": 1784935106,
|
||||||
"narHash": "sha256-kcLSJC8XBzmWoSBc90Zn81qrNsqWMkwQ1m/zZixOw30=",
|
"narHash": "sha256-aVgOBRynme6XNKYCZlf/oCjPDZFwddyUQPRRkEupC3c=",
|
||||||
"owner": "AvengeMedia",
|
"owner": "AvengeMedia",
|
||||||
"repo": "dank-qml-common",
|
"repo": "dank-qml-common",
|
||||||
"rev": "1919595f0dde4f5bf5ac0baa1902ff6d070971d5",
|
"rev": "a172b39841d8f42bac46ac133b76cade1fae91b4",
|
||||||
"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" ];
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,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();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -916,6 +934,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
|
||||||
@@ -1479,6 +1498,13 @@ Singleton {
|
|||||||
MangoService.generateLayoutConfig();
|
MangoService.generateLayoutConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateCompositorInput() {
|
||||||
|
if (typeof CompositorService === "undefined")
|
||||||
|
return;
|
||||||
|
if (CompositorService.isNiri && typeof NiriService !== "undefined")
|
||||||
|
NiriService.generateNiriInputConfig();
|
||||||
|
}
|
||||||
|
|
||||||
function updateFrameCompositorLayout() {
|
function updateFrameCompositorLayout() {
|
||||||
// Generate before begin() so compositor readiness is already pending at transitionRequested
|
// Generate before begin() so compositor readiness is already pending at transitionRequested
|
||||||
if (typeof CompositorService !== "undefined") {
|
if (typeof CompositorService !== "undefined") {
|
||||||
@@ -1707,6 +1733,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,
|
||||||
|
|||||||
@@ -144,7 +144,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;
|
||||||
|
|
||||||
@@ -1926,6 +1926,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,7 +1976,7 @@ 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" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
||||||
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
|
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
|
||||||
@@ -2131,8 +2172,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;
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 },
|
||||||
@@ -477,6 +495,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 },
|
||||||
|
|||||||
+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"
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -69,7 +69,62 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerDashTab(tabId) {
|
function dashSectionAnchor(section) {
|
||||||
|
let item;
|
||||||
|
switch (section) {
|
||||||
|
case "left":
|
||||||
|
item = barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection;
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
item = barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
item = barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection;
|
||||||
|
}
|
||||||
|
if (!item)
|
||||||
|
return null;
|
||||||
|
if (barWindow.isVertical)
|
||||||
|
return {
|
||||||
|
"pos": item.mapToItem(null, 0, item.height / 2),
|
||||||
|
"width": item.height
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"pos": item.mapToItem(null, 0, 0),
|
||||||
|
"width": item.width
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionDash(popout, position) {
|
||||||
|
if (!popout.setTriggerPosition) {
|
||||||
|
popout.triggerScreen = barWindow.screen;
|
||||||
|
return "center";
|
||||||
|
}
|
||||||
|
|
||||||
|
const explicit = position === "left" || position === "center" || position === "right";
|
||||||
|
const section = explicit ? position : (clockButtonRef?.section || "center");
|
||||||
|
const clockAnchor = clockButtonRef?.visualContent ? {
|
||||||
|
"pos": clockButtonRef.visualContent.mapToItem(null, 0, 0),
|
||||||
|
"width": clockButtonRef.visualWidth
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
let anchor;
|
||||||
|
if (!explicit && section !== "center" && clockAnchor)
|
||||||
|
anchor = clockAnchor;
|
||||||
|
else
|
||||||
|
anchor = dashSectionAnchor(section) || clockAnchor;
|
||||||
|
|
||||||
|
if (!anchor) {
|
||||||
|
popout.triggerScreen = barWindow.screen;
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
||||||
|
const pos = SettingsData.getPopupTriggerPosition(anchor.pos, barWindow.screen, barWindow.effectiveBarThickness, anchor.width, barConfig?.spacing ?? 4, barPosition, barConfig);
|
||||||
|
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerDashTab(tabId, position) {
|
||||||
const loader = PopoutService.dankDashPopoutLoader;
|
const loader = PopoutService.dankDashPopoutLoader;
|
||||||
if (!loader)
|
if (!loader)
|
||||||
return false;
|
return false;
|
||||||
@@ -78,38 +133,7 @@ Item {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let section = "center";
|
const section = positionDash(loader.item, position);
|
||||||
if (clockButtonRef && clockButtonRef.visualContent && loader.item.setTriggerPosition) {
|
|
||||||
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
|
||||||
section = clockButtonRef.section || "center";
|
|
||||||
|
|
||||||
let triggerPos, triggerWidth;
|
|
||||||
if (section === "center") {
|
|
||||||
const centerSection = barWindow.isVertical ? (barWindow.axis?.edge === "left" ? topBarContent.vCenterSection : topBarContent.vCenterSection) : topBarContent.hCenterSection;
|
|
||||||
if (centerSection) {
|
|
||||||
if (barWindow.isVertical) {
|
|
||||||
const centerY = centerSection.height / 2;
|
|
||||||
triggerPos = centerSection.mapToItem(null, 0, centerY);
|
|
||||||
triggerWidth = centerSection.height;
|
|
||||||
} else {
|
|
||||||
triggerPos = centerSection.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = centerSection.width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = clockButtonRef.visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = clockButtonRef.visualWidth;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = clockButtonRef.visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = clockButtonRef.visualWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, barConfig?.spacing ?? 4, barPosition, barConfig);
|
|
||||||
loader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
|
|
||||||
} else {
|
|
||||||
loader.item.triggerScreen = barWindow.screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loader.item.requestTab)
|
if (loader.item.requestTab)
|
||||||
loader.item.requestTab(tabId);
|
loader.item.requestTab(tabId);
|
||||||
PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
|
PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
|
||||||
@@ -808,16 +832,30 @@ Item {
|
|||||||
const pos = section.mapToItem(barWindow.hostWindow.contentItem, 0, 0);
|
const pos = section.mapToItem(barWindow.hostWindow.contentItem, 0, 0);
|
||||||
const implW = section.implicitWidth || 0;
|
const implW = section.implicitWidth || 0;
|
||||||
const implH = section.implicitHeight || 0;
|
const implH = section.implicitHeight || 0;
|
||||||
|
const contentSize = isCenter ? (section.contentSize || 0) : 0;
|
||||||
|
|
||||||
const offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
let offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
||||||
const offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
let offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
||||||
|
let w = implW;
|
||||||
|
let h = implH;
|
||||||
|
|
||||||
|
// index centering lays content out asymmetrically; use the real extent
|
||||||
|
if (contentSize > 0) {
|
||||||
|
if (barWindow.isVertical) {
|
||||||
|
offsetY = section.contentStart;
|
||||||
|
h = contentSize;
|
||||||
|
} else {
|
||||||
|
offsetX = section.contentStart;
|
||||||
|
w = contentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const edgePad = 2;
|
const edgePad = 2;
|
||||||
return {
|
return {
|
||||||
"x": pos.x + offsetX - edgePad,
|
"x": pos.x + offsetX - edgePad,
|
||||||
"y": pos.y + offsetY - edgePad,
|
"y": pos.y + offsetY - edgePad,
|
||||||
"w": implW + edgePad * 2,
|
"w": w + edgePad * 2,
|
||||||
"h": implH + edgePad * 2
|
"h": h + edgePad * 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,21 @@ PanelWindow {
|
|||||||
readonly property int barPos: body.barPos
|
readonly property int barPos: body.barPos
|
||||||
readonly property bool barRevealed: body.barRevealed
|
readonly property bool barRevealed: body.barRevealed
|
||||||
|
|
||||||
|
property alias controlCenterButtonRef: body.controlCenterButtonRef
|
||||||
|
property alias clockButtonRef: body.clockButtonRef
|
||||||
|
property alias systemUpdateButtonRef: body.systemUpdateButtonRef
|
||||||
|
|
||||||
function triggerSystemUpdate() {
|
function triggerSystemUpdate() {
|
||||||
body.triggerSystemUpdate();
|
body.triggerSystemUpdate();
|
||||||
}
|
}
|
||||||
function triggerControlCenter() {
|
function triggerControlCenter() {
|
||||||
body.triggerControlCenter();
|
body.triggerControlCenter();
|
||||||
}
|
}
|
||||||
function triggerDashTab(tabId) {
|
function triggerDashTab(tabId, position) {
|
||||||
body.triggerDashTab(tabId);
|
return body.triggerDashTab(tabId, position);
|
||||||
|
}
|
||||||
|
function positionDash(popout, position) {
|
||||||
|
return body.positionDash(popout, position);
|
||||||
}
|
}
|
||||||
function triggerWallpaperBrowser() {
|
function triggerWallpaperBrowser() {
|
||||||
body.triggerWallpaperBrowser();
|
body.triggerWallpaperBrowser();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -410,20 +410,55 @@ Item {
|
|||||||
id: maskItem
|
id: maskItem
|
||||||
visible: false
|
visible: false
|
||||||
readonly property bool expanded: dock.reveal
|
readonly property bool expanded: dock.reveal
|
||||||
|
readonly property bool chrome: dock.usesConnectedFrameChrome
|
||||||
|
readonly property bool atEndEdge: SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right
|
||||||
|
readonly property real innerReach: borderThickness + (SettingsData.appsDockEnlargeOnHover ? animationHeadroom : 0)
|
||||||
|
readonly property real bodyX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
||||||
|
readonly property real bodyY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
||||||
x: {
|
x: {
|
||||||
const baseX = dockCore.x + dockMouseArea.x;
|
if (chrome) {
|
||||||
if (isVertical && SettingsData.dockPosition === SettingsData.Position.Right)
|
const baseX = dockCore.x + dockMouseArea.x;
|
||||||
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
if (isVertical && atEndEdge)
|
||||||
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
||||||
|
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
||||||
|
}
|
||||||
|
if (!isVertical)
|
||||||
|
return dockCore.x + dockMouseArea.x - (expanded ? borderThickness : 0);
|
||||||
|
if (!atEndEdge)
|
||||||
|
return 0;
|
||||||
|
return expanded ? Math.min(bodyX - innerReach, dock.width - 1) : dock.width - 1;
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
const baseY = dockCore.y + dockMouseArea.y;
|
if (chrome) {
|
||||||
if (!isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom)
|
const baseY = dockCore.y + dockMouseArea.y;
|
||||||
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
if (!isVertical && atEndEdge)
|
||||||
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
||||||
|
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
||||||
|
}
|
||||||
|
if (isVertical)
|
||||||
|
return dockCore.y + dockMouseArea.y - (expanded ? borderThickness : 0);
|
||||||
|
if (!atEndEdge)
|
||||||
|
return 0;
|
||||||
|
return expanded ? Math.min(bodyY - innerReach, dock.height - 1) : dock.height - 1;
|
||||||
|
}
|
||||||
|
width: {
|
||||||
|
if (chrome)
|
||||||
|
return dockMouseArea.width + (isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.horizontalConnectorExtent * 2 : 0);
|
||||||
|
if (!isVertical)
|
||||||
|
return dockMouseArea.width + (expanded ? borderThickness * 2 : 0);
|
||||||
|
if (!expanded)
|
||||||
|
return 1;
|
||||||
|
return atEndEdge ? dock.width - x : Math.max(bodyX + dockBackground.width + innerReach, 1);
|
||||||
|
}
|
||||||
|
height: {
|
||||||
|
if (chrome)
|
||||||
|
return dockMouseArea.height + (!isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.verticalConnectorExtent * 2 : 0);
|
||||||
|
if (isVertical)
|
||||||
|
return dockMouseArea.height + (expanded ? borderThickness * 2 : 0);
|
||||||
|
if (!expanded)
|
||||||
|
return 1;
|
||||||
|
return atEndEdge ? dock.height - y : Math.max(bodyY + dockBackground.height + innerReach, 1);
|
||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property alias inputMaskItem: maskItem
|
readonly property alias inputMaskItem: maskItem
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ PanelWindow {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool active: false
|
property bool active: false
|
||||||
|
property bool _completed: false
|
||||||
|
|
||||||
signal fadeCompleted
|
signal fadeCompleted
|
||||||
signal fadeCancelled
|
signal fadeCancelled
|
||||||
@@ -36,7 +37,8 @@ PanelWindow {
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
onOpacityChanged: {
|
onOpacityChanged: {
|
||||||
if (opacity >= 0.99 && root.active) {
|
if (opacity >= 0.99 && root.active && !root._completed) {
|
||||||
|
root._completed = true;
|
||||||
root.fadeCompleted();
|
root.fadeCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +61,7 @@ PanelWindow {
|
|||||||
function startFade() {
|
function startFade() {
|
||||||
if (!SettingsData.fadeToDpmsEnabled)
|
if (!SettingsData.fadeToDpmsEnabled)
|
||||||
return;
|
return;
|
||||||
|
_completed = false;
|
||||||
active = true;
|
active = true;
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
@@ -74,6 +77,7 @@ PanelWindow {
|
|||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
active = false;
|
active = false;
|
||||||
|
_completed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendLockerReadyOnce() {
|
function sendLockerReadyOnce() {
|
||||||
|
if (root.demoMode)
|
||||||
|
return;
|
||||||
if (lockerReadySent)
|
if (lockerReadySent)
|
||||||
return;
|
return;
|
||||||
if (root.unlocking)
|
if (root.unlocking)
|
||||||
|
|||||||
@@ -31,14 +31,16 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property point anchorPos: Qt.point(0, 0)
|
property point anchorPos: Qt.point(0, 0)
|
||||||
property string anchorEdge: "top"
|
property bool isVertical: false
|
||||||
|
property string edge: "top"
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
function showAt(x, y, targetScreen, edge) {
|
function showAt(x, y, vertical, barEdge, targetScreen) {
|
||||||
if (targetScreen)
|
if (targetScreen)
|
||||||
root.screen = targetScreen;
|
root.screen = targetScreen;
|
||||||
anchorPos = Qt.point(x, y);
|
anchorPos = Qt.point(x, y);
|
||||||
anchorEdge = edge || "top";
|
isVertical = vertical ?? false;
|
||||||
|
edge = barEdge ?? "top";
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,21 +68,19 @@ PanelWindow {
|
|||||||
visible: root.visible
|
visible: root.visible
|
||||||
|
|
||||||
x: {
|
x: {
|
||||||
const left = 10;
|
if (root.isVertical) {
|
||||||
const right = root.width - width - 10;
|
if (root.edge === "left")
|
||||||
const want = root.anchorPos.x - width / 2;
|
return Math.min(root.width - width - 10, root.anchorPos.x);
|
||||||
return Math.max(left, Math.min(right, want));
|
return Math.max(10, root.anchorPos.x - width);
|
||||||
|
}
|
||||||
|
return Math.max(10, Math.min(root.width - width - 10, root.anchorPos.x - width / 2));
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
switch (root.anchorEdge) {
|
if (root.isVertical)
|
||||||
case "bottom":
|
|
||||||
return Math.max(10, root.anchorPos.y - height);
|
|
||||||
case "left":
|
|
||||||
case "right":
|
|
||||||
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
||||||
default:
|
if (root.edge === "bottom")
|
||||||
return Math.min(root.height - height - 10, root.anchorPos.y);
|
return Math.max(10, root.anchorPos.y - height);
|
||||||
}
|
return Math.min(root.height - height - 10, root.anchorPos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDismissed: root.closeMenu()
|
onDismissed: root.closeMenu()
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ PanelWindow {
|
|||||||
blurWidth: !win._finalized && !win.connectedFrameMode ? innerW * s : 0
|
blurWidth: !win._finalized && !win.connectedFrameMode ? innerW * s : 0
|
||||||
blurHeight: !win._finalized && !win.connectedFrameMode ? innerH * s : 0
|
blurHeight: !win._finalized && !win.connectedFrameMode ? innerH * s : 0
|
||||||
blurRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
blurRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||||
|
// Slide-out translates the card past the surface edge; intersecting with the
|
||||||
|
// resting bounds keeps the committed region on the visible content instead of
|
||||||
|
// leaving a stray blur sliver at the edge (#2917).
|
||||||
|
clipEnabled: true
|
||||||
|
clipX: content.x + content.cardInset
|
||||||
|
clipY: content.y + content.cardInset
|
||||||
|
clipWidth: innerW
|
||||||
|
clipHeight: innerH
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:notification-popup"
|
WlrLayershell.namespace: "dms:notification-popup"
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ DankOSD {
|
|||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
@@ -125,6 +130,11 @@ DankOSD {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property int gap: Theme.spacingS
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ DankOSD {
|
|||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
@@ -147,6 +152,11 @@ DankOSD {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property int gap: Theme.spacingS
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ DankOSD {
|
|||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
@@ -137,6 +142,11 @@ DankOSD {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property int gap: Theme.spacingS
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ DankOSD {
|
|||||||
width: parent.width - Theme.spacingS * 2
|
width: parent.width - Theme.spacingS * 2
|
||||||
height: 40
|
height: 40
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
@@ -132,6 +137,11 @@ DankOSD {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property int gap: Theme.spacingS
|
property int gap: Theme.spacingS
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Theme.iconSize
|
width: Theme.iconSize
|
||||||
height: Theme.iconSize
|
height: Theme.iconSize
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ Item {
|
|||||||
visible: DesktopService.autostartAvailable
|
visible: DesktopService.autostartAvailable
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "autostartAddEntry"
|
||||||
|
tags: ["autostart", "add", "entry", "command", "startup"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "add_circle"
|
iconName: "add_circle"
|
||||||
title: I18n.tr("Add Entry")
|
title: I18n.tr("Add Entry")
|
||||||
@@ -764,6 +766,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "autostartTrayIconFix"
|
||||||
|
tags: ["tray", "icons", "fix", "systemd"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "handyman"
|
iconName: "handyman"
|
||||||
title: I18n.tr("Tray Icon Fix")
|
title: I18n.tr("Tray Icon Fix")
|
||||||
|
|||||||
@@ -267,6 +267,24 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
tags: ["compositor", "toast"]
|
||||||
|
title: I18n.tr("Notifications")
|
||||||
|
settingKey: "compositorNotifications"
|
||||||
|
iconName: "notifications"
|
||||||
|
visible: CompositorService.isNiri || CompositorService.isMango
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["compositor", "toast", "reload"]
|
||||||
|
settingKey: "showConfigReloadToast"
|
||||||
|
text: I18n.tr("Show \"config reloaded\" Toast")
|
||||||
|
description: I18n.tr("Show a toast when the compositor config is reloaded")
|
||||||
|
checked: SessionData.showConfigReloadToast
|
||||||
|
onToggled: checked => SessionData.showConfigReloadToast = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "barEnable"
|
||||||
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
|
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
|
||||||
title: I18n.tr("Enable Bar")
|
title: I18n.tr("Enable Bar")
|
||||||
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
|
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
|
||||||
@@ -651,6 +652,8 @@ Item {
|
|||||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barAutoHide"
|
||||||
|
tags: ["autohide", "auto-hide", "reveal", "intellihide"]
|
||||||
text: I18n.tr("Auto-hide")
|
text: I18n.tr("Auto-hide")
|
||||||
description: I18n.tr("Automatically hide the bar when the pointer moves away")
|
description: I18n.tr("Automatically hide the bar when the pointer moves away")
|
||||||
checked: selectedBarConfig?.autoHide ?? false
|
checked: selectedBarConfig?.autoHide ?? false
|
||||||
@@ -700,6 +703,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barAutoHideStrict"
|
||||||
|
tags: ["autohide", "strict", "popout"]
|
||||||
width: parent.width - parent.leftPadding
|
width: parent.width - parent.leftPadding
|
||||||
text: I18n.tr("Strict auto-hide", "Dank bar setting: hide the bar when the pointer leaves even if a menu or bar popover is still open")
|
text: I18n.tr("Strict auto-hide", "Dank bar setting: hide the bar when the pointer leaves even if a menu or bar popover is still open")
|
||||||
description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
|
description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
|
||||||
@@ -713,6 +718,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barHideWhenWindowsOpen"
|
||||||
|
tags: ["hide", "windows", "empty", "workspace"]
|
||||||
width: parent.width - parent.leftPadding
|
width: parent.width - parent.leftPadding
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
||||||
text: I18n.tr("Hide When Windows Open")
|
text: I18n.tr("Hide When Windows Open")
|
||||||
@@ -734,6 +741,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barManualVisibility"
|
||||||
|
tags: ["manual", "show", "hide", "ipc", "toggle"]
|
||||||
text: I18n.tr("Manual Show/Hide")
|
text: I18n.tr("Manual Show/Hide")
|
||||||
description: I18n.tr("Toggle bar visibility manually via IPC")
|
description: I18n.tr("Toggle bar visibility manually via IPC")
|
||||||
checked: selectedBarConfig?.visible ?? true
|
checked: selectedBarConfig?.visible ?? true
|
||||||
@@ -753,6 +762,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barClickThrough"
|
||||||
|
tags: ["clickthrough", "click", "through", "mouse", "input", "mask", "passthrough"]
|
||||||
text: I18n.tr("Click Through")
|
text: I18n.tr("Click Through")
|
||||||
description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
|
description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
|
||||||
checked: selectedBarConfig?.clickThrough ?? false
|
checked: selectedBarConfig?.clickThrough ?? false
|
||||||
@@ -908,6 +919,8 @@ Item {
|
|||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: exclusiveZoneSlider
|
id: exclusiveZoneSlider
|
||||||
|
settingKey: "barExclusiveZone"
|
||||||
|
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
text: I18n.tr("Exclusive Zone Offset")
|
text: I18n.tr("Exclusive Zone Offset")
|
||||||
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
|
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
|
||||||
@@ -931,6 +944,8 @@ Item {
|
|||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: sizeSlider
|
id: sizeSlider
|
||||||
|
settingKey: "barSize"
|
||||||
|
tags: ["size", "thickness", "height", "inner"]
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
text: I18n.tr("Size")
|
text: I18n.tr("Size")
|
||||||
description: I18n.tr("Adjust the bar height via inner padding")
|
description: I18n.tr("Adjust the bar height via inner padding")
|
||||||
@@ -1152,6 +1167,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barSquareCorners"
|
||||||
|
tags: ["square", "corners", "rounding"]
|
||||||
text: I18n.tr("Square Corners")
|
text: I18n.tr("Square Corners")
|
||||||
description: I18n.tr("Remove corner rounding from the bar")
|
description: I18n.tr("Remove corner rounding from the bar")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1162,6 +1179,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barNoBackground"
|
||||||
|
tags: ["transparent", "background", "invisible"]
|
||||||
text: I18n.tr("No Background")
|
text: I18n.tr("No Background")
|
||||||
description: I18n.tr("Make the bar background fully transparent")
|
description: I18n.tr("Make the bar background fully transparent")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1172,6 +1191,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barMaximizeWidgetIcons"
|
||||||
|
tags: ["maximize", "icons", "stretch"]
|
||||||
text: I18n.tr("Maximize Widget Icons")
|
text: I18n.tr("Maximize Widget Icons")
|
||||||
description: I18n.tr("Stretch widget icons to fill the available bar height")
|
description: I18n.tr("Stretch widget icons to fill the available bar height")
|
||||||
checked: selectedBarConfig?.maximizeWidgetIcons ?? false
|
checked: selectedBarConfig?.maximizeWidgetIcons ?? false
|
||||||
@@ -1181,6 +1202,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barMaximizeWidgetText"
|
||||||
|
tags: ["maximize", "text", "stretch"]
|
||||||
text: I18n.tr("Maximize Widget Text")
|
text: I18n.tr("Maximize Widget Text")
|
||||||
description: I18n.tr("Stretch widget text to fill the available bar height")
|
description: I18n.tr("Stretch widget text to fill the available bar height")
|
||||||
checked: selectedBarConfig?.maximizeWidgetText ?? false
|
checked: selectedBarConfig?.maximizeWidgetText ?? false
|
||||||
@@ -1190,6 +1213,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barRemoveWidgetPadding"
|
||||||
|
tags: ["padding", "compact", "widgets"]
|
||||||
text: I18n.tr("Remove Widget Padding")
|
text: I18n.tr("Remove Widget Padding")
|
||||||
description: I18n.tr("Remove inner padding from all widgets")
|
description: I18n.tr("Remove inner padding from all widgets")
|
||||||
checked: selectedBarConfig?.removeWidgetPadding ?? false
|
checked: selectedBarConfig?.removeWidgetPadding ?? false
|
||||||
@@ -1206,6 +1231,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barGothCorners"
|
||||||
|
tags: ["goth", "corners", "concave", "cutout"]
|
||||||
text: I18n.tr("Goth Corners")
|
text: I18n.tr("Goth Corners")
|
||||||
description: I18n.tr("Apply inverse concave corner cutouts to the bar")
|
description: I18n.tr("Apply inverse concave corner cutouts to the bar")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1302,6 +1329,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
|
settingKey: "barMaximizeDetection"
|
||||||
|
tags: ["maximize", "gaps", "border", "fullscreen"]
|
||||||
iconName: "fit_screen"
|
iconName: "fit_screen"
|
||||||
title: I18n.tr("Maximize Detection")
|
title: I18n.tr("Maximize Detection")
|
||||||
description: I18n.tr("Remove gaps and border when windows are maximized")
|
description: I18n.tr("Remove gaps and border when windows are maximized")
|
||||||
@@ -1844,6 +1873,8 @@ Item {
|
|||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
iconName: "mouse"
|
iconName: "mouse"
|
||||||
|
settingKey: "barScrollWheel"
|
||||||
|
tags: ["scroll", "wheel", "workspace", "axis"]
|
||||||
title: I18n.tr("Scroll Wheel")
|
title: I18n.tr("Scroll Wheel")
|
||||||
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
||||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
|||||||
@@ -267,6 +267,8 @@ Item {
|
|||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsInternet"
|
||||||
|
tags: ["browser", "mail", "email", "web"]
|
||||||
title: I18n.tr("Internet", "Internet")
|
title: I18n.tr("Internet", "Internet")
|
||||||
iconName: "public"
|
iconName: "public"
|
||||||
|
|
||||||
@@ -293,6 +295,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsUtilities"
|
||||||
|
tags: ["file", "manager", "terminal", "editor"]
|
||||||
title: I18n.tr("Utilities", "Utilities")
|
title: I18n.tr("Utilities", "Utilities")
|
||||||
iconName: "terminal"
|
iconName: "terminal"
|
||||||
|
|
||||||
@@ -316,6 +320,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsDocuments"
|
||||||
|
tags: ["pdf", "text", "reader", "office"]
|
||||||
title: I18n.tr("Documents", "Documents")
|
title: I18n.tr("Documents", "Documents")
|
||||||
iconName: "edit_document"
|
iconName: "edit_document"
|
||||||
|
|
||||||
@@ -333,6 +339,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsMultimedia"
|
||||||
|
tags: ["image", "video", "music", "viewer", "player"]
|
||||||
title: I18n.tr("Multimedia", "Multimedia")
|
title: I18n.tr("Multimedia", "Multimedia")
|
||||||
iconName: "movie"
|
iconName: "movie"
|
||||||
AppSelector {
|
AppSelector {
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ Item {
|
|||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "desktopWidgetsManage"
|
||||||
|
tags: ["desktop", "widgets", "clock", "conky"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "widgets"
|
iconName: "widgets"
|
||||||
title: I18n.tr("Desktop Widgets")
|
title: I18n.tr("Desktop Widgets")
|
||||||
@@ -203,6 +205,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "desktopWidgetGroups"
|
||||||
|
tags: ["groups", "profiles", "layouts"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "folder"
|
iconName: "folder"
|
||||||
title: I18n.tr("Groups")
|
title: I18n.tr("Groups")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Singleton {
|
|||||||
readonly property var wlrOutputs: WlrOutputService.outputs
|
readonly property var wlrOutputs: WlrOutputService.outputs
|
||||||
property var outputs: ({})
|
property var outputs: ({})
|
||||||
property var savedOutputs: ({})
|
property var savedOutputs: ({})
|
||||||
|
property var savedParsedOutputs: ({})
|
||||||
property var allOutputs: buildAllOutputsMap()
|
property var allOutputs: buildAllOutputsMap()
|
||||||
|
|
||||||
property var includeStatus: ({
|
property var includeStatus: ({
|
||||||
@@ -942,15 +943,18 @@ Singleton {
|
|||||||
const paths = getConfigPaths();
|
const paths = getConfigPaths();
|
||||||
if (!paths) {
|
if (!paths) {
|
||||||
savedOutputs = {};
|
savedOutputs = {};
|
||||||
|
savedParsedOutputs = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
|
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
|
||||||
if (exitCode !== 0 || !content.trim()) {
|
if (exitCode !== 0 || !content.trim()) {
|
||||||
savedOutputs = {};
|
savedOutputs = {};
|
||||||
|
savedParsedOutputs = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parsed = parseOutputsConfig(content);
|
const parsed = parseOutputsConfig(content);
|
||||||
|
savedParsedOutputs = parsed;
|
||||||
const filtered = filterDisconnectedOnly(parsed);
|
const filtered = filterDisconnectedOnly(parsed);
|
||||||
savedOutputs = filtered;
|
savedOutputs = filtered;
|
||||||
|
|
||||||
@@ -1123,20 +1127,7 @@ Singleton {
|
|||||||
const name = match[1];
|
const name = match[1];
|
||||||
const body = match[2];
|
const body = match[2];
|
||||||
|
|
||||||
if (body.trim() === "off") {
|
const disabled = /^\s*off\s*$/m.test(body);
|
||||||
result[name] = {
|
|
||||||
"name": name,
|
|
||||||
"disabled": true,
|
|
||||||
"logical": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"scale": 1.0,
|
|
||||||
"transform": "Normal"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
||||||
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
||||||
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
||||||
@@ -1146,6 +1137,7 @@ Singleton {
|
|||||||
|
|
||||||
result[name] = {
|
result[name] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"disabled": disabled,
|
||||||
"logical": {
|
"logical": {
|
||||||
"x": posMatch ? parseInt(posMatch[1]) : 0,
|
"x": posMatch ? parseInt(posMatch[1]) : 0,
|
||||||
"y": posMatch ? parseInt(posMatch[2]) : 0,
|
"y": posMatch ? parseInt(posMatch[2]) : 0,
|
||||||
@@ -1809,6 +1801,41 @@ Singleton {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSavedParsedOutput(outputName) {
|
||||||
|
const output = outputs[outputName];
|
||||||
|
const candidates = [outputName];
|
||||||
|
if (output?.make && output?.model) {
|
||||||
|
candidates.push(output.make + " " + output.model + " " + (output.serial || "Unknown"));
|
||||||
|
candidates.push(output.make + " " + output.model);
|
||||||
|
}
|
||||||
|
for (const savedName in savedParsedOutputs) {
|
||||||
|
if (candidates.includes(savedName.trim()))
|
||||||
|
return savedParsedOutputs[savedName];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A disabled wlr head reports no current mode, scale 0 and position 0,0 —
|
||||||
|
// overlay the last written config so rewrites don't discard real settings.
|
||||||
|
function overlaySavedDisabledOutput(entry, outputName) {
|
||||||
|
const saved = findSavedParsedOutput(outputName);
|
||||||
|
if (!saved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const savedMode = saved.modes?.[saved.current_mode ?? 0];
|
||||||
|
if (savedMode && !entry.configured_mode)
|
||||||
|
entry.configured_mode = savedMode.width + "x" + savedMode.height + "@" + (savedMode.refresh_rate / 1000).toFixed(3);
|
||||||
|
if (saved.vrr_enabled !== undefined)
|
||||||
|
entry.vrr_enabled = saved.vrr_enabled;
|
||||||
|
if (!saved.logical || !entry.logical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entry.logical.x = saved.logical.x;
|
||||||
|
entry.logical.y = saved.logical.y;
|
||||||
|
entry.logical.scale = saved.logical.scale;
|
||||||
|
entry.logical.transform = saved.logical.transform;
|
||||||
|
}
|
||||||
|
|
||||||
function buildOutputsWithPendingChanges() {
|
function buildOutputsWithPendingChanges() {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
@@ -1818,7 +1845,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const outputName in outputs) {
|
for (const outputName in outputs) {
|
||||||
result[outputName] = JSON.parse(JSON.stringify(outputs[outputName]));
|
const entry = JSON.parse(JSON.stringify(outputs[outputName]));
|
||||||
|
if (entry.enabled === false)
|
||||||
|
overlaySavedDisabledOutput(entry, outputName);
|
||||||
|
result[outputName] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const outputName in pendingChanges) {
|
for (const outputName in pendingChanges) {
|
||||||
@@ -1830,6 +1860,8 @@ Singleton {
|
|||||||
result[outputName].logical.y = changes.position.y;
|
result[outputName].logical.y = changes.position.y;
|
||||||
}
|
}
|
||||||
if (changes.mode !== undefined && result[outputName].modes) {
|
if (changes.mode !== undefined && result[outputName].modes) {
|
||||||
|
if (result[outputName].configured_mode)
|
||||||
|
result[outputName].configured_mode = changes.mode;
|
||||||
for (var i = 0; i < result[outputName].modes.length; i++) {
|
for (var i = 0; i < result[outputName].modes.length; i++) {
|
||||||
if (formatMode(result[outputName].modes[i]) === changes.mode) {
|
if (formatMode(result[outputName].modes[i]) === changes.mode) {
|
||||||
result[outputName].current_mode = i;
|
result[outputName].current_mode = i;
|
||||||
|
|||||||
@@ -617,6 +617,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
|
settingKey: "dockExclusiveZone"
|
||||||
|
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||||
text: I18n.tr("Exclusive Zone Offset")
|
text: I18n.tr("Exclusive Zone Offset")
|
||||||
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
|
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
|
||||||
value: SettingsData.dockBottomGap
|
value: SettingsData.dockBottomGap
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ Item {
|
|||||||
settingKey: "mediaPlayer"
|
settingKey: "mediaPlayer"
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaWaveProgress"
|
||||||
|
tags: ["wave", "progress", "animated"]
|
||||||
text: I18n.tr("Wave Progress Bars")
|
text: I18n.tr("Wave Progress Bars")
|
||||||
description: I18n.tr("Use animated wave progress bars for media playback")
|
description: I18n.tr("Use animated wave progress bars for media playback")
|
||||||
checked: SettingsData.waveProgressEnabled
|
checked: SettingsData.waveProgressEnabled
|
||||||
@@ -45,6 +47,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaScrollTitle"
|
||||||
|
tags: ["scroll", "title", "marquee"]
|
||||||
text: I18n.tr("Scroll song title")
|
text: I18n.tr("Scroll song title")
|
||||||
description: I18n.tr("Scroll title if it doesn't fit in widget")
|
description: I18n.tr("Scroll title if it doesn't fit in widget")
|
||||||
checked: SettingsData.scrollTitleEnabled
|
checked: SettingsData.scrollTitleEnabled
|
||||||
@@ -52,6 +56,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaVisualizer"
|
||||||
|
tags: ["visualizer", "cava", "spectrum"]
|
||||||
text: I18n.tr("Audio Visualizer")
|
text: I18n.tr("Audio Visualizer")
|
||||||
description: I18n.tr("Show cava audio visualizer in media widget")
|
description: I18n.tr("Show cava audio visualizer in media widget")
|
||||||
checked: SettingsData.audioVisualizerEnabled
|
checked: SettingsData.audioVisualizerEnabled
|
||||||
@@ -59,6 +65,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaAdaptiveWidth"
|
||||||
|
tags: ["adaptive", "width", "shrink"]
|
||||||
text: I18n.tr("Adaptive Media Width")
|
text: I18n.tr("Adaptive Media Width")
|
||||||
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
||||||
checked: SettingsData.mediaAdaptiveWidthEnabled
|
checked: SettingsData.mediaAdaptiveWidthEnabled
|
||||||
@@ -66,6 +74,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaAlbumArtAccent"
|
||||||
|
tags: ["album", "art", "accent", "colors"]
|
||||||
text: I18n.tr("Use album art accent")
|
text: I18n.tr("Use album art accent")
|
||||||
description: I18n.tr("Use colors extracted from album art instead of system theme colors")
|
description: I18n.tr("Use colors extracted from album art instead of system theme colors")
|
||||||
checked: SettingsData.mediaUseAlbumArtAccent
|
checked: SettingsData.mediaUseAlbumArtAccent
|
||||||
@@ -136,6 +146,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaDeviceScrollVolume"
|
||||||
|
tags: ["device", "scroll", "volume"]
|
||||||
text: I18n.tr("Device list scroll volume")
|
text: I18n.tr("Device list scroll volume")
|
||||||
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
|
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
|
||||||
checked: SettingsData.audioDeviceScrollVolumeEnabled
|
checked: SettingsData.audioDeviceScrollVolumeEnabled
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
import qs.Modules.Settings.Widgets
|
||||||
|
import "../../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
|
property var inputIncludeStatus: ({
|
||||||
|
"exists": false,
|
||||||
|
"included": false,
|
||||||
|
"configFormat": "",
|
||||||
|
"readOnly": false
|
||||||
|
})
|
||||||
|
property bool checkingInclude: false
|
||||||
|
property bool fixingInclude: false
|
||||||
|
|
||||||
|
function getInputConfigPaths() {
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
||||||
|
if (CompositorService.compositor !== "niri")
|
||||||
|
return null;
|
||||||
|
return {
|
||||||
|
"configFile": configDir + "/niri/config.kdl",
|
||||||
|
"layoutFile": configDir + "/niri/dms/input.kdl",
|
||||||
|
"grepPattern": 'include.*"dms/input.kdl"',
|
||||||
|
"includeLine": 'include "dms/input.kdl"'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInputIncludeStatus() {
|
||||||
|
if (CompositorService.compositor !== "niri") {
|
||||||
|
inputIncludeStatus = {
|
||||||
|
"exists": false,
|
||||||
|
"included": false,
|
||||||
|
"configFormat": "",
|
||||||
|
"readOnly": false
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingInclude = true;
|
||||||
|
Proc.runCommand("check-input-include", [Proc.dmsBin, "config", "resolve-include", "niri", "input.kdl"], (output, exitCode) => {
|
||||||
|
checkingInclude = false;
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
inputIncludeStatus = {
|
||||||
|
"exists": false,
|
||||||
|
"included": false,
|
||||||
|
"configFormat": "",
|
||||||
|
"readOnly": false
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
inputIncludeStatus = JSON.parse(output.trim());
|
||||||
|
} catch (e) {
|
||||||
|
inputIncludeStatus = {
|
||||||
|
"exists": false,
|
||||||
|
"included": false,
|
||||||
|
"configFormat": "",
|
||||||
|
"readOnly": false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixInputInclude() {
|
||||||
|
const paths = getInputConfigPaths();
|
||||||
|
if (!paths)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fixingInclude = true;
|
||||||
|
const unixTime = Math.floor(Date.now() / 1000);
|
||||||
|
const backupFile = paths.configFile + ".backup" + unixTime;
|
||||||
|
const script = ConfigIncludeResolve.buildRepairScript({
|
||||||
|
configFile: paths.configFile,
|
||||||
|
backupFile: backupFile,
|
||||||
|
fragmentFile: paths.layoutFile,
|
||||||
|
grepPattern: paths.grepPattern,
|
||||||
|
includeLine: paths.includeLine
|
||||||
|
});
|
||||||
|
Proc.runCommand("fix-input-include", ["sh", "-c", script], (output, exitCode) => {
|
||||||
|
fixingInclude = false;
|
||||||
|
if (exitCode !== 0)
|
||||||
|
return;
|
||||||
|
checkInputIncludeStatus();
|
||||||
|
SettingsData.updateCompositorInput();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
checkInputIncludeStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
contentHeight: settingsColumn.height + Theme.spacingXL
|
||||||
|
contentWidth: width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: settingsColumn
|
||||||
|
|
||||||
|
topPadding: 4
|
||||||
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: warningBox
|
||||||
|
width: parent.width
|
||||||
|
height: warningContent.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
readonly property bool showSetup: !root.inputIncludeStatus.included
|
||||||
|
|
||||||
|
color: showSetup ? Theme.withAlpha(Theme.primary, 0.15) : Theme.withAlpha(Theme.primary, 0)
|
||||||
|
border.color: showSetup ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.primary, 0)
|
||||||
|
border.width: 1
|
||||||
|
visible: showSetup && !root.checkingInclude && CompositorService.isNiri
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: warningContent
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "warning"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("First Time Setup")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/input")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
id: fixButton
|
||||||
|
visible: warningBox.showSetup
|
||||||
|
text: root.fixingInclude ? I18n.tr("Setting up...") : I18n.tr("Setup")
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.primaryText
|
||||||
|
enabled: !root.fixingInclude
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: root.fixInputInclude()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
tags: ["mouse", "input", "sensitivity", "acceleration", "pointer"]
|
||||||
|
title: I18n.tr("Mouse Settings")
|
||||||
|
settingKey: "mouseSettings"
|
||||||
|
iconName: "mouse"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["mouse", "left", "handed", "button", "swap"]
|
||||||
|
settingKey: "mouseLeftHanded"
|
||||||
|
text: I18n.tr("Left-Handed Mode")
|
||||||
|
description: I18n.tr("Swap primary and secondary mouse buttons")
|
||||||
|
checked: SettingsData.mouseLeftHanded
|
||||||
|
onToggled: checked => SettingsData.set("mouseLeftHanded", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tags: ["mouse", "sensitivity", "speed", "accel"]
|
||||||
|
settingKey: "mouseAccelSpeed"
|
||||||
|
text: I18n.tr("Pointer Speed")
|
||||||
|
description: I18n.tr("Adjust pointer sensitivity speed")
|
||||||
|
value: Math.round(SettingsData.mouseAccelSpeed * 10)
|
||||||
|
minimum: -10
|
||||||
|
maximum: 10
|
||||||
|
step: 1
|
||||||
|
defaultValue: 0
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("mouseAccelSpeed", newValue / 10.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
tags: ["mouse", "acceleration", "profile"]
|
||||||
|
settingKey: "mouseAccelProfile"
|
||||||
|
text: I18n.tr("Acceleration Profile")
|
||||||
|
description: I18n.tr("Flat uses constant speed; Adaptive scales with movement speed")
|
||||||
|
model: [I18n.tr("Default"), I18n.tr("Flat"), I18n.tr("Adaptive")]
|
||||||
|
currentIndex: {
|
||||||
|
if (SettingsData.mouseAccelProfile === "flat") return 1;
|
||||||
|
if (SettingsData.mouseAccelProfile === "adaptive") return 2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected) return;
|
||||||
|
const profiles = ["default", "flat", "adaptive"];
|
||||||
|
SettingsData.set("mouseAccelProfile", profiles[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["mouse", "natural", "scroll", "direction"]
|
||||||
|
settingKey: "mouseNaturalScroll"
|
||||||
|
text: I18n.tr("Natural Scrolling")
|
||||||
|
description: I18n.tr("Reverse mouse wheel scrolling direction")
|
||||||
|
checked: SettingsData.mouseNaturalScroll
|
||||||
|
onToggled: checked => SettingsData.set("mouseNaturalScroll", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tags: ["mouse", "scroll", "speed", "factor"]
|
||||||
|
settingKey: "mouseScrollFactor"
|
||||||
|
text: I18n.tr("Scrolling Speed")
|
||||||
|
description: I18n.tr("Adjust scrolling sensitivity multiplier")
|
||||||
|
value: Math.round(SettingsData.mouseScrollFactor * 10)
|
||||||
|
minimum: 1
|
||||||
|
maximum: 30
|
||||||
|
step: 1
|
||||||
|
defaultValue: 10
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("mouseScrollFactor", newValue / 10.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
tags: ["mouse", "scroll", "method"]
|
||||||
|
settingKey: "mouseScrollMethod"
|
||||||
|
text: I18n.tr("Scroll Method")
|
||||||
|
description: I18n.tr("Choose when to generate scrolling events")
|
||||||
|
model: [I18n.tr("Default"), I18n.tr("No Scroll"), I18n.tr("On Button Down")]
|
||||||
|
currentIndex: {
|
||||||
|
if (SettingsData.mouseScrollMethod === "no-scroll") return 1;
|
||||||
|
if (SettingsData.mouseScrollMethod === "on-button-down") return 2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected) return;
|
||||||
|
const methods = ["default", "no-scroll", "on-button-down"];
|
||||||
|
SettingsData.set("mouseScrollMethod", methods[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["mouse", "middle", "click", "emulation"]
|
||||||
|
settingKey: "mouseMiddleEmulation"
|
||||||
|
text: I18n.tr("Middle Click Emulation")
|
||||||
|
description: I18n.tr("Emulate middle click by pressing left and right buttons")
|
||||||
|
checked: SettingsData.mouseMiddleEmulation
|
||||||
|
onToggled: checked => SettingsData.set("mouseMiddleEmulation", checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
tags: ["touchpad", "input", "sensitivity", "tap", "click", "natural"]
|
||||||
|
title: I18n.tr("Touchpad Settings")
|
||||||
|
settingKey: "touchpadSettings"
|
||||||
|
iconName: "trackpad_input_2"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "tap", "click"]
|
||||||
|
settingKey: "touchpadTapToClick"
|
||||||
|
text: I18n.tr("Tap to Click")
|
||||||
|
description: I18n.tr("Tap the touchpad surface to trigger left click clicks")
|
||||||
|
checked: SettingsData.touchpadTapToClick
|
||||||
|
onToggled: checked => SettingsData.set("touchpadTapToClick", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "tap", "drag"]
|
||||||
|
settingKey: "touchpadTapAndDrag"
|
||||||
|
text: I18n.tr("Tap and Drag")
|
||||||
|
description: I18n.tr("Tap and drag on the touchpad to move items")
|
||||||
|
checked: SettingsData.touchpadTapAndDrag
|
||||||
|
onToggled: checked => SettingsData.set("touchpadTapAndDrag", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "drag", "lock"]
|
||||||
|
settingKey: "touchpadDragLock"
|
||||||
|
text: I18n.tr("Drag Lock")
|
||||||
|
description: I18n.tr("Keep dragging when finger is briefly lifted")
|
||||||
|
checked: SettingsData.touchpadDragLock
|
||||||
|
onToggled: checked => SettingsData.set("touchpadDragLock", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tags: ["touchpad", "sensitivity", "speed", "accel"]
|
||||||
|
settingKey: "touchpadAccelSpeed"
|
||||||
|
text: I18n.tr("Touchpad Speed")
|
||||||
|
description: I18n.tr("Adjust touchpad pointer speed")
|
||||||
|
value: Math.round(SettingsData.touchpadAccelSpeed * 10)
|
||||||
|
minimum: -10
|
||||||
|
maximum: 10
|
||||||
|
step: 1
|
||||||
|
defaultValue: 0
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("touchpadAccelSpeed", newValue / 10.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
tags: ["touchpad", "acceleration", "profile"]
|
||||||
|
settingKey: "touchpadAccelProfile"
|
||||||
|
text: I18n.tr("Acceleration Profile")
|
||||||
|
description: I18n.tr("Flat uses constant speed; Adaptive scales with movement speed")
|
||||||
|
model: [I18n.tr("Default"), I18n.tr("Flat"), I18n.tr("Adaptive")]
|
||||||
|
currentIndex: {
|
||||||
|
if (SettingsData.touchpadAccelProfile === "flat") return 1;
|
||||||
|
if (SettingsData.touchpadAccelProfile === "adaptive") return 2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected) return;
|
||||||
|
const profiles = ["default", "flat", "adaptive"];
|
||||||
|
SettingsData.set("touchpadAccelProfile", profiles[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "natural", "scroll", "direction"]
|
||||||
|
settingKey: "touchpadNaturalScroll"
|
||||||
|
text: I18n.tr("Natural Scrolling")
|
||||||
|
description: I18n.tr("Reverse two-finger scrolling direction")
|
||||||
|
checked: SettingsData.touchpadNaturalScroll
|
||||||
|
onToggled: checked => SettingsData.set("touchpadNaturalScroll", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
tags: ["touchpad", "scroll", "speed", "factor"]
|
||||||
|
settingKey: "touchpadScrollFactor"
|
||||||
|
text: I18n.tr("Scrolling Speed")
|
||||||
|
description: I18n.tr("Adjust scrolling sensitivity multiplier")
|
||||||
|
value: Math.round(SettingsData.touchpadScrollFactor * 10)
|
||||||
|
minimum: 1
|
||||||
|
maximum: 30
|
||||||
|
step: 1
|
||||||
|
defaultValue: 10
|
||||||
|
onSliderValueChanged: newValue => SettingsData.set("touchpadScrollFactor", newValue / 10.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsButtonGroupRow {
|
||||||
|
tags: ["touchpad", "scroll", "method"]
|
||||||
|
settingKey: "touchpadScrollMethod"
|
||||||
|
text: I18n.tr("Scroll Method")
|
||||||
|
description: I18n.tr("Choose when to generate scrolling events")
|
||||||
|
model: [I18n.tr("Default"), I18n.tr("Two Finger"), I18n.tr("Edge"), I18n.tr("No Scroll"), I18n.tr("On Button Down")]
|
||||||
|
currentIndex: {
|
||||||
|
if (SettingsData.touchpadScrollMethod === "two-finger") return 1;
|
||||||
|
if (SettingsData.touchpadScrollMethod === "edge") return 2;
|
||||||
|
if (SettingsData.touchpadScrollMethod === "no-scroll") return 3;
|
||||||
|
if (SettingsData.touchpadScrollMethod === "on-button-down") return 4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
if (!selected) return;
|
||||||
|
const methods = ["default", "two-finger", "edge", "no-scroll", "on-button-down"];
|
||||||
|
SettingsData.set("touchpadScrollMethod", methods[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "disable", "typing", "dwt"]
|
||||||
|
settingKey: "touchpadDisableWhileTyping"
|
||||||
|
text: I18n.tr("Disable While Typing")
|
||||||
|
description: I18n.tr("Prevent accidental cursor jumps while typing")
|
||||||
|
checked: SettingsData.touchpadDisableWhileTyping
|
||||||
|
onToggled: checked => SettingsData.set("touchpadDisableWhileTyping", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "disable", "external", "mouse"]
|
||||||
|
settingKey: "touchpadDisableOnExternalMouse"
|
||||||
|
text: I18n.tr("Disable on External Mouse")
|
||||||
|
description: I18n.tr("Disable touchpad when an external mouse is connected")
|
||||||
|
checked: SettingsData.touchpadDisableOnExternalMouse
|
||||||
|
onToggled: checked => SettingsData.set("touchpadDisableOnExternalMouse", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
tags: ["touchpad", "middle", "click", "emulation"]
|
||||||
|
settingKey: "touchpadMiddleEmulation"
|
||||||
|
text: I18n.tr("Middle Click Emulation")
|
||||||
|
description: I18n.tr("Emulate middle click by pressing left and right buttons")
|
||||||
|
checked: SettingsData.touchpadMiddleEmulation
|
||||||
|
onToggled: checked => SettingsData.set("touchpadMiddleEmulation", checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,6 +80,7 @@ Item {
|
|||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "mux"
|
tab: "mux"
|
||||||
tags: ["mux", "session", "filter", "exclude", "hide"]
|
tags: ["mux", "session", "filter", "exclude", "hide"]
|
||||||
|
settingKey: "muxSessionFilter"
|
||||||
title: I18n.tr("Session Filter")
|
title: I18n.tr("Session Filter")
|
||||||
iconName: "filter_list"
|
iconName: "filter_list"
|
||||||
|
|
||||||
|
|||||||
@@ -844,6 +844,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
settingKey: "notificationIgnoreAppTimeout"
|
||||||
|
tags: ["notification", "timeout", "expire", "app", "override", "duration"]
|
||||||
|
text: I18n.tr("Ignore App-Requested Timeout")
|
||||||
|
description: I18n.tr("Always use the durations above, even if an app requests a shorter or longer one")
|
||||||
|
checked: SettingsData.notificationIgnoreAppTimeout
|
||||||
|
onToggled: checked => SettingsData.set("notificationIgnoreAppTimeout", checked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
|
settingKey: "systemUpdaterCheckInterval"
|
||||||
|
tags: ["interval", "poll", "frequency"]
|
||||||
text: I18n.tr("Check interval")
|
text: I18n.tr("Check interval")
|
||||||
description: I18n.tr("How often the server polls for new updates.")
|
description: I18n.tr("How often the server polls for new updates.")
|
||||||
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
||||||
@@ -157,6 +159,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterCheckOnStart"
|
||||||
|
tags: ["startup", "check", "boot"]
|
||||||
text: I18n.tr("Check on startup")
|
text: I18n.tr("Check on startup")
|
||||||
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
||||||
checked: SettingsData.updaterCheckOnStart
|
checked: SettingsData.updaterCheckOnStart
|
||||||
@@ -164,6 +168,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterFlatpak"
|
||||||
|
tags: ["flatpak", "include"]
|
||||||
text: I18n.tr("Include Flatpak updates")
|
text: I18n.tr("Include Flatpak updates")
|
||||||
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
||||||
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
||||||
@@ -172,6 +178,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterAUR"
|
||||||
|
tags: ["aur", "paru", "yay"]
|
||||||
text: I18n.tr("Include AUR updates")
|
text: I18n.tr("Include AUR updates")
|
||||||
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
||||||
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
||||||
@@ -328,6 +336,8 @@ Item {
|
|||||||
settingKey: "systemUpdaterAdvanced"
|
settingKey: "systemUpdaterAdvanced"
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterCustomCommand"
|
||||||
|
tags: ["custom", "command", "terminal"]
|
||||||
text: I18n.tr("Use Custom Command")
|
text: I18n.tr("Use Custom Command")
|
||||||
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
||||||
checked: SettingsData.updaterUseCustomCommand
|
checked: SettingsData.updaterUseCustomCommand
|
||||||
|
|||||||
@@ -167,7 +167,13 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - Theme.spacingM
|
height: parent.height - searchField.height - Theme.spacingM
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
model: root.filteredApps
|
// Start with no model. It is (re)bound in show() so that each open
|
||||||
|
// gets a fresh QQmlDelegateModel, avoiding a crash where a stale
|
||||||
|
// incubation queue from a previous open races with a background
|
||||||
|
// refreshApplications() replacing the underlying DesktopEntries
|
||||||
|
// QObjectModel (SIGSEGV in QQmlIncubatorPrivate::incubate via
|
||||||
|
// libQt6QmlModels). See hide()/onVisibleChanged() for the teardown.
|
||||||
|
model: null
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
@@ -316,11 +322,21 @@ FloatingWindow {
|
|||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
|
// Rebind the model reactively (search relies on filteredApps changes flowing
|
||||||
|
// through), and do it after populating so the ListView starts incubating from
|
||||||
|
// a fully-formed array rather than [].
|
||||||
|
appList.model = Qt.binding(() => root.filteredApps);
|
||||||
visible = true;
|
visible = true;
|
||||||
Qt.callLater(() => searchField.forceActiveFocus());
|
Qt.callLater(() => searchField.forceActiveFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
// Drop the model before clearing visible, so the ListView releases its
|
||||||
|
// QQmlDelegateModel (and any in-flight incubators) synchronously on this
|
||||||
|
// frame. This is the crux of the fix: a later show() will allocate a fresh
|
||||||
|
// DelegateModel instead of reusing one whose incubation queue may hold
|
||||||
|
// references invalidated by a concurrent refreshApplications().
|
||||||
|
appList.model = null;
|
||||||
visible = false;
|
visible = false;
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
filteredApps = [];
|
filteredApps = [];
|
||||||
@@ -330,6 +346,9 @@ FloatingWindow {
|
|||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
|
// Guard against visibility being cleared without going through hide()
|
||||||
|
// (e.g. window manager close, FloatingWindow.onClosed -> hide()).
|
||||||
|
appList.model = null;
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
filteredApps = [];
|
filteredApps = [];
|
||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
|
|||||||
@@ -17,7 +17,22 @@ Singleton {
|
|||||||
interval: 500
|
interval: 500
|
||||||
repeat: false
|
repeat: false
|
||||||
running: true
|
running: true
|
||||||
onTriggered: root.suppressSound = false
|
onTriggered: {
|
||||||
|
root.suppressSound = false;
|
||||||
|
root.applyPowerProfile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPowerProfile() {
|
||||||
|
if (!batteryAvailable)
|
||||||
|
return;
|
||||||
|
const profileValue = isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||||
|
if (profileValue === "")
|
||||||
|
return;
|
||||||
|
const targetProfile = parseInt(profileValue);
|
||||||
|
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
||||||
|
return;
|
||||||
|
PowerProfiles.profile = targetProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
||||||
@@ -206,14 +221,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileValue = BatteryService.isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
applyPowerProfile();
|
||||||
|
|
||||||
if (profileValue !== "") {
|
|
||||||
const targetProfile = parseInt(profileValue);
|
|
||||||
if (!isNaN(targetProfile) && PowerProfiles.profile !== targetProfile) {
|
|
||||||
PowerProfiles.profile = targetProfile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousPluggedState = isPluggedIn;
|
previousPluggedState = isPluggedIn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ Item {
|
|||||||
property string socketPath: ""
|
property string socketPath: ""
|
||||||
readonly property bool socketFound: socketPath.length > 0
|
readonly property bool socketFound: socketPath.length > 0
|
||||||
property bool connected: false
|
property bool connected: false
|
||||||
property bool binaryExists: false
|
readonly property bool binaryExists: launchKind.length > 0
|
||||||
property bool binaryChecked: false
|
property bool binaryChecked: false
|
||||||
|
// "dcal" for a host install, "flatpak" for a sandboxed one, "" for neither.
|
||||||
|
property string launchKind: ""
|
||||||
|
|
||||||
property var calendars: []
|
property var calendars: []
|
||||||
property var events: []
|
property var events: []
|
||||||
@@ -55,18 +57,20 @@ Item {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: binaryCheck
|
id: binaryCheck
|
||||||
command: ["sh", "-c", "command -v dcal"]
|
command: ["sh", "-c", "if command -v dcal >/dev/null 2>&1; then echo dcal; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankcalendar >/dev/null 2>&1; then echo flatpak; fi"]
|
||||||
running: false
|
running: false
|
||||||
onExited: code => {
|
stdout: StdioCollector {
|
||||||
root.binaryExists = (code === 0);
|
onStreamFinished: {
|
||||||
root.binaryChecked = true;
|
root.launchKind = text.trim();
|
||||||
|
root.binaryChecked = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: discoverProcess
|
id: discoverProcess
|
||||||
running: false
|
running: false
|
||||||
command: ["sh", "-c", "s=\"${DANKCAL_SOCKET:-}\"; if [ -S \"$s\" ]; then echo \"$s\"; exit 0; fi; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/dankcal-*.sock /tmp/dankcal-*.sock; do [ -S \"$f\" ] || continue; p=$(basename \"$f\" .sock); p=${p#dankcal-}; if kill -0 \"$p\" 2>/dev/null; then echo \"$f\"; exit 0; fi; done"]
|
command: ["sh", "-c", "s=\"${DANKCAL_SOCKET:-}\"; if [ -S \"$s\" ]; then echo \"$s\"; exit 0; fi; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/app/com.danklinux.dankcalendar/dankcal-*.sock; do if [ -S \"$f\" ]; then echo \"$f\"; exit 0; fi; done; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/dankcal-*.sock /tmp/dankcal-*.sock; do [ -S \"$f\" ] || continue; p=$(basename \"$f\" .sock); p=${p#dankcal-}; if kill -0 \"$p\" 2>/dev/null; then echo \"$f\"; exit 0; fi; done"]
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
@@ -98,9 +102,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function launch() {
|
function launch() {
|
||||||
if (!binaryExists)
|
switch (launchKind) {
|
||||||
|
case "dcal":
|
||||||
|
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
|
||||||
|
break;
|
||||||
|
case "flatpak":
|
||||||
|
Quickshell.execDetached(["flatpak", "run", "com.danklinux.dankcalendar", "run", "-d", "--hidden"]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
|
}
|
||||||
if (enabled && !connected)
|
if (enabled && !connected)
|
||||||
discoverProcess.running = true;
|
discoverProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Singleton {
|
|||||||
return "wifi_1_bar";
|
return "wifi_1_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
property string userPreference: "auto"
|
readonly property string userPreference: SettingsData.networkPreference
|
||||||
property bool isConnecting: false
|
property bool isConnecting: false
|
||||||
property string connectingSSID: ""
|
property string connectingSSID: ""
|
||||||
property string connectionError: ""
|
property string connectionError: ""
|
||||||
@@ -210,7 +210,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
root.userPreference = SettingsData.networkPreference;
|
|
||||||
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
checkDMSCapabilities();
|
checkDMSCapabilities();
|
||||||
@@ -732,7 +731,6 @@ Singleton {
|
|||||||
function setNetworkPreference(preference) {
|
function setNetworkPreference(preference) {
|
||||||
if (!networkAvailable)
|
if (!networkAvailable)
|
||||||
return;
|
return;
|
||||||
userPreference = preference;
|
|
||||||
changingPreference = true;
|
changingPreference = true;
|
||||||
targetPreference = preference;
|
targetPreference = preference;
|
||||||
SettingsData.set("networkPreference", preference);
|
SettingsData.set("networkPreference", preference);
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ Singleton {
|
|||||||
const data = response.result.data;
|
const data = response.result.data;
|
||||||
|
|
||||||
if (service === "server") {
|
if (service === "server") {
|
||||||
|
const prevCapabilities = capabilities;
|
||||||
apiVersion = data.apiVersion || 0;
|
apiVersion = data.apiVersion || 0;
|
||||||
cliVersion = data.cliVersion || "";
|
cliVersion = data.cliVersion || "";
|
||||||
capabilities = data.capabilities || [];
|
capabilities = data.capabilities || [];
|
||||||
@@ -346,6 +347,12 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
capabilitiesReceived();
|
capabilitiesReceived();
|
||||||
|
|
||||||
|
const capabilitiesChanged = prevCapabilities.length !== capabilities.length || capabilities.some(c => !prevCapabilities.includes(c));
|
||||||
|
if (prevCapabilities.length > 0 && capabilitiesChanged) {
|
||||||
|
log.info("Capabilities changed, resubscribing");
|
||||||
|
subscribe(activeSubscriptions);
|
||||||
|
}
|
||||||
} else if (service === "network") {
|
} else if (service === "network") {
|
||||||
networkStateUpdate(data);
|
networkStateUpdate(data);
|
||||||
} else if (service === "network.credentials") {
|
} else if (service === "network.credentials") {
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ Singleton {
|
|||||||
function reloadConfig(showToast, suppressWatch) {
|
function reloadConfig(showToast, suppressWatch) {
|
||||||
if (!CompositorService.isMango || !root.available)
|
if (!CompositorService.isMango || !root.available)
|
||||||
return;
|
return;
|
||||||
const shouldShowToast = showToast !== false;
|
const shouldShowToast = showToast !== false && SessionData.showConfigReloadToast;
|
||||||
const shouldSuppressWatch = suppressWatch !== false;
|
const shouldSuppressWatch = suppressWatch !== false;
|
||||||
if (shouldSuppressWatch)
|
if (shouldSuppressWatch)
|
||||||
suppressWatchedConfigReloads(1500);
|
suppressWatchedConfigReloads(1500);
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ Singleton {
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
fetchOutputs();
|
fetchOutputs();
|
||||||
Paths.mkdir(screenshotsDir);
|
Paths.mkdir(screenshotsDir);
|
||||||
|
generateNiriInputConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -105,6 +106,15 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CompositorService
|
||||||
|
function onIsNiriChanged() {
|
||||||
|
if (CompositorService.isNiri) {
|
||||||
|
generateNiriInputConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: validateProcess
|
id: validateProcess
|
||||||
command: ["niri", "validate"]
|
command: ["niri", "validate"]
|
||||||
@@ -175,6 +185,20 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: writeInputProcess
|
||||||
|
property string inputContent: ""
|
||||||
|
property string inputPath: ""
|
||||||
|
|
||||||
|
onExited: exitCode => {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
log.info("Generated input config at", inputPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.warn("Failed to write input config, exit code:", exitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: writeCursorProcess
|
id: writeCursorProcess
|
||||||
property string cursorContent: ""
|
property string cursorContent: ""
|
||||||
@@ -609,7 +633,7 @@ Singleton {
|
|||||||
configGenerationAction.schedule();
|
configGenerationAction.schedule();
|
||||||
configReloaded();
|
configReloaded();
|
||||||
|
|
||||||
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
|
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression && SessionData.showConfigReloadToast) {
|
||||||
ToastService.showInfo(I18n.tr("niri: config reloaded"), "", "", "niri-config");
|
ToastService.showInfo(I18n.tr("niri: config reloaded"), "", "", "niri-config");
|
||||||
} else if (suppressNextConfigToast) {
|
} else if (suppressNextConfigToast) {
|
||||||
suppressNextConfigToast = false;
|
suppressNextConfigToast = false;
|
||||||
@@ -1261,7 +1285,7 @@ window-rule {
|
|||||||
writeAlttabProcess.running = true;
|
writeAlttabProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const name of ["outputs", "binds", "cursor", "windowrules", "colors", "alttab", "layout"]) {
|
for (const name of ["outputs", "binds", "cursor", "windowrules", "colors", "alttab", "layout", "input"]) {
|
||||||
const path = niriDmsDir + "/" + name + ".kdl";
|
const path = niriDmsDir + "/" + name + ".kdl";
|
||||||
Proc.runCommand("niri-ensure-" + name, ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${path}" ] && touch "${path}" || true`], (output, exitCode) => {
|
Proc.runCommand("niri-ensure-" + name, ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${path}" ] && touch "${path}" || true`], (output, exitCode) => {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0)
|
||||||
@@ -1487,8 +1511,6 @@ window-rule {
|
|||||||
|
|
||||||
if (outputSettings.disabled) {
|
if (outputSettings.disabled) {
|
||||||
kdlContent += ` off\n`;
|
kdlContent += ` off\n`;
|
||||||
kdlContent += `}\n\n`;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.configured_mode) {
|
if (output.configured_mode) {
|
||||||
@@ -1641,6 +1663,129 @@ window-rule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateNiriInputConfig() {
|
||||||
|
if (!CompositorService.isNiri)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const defaultMouseLeftHanded = false;
|
||||||
|
const defaultMouseMiddleEmulation = false;
|
||||||
|
const defaultMouseNaturalScroll = false;
|
||||||
|
const defaultMouseProfile = "default";
|
||||||
|
const defaultMouseScrollFactor = 1.0;
|
||||||
|
const defaultMouseScrollMethod = "default";
|
||||||
|
const defaultMouseSpeed = 0.0;
|
||||||
|
|
||||||
|
const defaultTouchpadDisableOnExternalMouse = false;
|
||||||
|
const defaultTouchpadDisableWhileTyping = true;
|
||||||
|
const defaultTouchpadDragLock = false;
|
||||||
|
const defaultTouchpadMiddleEmulation = false;
|
||||||
|
const defaultTouchpadNaturalScroll = true;
|
||||||
|
const defaultTouchpadProfile = "default";
|
||||||
|
const defaultTouchpadScrollFactor = 1.0;
|
||||||
|
const defaultTouchpadScrollMethod = "default";
|
||||||
|
const defaultTouchpadSpeed = 0.0;
|
||||||
|
const defaultTouchpadTap = true;
|
||||||
|
const defaultTouchpadTapAndDrag = true;
|
||||||
|
|
||||||
|
const mouseLeftHanded = typeof SettingsData !== "undefined" ? SettingsData.mouseLeftHanded : defaultMouseLeftHanded;
|
||||||
|
const mouseMiddleEmulation = typeof SettingsData !== "undefined" ? SettingsData.mouseMiddleEmulation : defaultMouseMiddleEmulation;
|
||||||
|
const mouseNaturalScroll = typeof SettingsData !== "undefined" ? SettingsData.mouseNaturalScroll : defaultMouseNaturalScroll;
|
||||||
|
const mouseProfile = typeof SettingsData !== "undefined" ? SettingsData.mouseAccelProfile : defaultMouseProfile;
|
||||||
|
const mouseScrollFactor = typeof SettingsData !== "undefined" ? SettingsData.mouseScrollFactor : defaultMouseScrollFactor;
|
||||||
|
const mouseScrollMethod = typeof SettingsData !== "undefined" ? SettingsData.mouseScrollMethod : defaultMouseScrollMethod;
|
||||||
|
const mouseSpeed = typeof SettingsData !== "undefined" ? SettingsData.mouseAccelSpeed : defaultMouseSpeed;
|
||||||
|
|
||||||
|
const touchpadDisableOnExternalMouse = typeof SettingsData !== "undefined" ? SettingsData.touchpadDisableOnExternalMouse : defaultTouchpadDisableOnExternalMouse;
|
||||||
|
const touchpadDisableWhileTyping = typeof SettingsData !== "undefined" ? SettingsData.touchpadDisableWhileTyping : defaultTouchpadDisableWhileTyping;
|
||||||
|
const touchpadDragLock = typeof SettingsData !== "undefined" ? SettingsData.touchpadDragLock : defaultTouchpadDragLock;
|
||||||
|
const touchpadMiddleEmulation = typeof SettingsData !== "undefined" ? SettingsData.touchpadMiddleEmulation : defaultTouchpadMiddleEmulation;
|
||||||
|
const touchpadNaturalScroll = typeof SettingsData !== "undefined" ? SettingsData.touchpadNaturalScroll : defaultTouchpadNaturalScroll;
|
||||||
|
const touchpadProfile = typeof SettingsData !== "undefined" ? SettingsData.touchpadAccelProfile : defaultTouchpadProfile;
|
||||||
|
const touchpadScrollFactor = typeof SettingsData !== "undefined" ? SettingsData.touchpadScrollFactor : defaultTouchpadScrollFactor;
|
||||||
|
const touchpadScrollMethod = typeof SettingsData !== "undefined" ? SettingsData.touchpadScrollMethod : defaultTouchpadScrollMethod;
|
||||||
|
const touchpadSpeed = typeof SettingsData !== "undefined" ? SettingsData.touchpadAccelSpeed : defaultTouchpadSpeed;
|
||||||
|
const touchpadTap = typeof SettingsData !== "undefined" ? SettingsData.touchpadTapToClick : defaultTouchpadTap;
|
||||||
|
const touchpadTapAndDrag = typeof SettingsData !== "undefined" ? SettingsData.touchpadTapAndDrag : defaultTouchpadTapAndDrag;
|
||||||
|
|
||||||
|
const dmsWarning = `// ! DO NOT EDIT !
|
||||||
|
// ! AUTO-GENERATED BY DMS !
|
||||||
|
// ! CHANGES WILL BE OVERWRITTEN !
|
||||||
|
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
let inputContent = dmsWarning + "input {\n";
|
||||||
|
|
||||||
|
// Mouse block
|
||||||
|
inputContent += " mouse {\n";
|
||||||
|
inputContent += ` accel-speed ${mouseSpeed.toFixed(1)}\n`;
|
||||||
|
if (mouseProfile !== "default") {
|
||||||
|
inputContent += ` accel-profile "${mouseProfile}"\n`;
|
||||||
|
}
|
||||||
|
if (mouseNaturalScroll) {
|
||||||
|
inputContent += " natural-scroll\n";
|
||||||
|
}
|
||||||
|
if (mouseLeftHanded) {
|
||||||
|
inputContent += " left-handed\n";
|
||||||
|
}
|
||||||
|
if (mouseMiddleEmulation) {
|
||||||
|
inputContent += " middle-emulation\n";
|
||||||
|
}
|
||||||
|
if (mouseScrollFactor !== 1.0) {
|
||||||
|
inputContent += ` scroll-factor ${mouseScrollFactor.toFixed(1)}\n`;
|
||||||
|
}
|
||||||
|
if (mouseScrollMethod !== "default") {
|
||||||
|
inputContent += ` scroll-method "${mouseScrollMethod}"\n`;
|
||||||
|
}
|
||||||
|
inputContent += " }\n";
|
||||||
|
|
||||||
|
// Touchpad block
|
||||||
|
inputContent += " touchpad {\n";
|
||||||
|
if (touchpadTap) {
|
||||||
|
inputContent += " tap\n";
|
||||||
|
}
|
||||||
|
inputContent += ` accel-speed ${touchpadSpeed.toFixed(1)}\n`;
|
||||||
|
if (touchpadProfile !== "default") {
|
||||||
|
inputContent += ` accel-profile "${touchpadProfile}"\n`;
|
||||||
|
}
|
||||||
|
if (touchpadNaturalScroll) {
|
||||||
|
inputContent += " natural-scroll\n";
|
||||||
|
}
|
||||||
|
if (touchpadDisableOnExternalMouse) {
|
||||||
|
inputContent += " disabled-on-external-mouse\n";
|
||||||
|
}
|
||||||
|
if (touchpadDisableWhileTyping) {
|
||||||
|
inputContent += " dwt\n";
|
||||||
|
}
|
||||||
|
if (touchpadMiddleEmulation) {
|
||||||
|
inputContent += " middle-emulation\n";
|
||||||
|
}
|
||||||
|
if (touchpadScrollFactor !== 1.0) {
|
||||||
|
inputContent += ` scroll-factor ${touchpadScrollFactor.toFixed(1)}\n`;
|
||||||
|
}
|
||||||
|
if (touchpadScrollMethod !== "default") {
|
||||||
|
inputContent += ` scroll-method "${touchpadScrollMethod}"\n`;
|
||||||
|
}
|
||||||
|
if (!touchpadTapAndDrag) {
|
||||||
|
inputContent += " drag false\n";
|
||||||
|
}
|
||||||
|
if (touchpadDragLock) {
|
||||||
|
inputContent += " drag-lock\n";
|
||||||
|
}
|
||||||
|
inputContent += " }\n";
|
||||||
|
|
||||||
|
inputContent += "}\n";
|
||||||
|
|
||||||
|
const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
|
||||||
|
const niriDmsDir = configDir + "/niri/dms";
|
||||||
|
const inputPath = niriDmsDir + "/input.kdl";
|
||||||
|
|
||||||
|
writeInputProcess.inputContent = inputContent;
|
||||||
|
writeInputProcess.inputPath = inputPath;
|
||||||
|
writeInputProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${inputPath}" << 'EOF'\n${inputContent}\nEOF`];
|
||||||
|
writeInputProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function screenshot(): string {
|
function screenshot(): string {
|
||||||
if (!CompositorService.isNiri) {
|
if (!CompositorService.isNiri) {
|
||||||
|
|||||||
@@ -777,7 +777,7 @@ Singleton {
|
|||||||
return 5000;
|
return 5000;
|
||||||
// expireTimeout is in milliseconds; -1 defers to our settings.
|
// expireTimeout is in milliseconds; -1 defers to our settings.
|
||||||
const appTimeout = wrapper.notification.expireTimeout;
|
const appTimeout = wrapper.notification.expireTimeout;
|
||||||
if (appTimeout >= 0)
|
if (appTimeout >= 0 && !SettingsData.notificationIgnoreAppTimeout)
|
||||||
return Math.round(appTimeout);
|
return Math.round(appTimeout);
|
||||||
switch (wrapper.urgency) {
|
switch (wrapper.urgency) {
|
||||||
case NotificationUrgency.Low:
|
case NotificationUrgency.Low:
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeSettings() {
|
function closeSettings() {
|
||||||
settingsModal?.close();
|
settingsModal?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSettings() {
|
function toggleSettings() {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Singleton {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: colorSchemeSettleTimer
|
id: colorSchemeSettleTimer
|
||||||
interval: 1000
|
interval: 750
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!root.canSyncColorScheme())
|
if (!root.canSyncColorScheme())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* BEGIN DMS OVERRIDE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GTK Colors
|
* GTK Colors
|
||||||
* Generated with Matugen
|
* Generated with Matugen
|
||||||
@@ -46,3 +48,5 @@ a color between surface and surface container so I think just giving this surfac
|
|||||||
@define-color theme_unfocused_base_color @window_bg_color;
|
@define-color theme_unfocused_base_color @window_bg_color;
|
||||||
@define-color theme_unfocused_selected_bg_color @accent_bg_color;
|
@define-color theme_unfocused_selected_bg_color @accent_bg_color;
|
||||||
@define-color theme_unfocused_selected_fg_color @accent_fg_color;
|
@define-color theme_unfocused_selected_fg_color @accent_fg_color;
|
||||||
|
|
||||||
|
/* END DMS OVERRIDE */
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* BEGIN DMS OVERRIDE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GTK Colors
|
* GTK Colors
|
||||||
* Generated with Matugen
|
* Generated with Matugen
|
||||||
@@ -7820,3 +7822,5 @@ window.background:not(.solid-csd):not(.csd) > notebook > stack:not(:only-child)
|
|||||||
@define-color sidebar_fg_color @window_fg_color;
|
@define-color sidebar_fg_color @window_fg_color;
|
||||||
@define-color sidebar_border_color @window_bg_color;
|
@define-color sidebar_border_color @window_bg_color;
|
||||||
@define-color sidebar_backdrop_color @window_bg_color;
|
@define-color sidebar_backdrop_color @window_bg_color;
|
||||||
|
|
||||||
|
/* END DMS OVERRIDE */
|
||||||
|
|||||||
+299
-82
@@ -1,114 +1,331 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
CONFIG_DIR="$1"
|
CONFIG_DIR="$1"
|
||||||
SHELL_DIR="$3"
|
# apply: setup overrides and config dirs
|
||||||
|
# patch: refresh overrides in adw-gtk3
|
||||||
|
# remove: remove all overrides
|
||||||
|
# assets: relink adw-gtk3 assets if DMS already manages gtk.css
|
||||||
|
MODE="${2:-apply}"
|
||||||
|
IS_LIGHT="${3:-light}"
|
||||||
|
SHELL_DIR="$4"
|
||||||
|
|
||||||
if [ -z "$CONFIG_DIR" ]; then
|
if [ -z "$CONFIG_DIR" ]; then
|
||||||
echo "Usage: $0 <config_dir> [is_light] [shell_dir]" >&2
|
echo "Usage: $0 <config_dir> [apply|patch|remove|assets] [is_light] [shell_dir]" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
get_adw_gtk3_dir() {
|
||||||
|
local variant="$1"
|
||||||
|
local name=""
|
||||||
|
[ "$variant" == "dark" ] && name="-$variant"
|
||||||
|
|
||||||
|
local candidates=(
|
||||||
|
"$HOME/.local/share/themes/adw-gtk3${name}/gtk-3.0"
|
||||||
|
"$HOME/.themes/adw-gtk3${name}/gtk-3.0"
|
||||||
|
"/usr/share/themes/adw-gtk3${name}/gtk-3.0"
|
||||||
|
"/usr/local/share/themes/adw-gtk3${name}/gtk-3.0"
|
||||||
|
)
|
||||||
|
local target=""
|
||||||
|
for c in "${candidates[@]}"; do
|
||||||
|
if [ -d "$c" ]; then
|
||||||
|
target="$c"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "$target"
|
||||||
|
}
|
||||||
|
|
||||||
# The light template references adw-gtk3 image assets relative to gtk.css
|
# The light template references adw-gtk3 image assets relative to gtk.css
|
||||||
# (check/radio glyphs, slider knobs); without them checked boxes render as
|
# (check/radio glyphs, slider knobs); without them checked boxes render as
|
||||||
# solid blocks.
|
# solid blocks.
|
||||||
link_gtk3_assets() {
|
link_gtk3_assets() {
|
||||||
local gtk3_dir="$1"
|
local gtk3_dir="$1"
|
||||||
local assets_link="$gtk3_dir/assets"
|
local assets_link="$gtk3_dir/assets"
|
||||||
|
|
||||||
if [ -e "$assets_link" ] && [ ! -L "$assets_link" ]; then
|
if [ -e "$assets_link" ] && [ ! -L "$assets_link" ]; then
|
||||||
echo "Leaving user-managed $assets_link in place"
|
echo "Leaving user-managed $assets_link in place"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local candidates=(
|
local candidates=(
|
||||||
"$HOME/.local/share/themes/adw-gtk3/gtk-3.0/assets"
|
"$HOME/.local/share/themes/adw-gtk3/gtk-3.0/assets"
|
||||||
"$HOME/.themes/adw-gtk3/gtk-3.0/assets"
|
"$HOME/.themes/adw-gtk3/gtk-3.0/assets"
|
||||||
"/usr/share/themes/adw-gtk3/gtk-3.0/assets"
|
"/usr/share/themes/adw-gtk3/gtk-3.0/assets"
|
||||||
"/usr/local/share/themes/adw-gtk3/gtk-3.0/assets"
|
"/usr/local/share/themes/adw-gtk3/gtk-3.0/assets"
|
||||||
)
|
)
|
||||||
local target=""
|
local target=""
|
||||||
for c in "${candidates[@]}"; do
|
for c in "${candidates[@]}"; do
|
||||||
if [ -d "$c" ]; then
|
if [ -d "$c" ]; then
|
||||||
target="$c"
|
target="$c"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ -z "$target" ] && [ -n "$SHELL_DIR" ] && [ -d "$SHELL_DIR/matugen/gtk3-assets" ]; then
|
if [ -z "$target" ] && [ -n "$SHELL_DIR" ] && [ -d "$SHELL_DIR/matugen/gtk3-assets" ]; then
|
||||||
target="$SHELL_DIR/matugen/gtk3-assets"
|
target="$SHELL_DIR/matugen/gtk3-assets"
|
||||||
fi
|
fi
|
||||||
if [ -z "$target" ]; then
|
if [ -z "$target" ]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ln -sfn "$target" "$assets_link"
|
ln -sfn "$target" "$assets_link"
|
||||||
echo "Linked GTK3 assets: $assets_link -> $target"
|
echo "Linked GTK3 assets: $assets_link -> $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
DANK_IMPORT='@import url("dank-colors.css");'
|
||||||
|
DANK_IMPORT_RE='^@import url.*dank-colors\.css.*);$'
|
||||||
|
|
||||||
|
# gtk.css is DMS-managed if it is our symlink or carries our import line.
|
||||||
|
# User-managed symlinks (e.g. home-manager) are never touched.
|
||||||
|
dms_managed_css() {
|
||||||
|
local gtk_css="$1"
|
||||||
|
if [ -L "$gtk_css" ]; then
|
||||||
|
case "$(readlink "$gtk_css")" in
|
||||||
|
*dank-colors.css*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
[ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"
|
||||||
|
}
|
||||||
|
|
||||||
|
inject_dank_import() {
|
||||||
|
local gtk_css="$1"
|
||||||
|
|
||||||
|
if [ -L "$gtk_css" ]; then
|
||||||
|
if ! dms_managed_css "$gtk_css"; then
|
||||||
|
echo "Warning: '$gtk_css' is a user-managed symlink; leaving it untouched" >&2
|
||||||
|
echo "Import dank-colors.css from your own stylesheet to use DMS colors" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rm "$gtk_css"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"; then
|
||||||
|
echo "Dank import already present in '$gtk_css'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$gtk_css" ] && [ -s "$gtk_css" ]; then
|
||||||
|
sed -i "1i\\$DANK_IMPORT" "$gtk_css"
|
||||||
|
else
|
||||||
|
echo "$DANK_IMPORT" >"$gtk_css"
|
||||||
|
fi
|
||||||
|
echo "Added dank-colors import to '$gtk_css'"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_dank_import() {
|
||||||
|
local gtk_css="$1"
|
||||||
|
|
||||||
|
if [ -L "$gtk_css" ]; then
|
||||||
|
if dms_managed_css "$gtk_css"; then
|
||||||
|
rm "$gtk_css"
|
||||||
|
echo "Removed DMS-managed symlink '$gtk_css'"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"; then
|
||||||
|
sed -i "/$DANK_IMPORT_RE/d" "$gtk_css"
|
||||||
|
echo "Removed dank-colors import from '$gtk_css'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_gtk3_patch() {
|
||||||
|
local theme_dir="$1"
|
||||||
|
local css_variant="$2"
|
||||||
|
[ "$css_variant" != "-dark" ] && css_variant=""
|
||||||
|
sed -i '/\/\* BEGIN DMS OVERRIDE \*\//,/\/\* END DMS OVERRIDE \*\//d' "${theme_dir}/gtk${css_variant}.css"
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_gtk3_colors() {
|
||||||
|
local config_dir="$1"
|
||||||
|
|
||||||
|
local gtk3_dir="$config_dir/gtk-3.0"
|
||||||
|
|
||||||
|
# remove global override
|
||||||
|
remove_dank_import "$gtk3_dir/gtk.css"
|
||||||
|
if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then
|
||||||
|
echo "Nothing to remove at '${gtk3_dir}'"
|
||||||
|
else
|
||||||
|
if rm "${gtk3_dir}/dank-colors.css"; then
|
||||||
|
echo "Removed GTK3 override from '${gtk3_dir}'"
|
||||||
|
else
|
||||||
|
echo "Failed to remove GTK3 override from '${gtk3_dir}'"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# remove adw-gtk3 inclusions
|
||||||
|
for variant in light dark; do
|
||||||
|
local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant")
|
||||||
|
|
||||||
|
if [ -z "$adw_gtk3_dir" ] || [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
|
||||||
|
echo "No user version of adw-gtk3 ${variant} found, nothing to unpatch"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
for css_variant in light dark; do
|
||||||
|
if remove_gtk3_patch "$adw_gtk3_dir" "$css_variant"; then
|
||||||
|
echo "Removed GTK colors patch from '${adw_gtk3_dir}' in '$css_variant' stylesheet"
|
||||||
|
else
|
||||||
|
echo "Failed to remove GTK colors patch from '${adw_gtk3_dir}' in '$css_variant' stylesheet" >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
do_patch() {
|
||||||
|
local theme_dir="$1"
|
||||||
|
local variant="$2"
|
||||||
|
if [ -z "$theme_dir" ] || [[ "$theme_dir" =~ ^/usr ]]; then
|
||||||
|
echo "Skipping '$variant' patch: no user copy of adw-gtk3 for this variant"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local css_variant=""
|
||||||
|
[ "$variant" = "dark" ] && css_variant="-${variant}"
|
||||||
|
if {
|
||||||
|
remove_gtk3_patch "$theme_dir" "$css_variant"
|
||||||
|
cat "${gtk3_dir}/dank-colors.css" >>"${theme_dir}/gtk${css_variant}.css"
|
||||||
|
}; then
|
||||||
|
echo "Successfully patched '$theme_dir/gtk${css_variant}.css' with GTK '$variant' colors"
|
||||||
|
else
|
||||||
|
echo "Error: failed to patch '$theme_dir/gtk${css_variant}.css' with GTK '$variant' colors" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_gtk3_colors() {
|
||||||
|
local config_dir="$1"
|
||||||
|
local is_light="$2"
|
||||||
|
|
||||||
|
# Include generated colors for current variant
|
||||||
|
local gtk3_dir="$config_dir/gtk-3.0"
|
||||||
|
local variant="light"
|
||||||
|
[ "$is_light" = "false" ] && variant="dark"
|
||||||
|
local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant")
|
||||||
|
|
||||||
|
if [ -z "$adw_gtk3_dir" ] || [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
|
||||||
|
echo "No user version of adw-gtk3 ${variant} was found, skipping patch"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then
|
||||||
|
echo "Error: GTK3 dank-colors.css not found at '${gtk3_dir}'" >&2
|
||||||
|
echo "Run matugen first to generate theme files" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NOTE : for adw-gtk3-dark gtk.css and gtk-dark.css are the same file
|
||||||
|
if [ "$variant" = "dark" ]; then
|
||||||
|
do_patch "$adw_gtk3_dir" "dark"
|
||||||
|
do_patch "$adw_gtk3_dir" "light"
|
||||||
|
do_patch "$(get_adw_gtk3_dir "light")" "dark"
|
||||||
|
else
|
||||||
|
do_patch "$adw_gtk3_dir" "light"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_gtk3_colors() {
|
apply_gtk3_colors() {
|
||||||
local config_dir="$1"
|
local config_dir="$1"
|
||||||
|
|
||||||
local gtk3_dir="$config_dir/gtk-3.0"
|
local gtk3_dir="$config_dir/gtk-3.0"
|
||||||
local dank_colors="$gtk3_dir/dank-colors.css"
|
local gtk3_override="$gtk3_dir/gtk.css"
|
||||||
local gtk_css="$gtk3_dir/gtk.css"
|
# If no adw-gtk3 or only system wide, use global override
|
||||||
|
local adw_gtk3 && adw_gtk3="$(get_adw_gtk3_dir)"
|
||||||
|
if [[ "$adw_gtk3" =~ ^/usr ]] || [[ -z "$adw_gtk3" ]]; then
|
||||||
|
echo "Warning: No user version of adw-gtk3 found" >&2
|
||||||
|
echo "Falling back on global css override" >&2
|
||||||
|
local dank_colors="$gtk3_dir/dank-colors.css"
|
||||||
|
|
||||||
if [ ! -f "$dank_colors" ]; then
|
if [ ! -f "$dank_colors" ]; then
|
||||||
echo "Error: dank-colors.css not found at $dank_colors" >&2
|
echo "Error: dank-colors.css not found at $dank_colors" >&2
|
||||||
echo "Run matugen first to generate theme files" >&2
|
echo "Run matugen first to generate theme files" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -L "$gtk_css" ]; then
|
inject_dank_import "$gtk3_override" || exit 1
|
||||||
rm "$gtk_css"
|
link_gtk3_assets "$gtk3_dir"
|
||||||
elif [ -f "$gtk_css" ]; then
|
|
||||||
mv "$gtk_css" "$gtk_css.backup.$(date +%s)"
|
|
||||||
echo "Backed up existing gtk.css"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ln -s "dank-colors.css" "$gtk_css"
|
return
|
||||||
echo "Created symlink: $gtk_css -> dank-colors.css"
|
fi
|
||||||
|
|
||||||
link_gtk3_assets "$gtk3_dir"
|
# adw-gtk3 carries the colors; ensure there's no DMS global override
|
||||||
|
remove_dank_import "$gtk3_override"
|
||||||
|
|
||||||
|
# Backup pristine adw-gtk3 stylesheets once
|
||||||
|
for variant in light dark; do
|
||||||
|
local adw_gtk3_dir && adw_gtk3_dir="$(get_adw_gtk3_dir "$variant")"
|
||||||
|
for css in gtk.css gtk-dark.css; do
|
||||||
|
if [ -f "$adw_gtk3_dir/$css" ] && [ ! -f "$adw_gtk3_dir/$css.dms-backup" ]; then
|
||||||
|
cp "$adw_gtk3_dir/$css" "$adw_gtk3_dir/$css.dms-backup"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_gtk4_colors() {
|
||||||
|
local config_dir="$1"
|
||||||
|
|
||||||
|
local gtk4_dir="$config_dir/gtk-4.0"
|
||||||
|
local dank_colors="$gtk4_dir/dank-colors.css"
|
||||||
|
local gtk_css="$gtk4_dir/gtk.css"
|
||||||
|
|
||||||
|
remove_dank_import "$gtk_css"
|
||||||
|
|
||||||
|
if [ ! -f "$dank_colors" ]; then
|
||||||
|
echo "Nothing to remove in '$gtk4_dir'"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm "$dank_colors"
|
||||||
|
echo "Removed 'dank-colors.css' from '$gtk4_dir'"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_gtk4_colors() {
|
apply_gtk4_colors() {
|
||||||
local config_dir="$1"
|
local config_dir="$1"
|
||||||
|
|
||||||
local gtk4_dir="$config_dir/gtk-4.0"
|
local gtk4_dir="$config_dir/gtk-4.0"
|
||||||
local dank_colors="$gtk4_dir/dank-colors.css"
|
local dank_colors="$gtk4_dir/dank-colors.css"
|
||||||
local gtk_css="$gtk4_dir/gtk.css"
|
local gtk_css="$gtk4_dir/gtk.css"
|
||||||
local gtk4_import="@import url(\"dank-colors.css\");"
|
|
||||||
|
|
||||||
if [ ! -f "$dank_colors" ]; then
|
if [ ! -f "$dank_colors" ]; then
|
||||||
echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2
|
echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2
|
||||||
echo "Run matugen first to generate theme files" >&2
|
echo "Run matugen first to generate theme files" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
|
inject_dank_import "$gtk_css" || exit 1
|
||||||
echo "GTK4 import already exists"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$gtk_css" ] && [ -s "$gtk_css" ]; then
|
|
||||||
sed -i "1i\\$gtk4_import" "$gtk_css"
|
|
||||||
else
|
|
||||||
echo "$gtk4_import" >"$gtk_css"
|
|
||||||
fi
|
|
||||||
echo "Updated GTK4 CSS import"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Repair pass for shells whose gtk.css was linked before asset handling
|
case "$MODE" in
|
||||||
# existed; only acts when DMS already manages gtk.css.
|
patch)
|
||||||
if [ "$4" = "assets-only" ]; then
|
# Only refresh themes the user opted into via 'apply'
|
||||||
if [ -L "$CONFIG_DIR/gtk-3.0/gtk.css" ]; then
|
if ! dms_managed_css "$CONFIG_DIR/gtk-4.0/gtk.css"; then
|
||||||
link_gtk3_assets "$CONFIG_DIR/gtk-3.0"
|
echo "DMS GTK theming is not applied, skipping patch"
|
||||||
fi
|
exit 2
|
||||||
exit 0
|
fi
|
||||||
fi
|
patch_gtk3_colors "$CONFIG_DIR" "$IS_LIGHT"
|
||||||
|
echo "GTK3 colors patched successfully"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
remove_gtk3_colors "$CONFIG_DIR"
|
||||||
|
remove_gtk4_colors "$CONFIG_DIR"
|
||||||
|
;;
|
||||||
|
apply)
|
||||||
|
mkdir -p "$CONFIG_DIR/gtk-3.0" "$CONFIG_DIR/gtk-4.0"
|
||||||
|
|
||||||
mkdir -p "$CONFIG_DIR/gtk-3.0" "$CONFIG_DIR/gtk-4.0"
|
apply_gtk3_colors "$CONFIG_DIR"
|
||||||
|
apply_gtk4_colors "$CONFIG_DIR"
|
||||||
|
|
||||||
apply_gtk3_colors "$CONFIG_DIR"
|
echo "GTK colors applied successfully"
|
||||||
apply_gtk4_colors "$CONFIG_DIR"
|
;;
|
||||||
|
assets)
|
||||||
echo "GTK colors applied successfully"
|
# Repair pass at startup; only acts when DMS already manages gtk.css
|
||||||
|
if dms_managed_css "$CONFIG_DIR/gtk-3.0/gtk.css"; then
|
||||||
|
link_gtk3_assets "$CONFIG_DIR/gtk-3.0"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 <config_dir> [apply|patch|remove|assets] [is_light] [shell_dir]" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user