mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e0a8ae0fb | |||
| 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",
|
||||
"outputs.kdl",
|
||||
"binds.kdl",
|
||||
"input.kdl",
|
||||
"cursor.conf",
|
||||
"layout.conf",
|
||||
"outputs.conf",
|
||||
|
||||
@@ -143,6 +143,10 @@ var dmsConfigSpecs = map[string]dmsConfigSpec{
|
||||
niriFile: "alttab.kdl",
|
||||
niriContent: func(_ string) string { return config.NiriAlttabConfig },
|
||||
},
|
||||
"input": {
|
||||
niriFile: "input.kdl",
|
||||
niriContent: func(_ string) string { return config.NiriInputConfig },
|
||||
},
|
||||
"outputs": {
|
||||
niriFile: "outputs.kdl",
|
||||
hyprFile: "outputs.lua",
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ 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/aymanbagabas/go-osc52/v2 v2.0.1 // 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/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260721162324-38d48943054d h1:EZN2x2uAX975Uvw4MByqWaaVTp2fOJrNTl4V4EQbSn8=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260721162324-38d48943054d/go.mod h1:7p7cfydr4WM1G6eOPFlANXF3IV5du3FoA4CbDPprHAo=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05 h1:Ij/yzOT8y2HL7V5Rkec1GxJV0rUvhKqfAzyGxVRTk1o=
|
||||
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/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
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},
|
||||
{"alttab.kdl", NiriAlttabConfig},
|
||||
{"binds.kdl", strings.ReplaceAll(NiriBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)},
|
||||
{"input.kdl", NiriInputConfig},
|
||||
{"outputs.kdl", ""},
|
||||
{"cursor.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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// off
|
||||
// natural-scroll
|
||||
@@ -77,6 +52,7 @@ input {
|
||||
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
|
||||
// focus-follows-mouse max-scroll-amount="0%"
|
||||
}
|
||||
|
||||
// You can configure outputs by their name, which you can find
|
||||
// by running `niri msg outputs` while inside a niri instance.
|
||||
// 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/outputs.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
|
||||
var NiriBindsConfig string
|
||||
|
||||
//go:embed embedded/niri-input.kdl
|
||||
var NiriInputConfig string
|
||||
|
||||
@@ -386,8 +386,8 @@ func buildOnce(opts *Options) (bool, error) {
|
||||
default:
|
||||
syncAccentColor(primaryDark)
|
||||
}
|
||||
refreshGTK(opts.Mode)
|
||||
refreshGTK4()
|
||||
refreshGTKTheme(opts.Mode)
|
||||
refreshGTKColorScheme()
|
||||
}
|
||||
|
||||
if isDMSKDEColorSchemeActive(opts.ConfigDir) {
|
||||
@@ -911,7 +911,7 @@ func generateDank16Variants(primaryDark, primaryLight, surface string, mode Colo
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
@@ -968,7 +968,7 @@ func applyKDEColorScheme(mode ColorMode) {
|
||||
}
|
||||
}
|
||||
|
||||
func refreshGTK(mode ColorMode) {
|
||||
func refreshGTKTheme(mode ColorMode) {
|
||||
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
|
||||
log.Warnf("Failed to reset gtk-theme: %v", err)
|
||||
}
|
||||
@@ -993,8 +993,8 @@ func expectColorSchemeEcho(scheme string) {
|
||||
// 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
|
||||
// mid-repaint and latch the wrong mode, so this is opt-in.
|
||||
func refreshGTK4() {
|
||||
if os.Getenv("DMS_ENABLE_GTK4_REFRESH") != "1" {
|
||||
func refreshGTKColorScheme() {
|
||||
if os.Getenv("DMS_ENABLE_GTK_REFRESH") != "1" {
|
||||
return
|
||||
}
|
||||
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
|
||||
@@ -1012,13 +1012,13 @@ func refreshGTK4() {
|
||||
|
||||
expectColorSchemeEcho(toggle)
|
||||
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
|
||||
}
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
expectColorSchemeEcho(current)
|
||||
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)
|
||||
m.promptBroker = broker
|
||||
|
||||
adapter, err := m.findAdapter()
|
||||
adapter, err := findAdapter(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
m.adapterPath = adapter
|
||||
|
||||
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
||||
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
|
||||
func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
|
||||
obj := conn.Object(bluezService, dbus.ObjectPath("/"))
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
|
||||
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 {
|
||||
@@ -89,7 +89,7 @@ func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no adapter found")
|
||||
return "", ErrNoAdapter
|
||||
}
|
||||
|
||||
func (m *Manager) initialize() error {
|
||||
@@ -487,6 +487,11 @@ func (m *Manager) StopDiscovery() 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)
|
||||
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 cupsManager == nil {
|
||||
mgr, err := ensureCupsManager()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
||||
return
|
||||
}
|
||||
cups.HandleRequest(conn, req, cupsManager)
|
||||
cups.HandleRequest(conn, req, mgr)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
|
||||
@@ -80,8 +79,8 @@ var geoClientInstance geolocation.Client
|
||||
const dbusClientID = "dms-dbus-client"
|
||||
|
||||
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
||||
var cupsSubscribers syncmap.Map[string, bool]
|
||||
var cupsSubscriberCount atomic.Int32
|
||||
var cupsMu sync.Mutex
|
||||
var cupsSubscriberCount int
|
||||
|
||||
var appPaths = paths.New("danklinux")
|
||||
|
||||
@@ -174,17 +173,55 @@ func InitializeAppPickerManager() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitializeCupsManager() error {
|
||||
func initializeCupsManagerLocked() (bool, error) {
|
||||
if cupsManager != nil {
|
||||
return false, nil
|
||||
}
|
||||
manager, err := cups.NewManager()
|
||||
if err != nil {
|
||||
log.Warnf("Failed to initialize cups manager: %v", err)
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
cupsManager = manager
|
||||
|
||||
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 {
|
||||
@@ -379,7 +416,7 @@ func getCapabilities() Capabilities {
|
||||
caps = append(caps, "browser")
|
||||
}
|
||||
|
||||
if cupsManager != nil {
|
||||
if cupsAvailable() {
|
||||
caps = append(caps, "cups")
|
||||
}
|
||||
|
||||
@@ -449,7 +486,7 @@ func getServerInfo() ServerInfo {
|
||||
caps = append(caps, "browser")
|
||||
}
|
||||
|
||||
if cupsManager != nil {
|
||||
if cupsAvailable() {
|
||||
caps = append(caps, "cups")
|
||||
}
|
||||
|
||||
@@ -913,38 +950,31 @@ func handleSubscribe(conn *models.Conn, req models.Request) {
|
||||
}
|
||||
|
||||
if shouldSubscribe("cups") {
|
||||
cupsSubscribers.Store(clientID+"-cups", true)
|
||||
count := cupsSubscriberCount.Add(1)
|
||||
cupsMu.Lock()
|
||||
cupsSubscriberCount++
|
||||
created, err := initializeCupsManagerLocked()
|
||||
mgr := cupsManager
|
||||
cupsMu.Unlock()
|
||||
|
||||
if count == 1 {
|
||||
if err := InitializeCupsManager(); err != nil {
|
||||
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
||||
} else {
|
||||
notifyCapabilityChange()
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
||||
} else if created {
|
||||
notifyCapabilityChange()
|
||||
}
|
||||
|
||||
if cupsManager != nil {
|
||||
if mgr == nil {
|
||||
releaseCupsSubscriber()
|
||||
} else {
|
||||
wg.Add(1)
|
||||
cupsChan := cupsManager.Subscribe(clientID + "-cups")
|
||||
cupsChan := mgr.Subscribe(clientID + "-cups")
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
cupsManager.Unsubscribe(clientID + "-cups")
|
||||
cupsSubscribers.Delete(clientID + "-cups")
|
||||
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()
|
||||
}
|
||||
}
|
||||
mgr.Unsubscribe(clientID + "-cups")
|
||||
releaseCupsSubscriber()
|
||||
}()
|
||||
|
||||
initialState := cupsManager.GetState()
|
||||
initialState := mgr.GetState()
|
||||
select {
|
||||
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
|
||||
case <-stopChan:
|
||||
@@ -1254,9 +1284,12 @@ func cleanupManagers() {
|
||||
if appPickerManager != nil {
|
||||
appPickerManager.Close()
|
||||
}
|
||||
cupsMu.Lock()
|
||||
if cupsManager != nil {
|
||||
cupsManager.Close()
|
||||
cupsManager = nil
|
||||
}
|
||||
cupsMu.Unlock()
|
||||
if brightnessManager != nil {
|
||||
brightnessManager.Close()
|
||||
}
|
||||
@@ -1635,10 +1668,21 @@ func (s *Server) Serve(printDocs bool) error {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := InitializeBluezManager(); err != nil {
|
||||
for {
|
||||
err := InitializeBluezManager()
|
||||
if err == nil {
|
||||
notifyCapabilityChange()
|
||||
return
|
||||
}
|
||||
log.Warnf("Bluez manager unavailable: %v", err)
|
||||
} else {
|
||||
notifyCapabilityChange()
|
||||
if !errors.Is(err, bluez.ErrNoAdapter) {
|
||||
return
|
||||
}
|
||||
if err := bluez.WaitForAdapter(); err != nil {
|
||||
log.Warnf("Bluetooth adapter watch failed: %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("Bluetooth adapter appeared, initializing bluez manager")
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
"github.com/AvengeMedia/dankgo/boottimer"
|
||||
"github.com/godbus/dbus/v5"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
@@ -23,9 +24,6 @@ import (
|
||||
|
||||
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) {
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, err
|
||||
@@ -752,7 +750,8 @@ func (m *Manager) schedulerLoop() {
|
||||
m.post(func() { m.applyCurrentTemp("startup") })
|
||||
}
|
||||
|
||||
var timer *time.Timer
|
||||
timer := boottimer.New(24 * time.Hour)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
m.configMutex.RLock()
|
||||
enabled := m.config.Enabled
|
||||
@@ -761,31 +760,19 @@ func (m *Manager) schedulerLoop() {
|
||||
now := time.Now()
|
||||
m.recalcSchedule(now)
|
||||
|
||||
var waitDur time.Duration
|
||||
waitDur := 24 * time.Hour
|
||||
if enabled {
|
||||
deadline := m.getNextDeadline(now)
|
||||
waitDur = time.Until(deadline)
|
||||
switch {
|
||||
case waitDur < time.Second:
|
||||
if waitDur = time.Until(deadline); waitDur < time.Second {
|
||||
waitDur = time.Second
|
||||
case waitDur > maxScheduleWait:
|
||||
waitDur = maxScheduleWait
|
||||
}
|
||||
} else {
|
||||
waitDur = 24 * time.Hour
|
||||
}
|
||||
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
timer = time.NewTimer(waitDur)
|
||||
timer.Reset(waitDur)
|
||||
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
timer.Stop()
|
||||
return
|
||||
case <-m.updateTrigger:
|
||||
timer.Stop()
|
||||
m.scheduleMutex.Lock()
|
||||
m.schedule.calcDay = time.Time{}
|
||||
m.scheduleMutex.Unlock()
|
||||
@@ -1086,14 +1073,11 @@ func (m *Manager) handleResume() {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
out.lastTemp = 0
|
||||
return true
|
||||
})
|
||||
m.recalcSchedule(time.Now())
|
||||
m.applyCurrentTemp("resume")
|
||||
m.triggerUpdate()
|
||||
}
|
||||
|
||||
func (m *Manager) triggerUpdate() {
|
||||
|
||||
+1
-1
Submodule dank-qml-common updated: 1919595f0d...a172b39841
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1784647395,
|
||||
"narHash": "sha256-kcLSJC8XBzmWoSBc90Zn81qrNsqWMkwQ1m/zZixOw30=",
|
||||
"lastModified": 1784935106,
|
||||
"narHash": "sha256-aVgOBRynme6XNKYCZlf/oCjPDZFwddyUQPRRkEupC3c=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "1919595f0dde4f5bf5ac0baa1902ff6d070971d5",
|
||||
"rev": "a172b39841d8f42bac46ac133b76cade1fae91b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-G0KCUaNwjWfLcSnb68TMXjpB2xxwheXgrBkFEzcSZTc=";
|
||||
vendorHash = "sha256-ZvaOPC92ZFRPqSyLJa2TA9OUKQ3QnWCIMxrnYLGnC58=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
|
||||
@@ -224,6 +224,7 @@ Singleton {
|
||||
property string niriOverviewLastMode: "apps"
|
||||
property string settingsSidebarExpandedIds: ","
|
||||
property string settingsSidebarCollapsedIds: ","
|
||||
property bool showConfigReloadToast: true
|
||||
|
||||
Component.onCompleted: {
|
||||
loadSettings();
|
||||
|
||||
@@ -184,6 +184,24 @@ Singleton {
|
||||
property int mangoLayoutRadiusOverride: -1
|
||||
property int mangoLayoutBorderSize: -1
|
||||
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 bool showWeekNumber: false
|
||||
@@ -916,6 +934,7 @@ Singleton {
|
||||
property int notificationTimeoutLow: 5000
|
||||
property int notificationTimeoutNormal: 5000
|
||||
property int notificationTimeoutCritical: 0
|
||||
property bool notificationIgnoreAppTimeout: false
|
||||
property bool notificationCompactMode: false
|
||||
property bool notificationShowTimeoutBar: false
|
||||
property bool notificationDedupeEnabled: true
|
||||
@@ -1479,6 +1498,13 @@ Singleton {
|
||||
MangoService.generateLayoutConfig();
|
||||
}
|
||||
|
||||
function updateCompositorInput() {
|
||||
if (typeof CompositorService === "undefined")
|
||||
return;
|
||||
if (CompositorService.isNiri && typeof NiriService !== "undefined")
|
||||
NiriService.generateNiriInputConfig();
|
||||
}
|
||||
|
||||
function updateFrameCompositorLayout() {
|
||||
// Generate before begin() so compositor readiness is already pending at transitionRequested
|
||||
if (typeof CompositorService !== "undefined") {
|
||||
@@ -1707,6 +1733,7 @@ Singleton {
|
||||
"applyStoredTheme": applyStoredTheme,
|
||||
"regenSystemThemes": regenSystemThemes,
|
||||
"updateCompositorLayout": updateCompositorLayout,
|
||||
"updateCompositorInput": updateCompositorInput,
|
||||
"applyStoredIconTheme": applyStoredIconTheme,
|
||||
"updateBarConfigs": updateBarConfigs,
|
||||
"updateCompositorCursor": updateCompositorCursor,
|
||||
@@ -2735,6 +2762,35 @@ Singleton {
|
||||
return edges;
|
||||
}
|
||||
|
||||
function getOverlayBarEdgesForScreen(screen) {
|
||||
if (!screen)
|
||||
return [];
|
||||
var edges = [];
|
||||
for (var i = 0; i < barConfigs.length; i++) {
|
||||
var bc = barConfigs[i];
|
||||
if (!bc.enabled || !(bc.useOverlayLayer ?? false))
|
||||
continue;
|
||||
var prefs = bc.screenPreferences || ["all"];
|
||||
if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs))
|
||||
continue;
|
||||
switch (bc.position ?? 0) {
|
||||
case SettingsData.Position.Top:
|
||||
edges.push("top");
|
||||
break;
|
||||
case SettingsData.Position.Bottom:
|
||||
edges.push("bottom");
|
||||
break;
|
||||
case SettingsData.Position.Left:
|
||||
edges.push("left");
|
||||
break;
|
||||
case SettingsData.Position.Right:
|
||||
edges.push("right");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
function frameEdgeInsetForSide(screen, side) {
|
||||
if (!frameEnabled || !screen)
|
||||
return 0;
|
||||
|
||||
@@ -144,7 +144,7 @@ Singleton {
|
||||
Quickshell.execDetached(["mkdir", "-p", stateDir]);
|
||||
// shellDir may be an embedded-UI extraction, which is read-only and
|
||||
// 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, "", "", shellDir]);
|
||||
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
|
||||
matugenAvailable = (code === 0) && !envDisableMatugen;
|
||||
|
||||
@@ -1926,6 +1926,41 @@ Singleton {
|
||||
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) => {
|
||||
if (exitCode !== 0) {
|
||||
log.warn(`Failed to patch GTK3 colors: ${output}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyGtkColors() {
|
||||
if (!matugenAvailable) {
|
||||
if (typeof ToastService !== "undefined") {
|
||||
@@ -1935,7 +1970,7 @@ Singleton {
|
||||
}
|
||||
|
||||
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 (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
|
||||
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
|
||||
@@ -2131,8 +2166,13 @@ Singleton {
|
||||
root.matugenCompleted(currentMode, "error");
|
||||
}
|
||||
|
||||
if (!pendingThemeRequest)
|
||||
if (!pendingThemeRequest) {
|
||||
if (SettingsData.matugenTemplateGtk) {
|
||||
patchGtk3colors();
|
||||
refreshGtkTheme();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const req = pendingThemeRequest;
|
||||
pendingThemeRequest = null;
|
||||
|
||||
@@ -98,7 +98,8 @@ var SPEC = {
|
||||
niriOverviewLastMode: { def: "apps" },
|
||||
|
||||
settingsSidebarExpandedIds: { def: "," },
|
||||
settingsSidebarCollapsedIds: { def: "," }
|
||||
settingsSidebarCollapsedIds: { def: "," },
|
||||
showConfigReloadToast: { def: true }
|
||||
};
|
||||
|
||||
function getValidKeys() {
|
||||
|
||||
@@ -38,6 +38,24 @@ var SPEC = {
|
||||
mangoLayoutRadiusOverride: { def: -1, onChange: "updateCompositorLayout" },
|
||||
mangoLayoutBorderSize: { def: -1, onChange: "updateCompositorLayout" },
|
||||
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 },
|
||||
showWeekNumber: { def: false },
|
||||
@@ -477,6 +495,7 @@ var SPEC = {
|
||||
notificationTimeoutLow: { def: 5000 },
|
||||
notificationTimeoutNormal: { def: 5000 },
|
||||
notificationTimeoutCritical: { def: 0 },
|
||||
notificationIgnoreAppTimeout: { def: false },
|
||||
notificationCompactMode: { def: false },
|
||||
notificationShowTimeoutBar: { def: false },
|
||||
notificationDedupeEnabled: { def: true },
|
||||
|
||||
@@ -31,6 +31,8 @@ Item {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("DMSShell")
|
||||
readonly property var _sessionsServiceRef: SessionsService
|
||||
// Keep the optional integration service alive
|
||||
readonly property var _dankConnectServiceRef: DankConnectService
|
||||
|
||||
property var core: null
|
||||
|
||||
@@ -314,6 +316,17 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: BarWidgetService
|
||||
property: "dockContextMenu"
|
||||
value: dockContextMenuLoader.item
|
||||
}
|
||||
Binding {
|
||||
target: BarWidgetService
|
||||
property: "dockTrashContextMenu"
|
||||
value: dockTrashContextMenuLoader.item
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDockShowTrashChanged() {
|
||||
|
||||
@@ -718,5 +718,20 @@ FocusScope {
|
||||
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
|
||||
|
||||
function show() {
|
||||
if (visible && !backingWindowVisible) {
|
||||
visible = false;
|
||||
}
|
||||
visible = true;
|
||||
}
|
||||
|
||||
@@ -49,7 +52,11 @@ FloatingWindow {
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
visible = !visible;
|
||||
if (visible && backingWindowVisible) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
show();
|
||||
}
|
||||
|
||||
function setTabIndex(tabIndex: int) {
|
||||
@@ -61,13 +68,12 @@ FloatingWindow {
|
||||
|
||||
function showWithTab(tabIndex: int) {
|
||||
setTabIndex(tabIndex);
|
||||
visible = true;
|
||||
show();
|
||||
}
|
||||
|
||||
function showWithTabName(tabName: string) {
|
||||
var idx = sidebar.resolveTabIndex(tabName);
|
||||
setTabIndex(idx);
|
||||
visible = true;
|
||||
setTabIndex(sidebar.resolveTabIndex(tabName));
|
||||
show();
|
||||
}
|
||||
|
||||
function resolveTabIndex(tabName: string): int {
|
||||
|
||||
@@ -319,6 +319,13 @@ Rectangle {
|
||||
"icon": "headphones",
|
||||
"tabIndex": 29
|
||||
},
|
||||
{
|
||||
"id": "mouse_touchpad",
|
||||
"text": I18n.tr("Mouse & Touchpad"),
|
||||
"icon": "mouse",
|
||||
"tabIndex": 44,
|
||||
"niriOnly": true
|
||||
},
|
||||
{
|
||||
"id": "locale",
|
||||
"text": I18n.tr("Locale"),
|
||||
|
||||
@@ -13,6 +13,9 @@ Item {
|
||||
signal colorPickerRequested
|
||||
signal barReady(var barConfig)
|
||||
|
||||
Component.onCompleted: BarWidgetService.registerDankBarItem(root.barConfig?.id, root)
|
||||
Component.onDestruction: BarWidgetService.unregisterDankBarItem(root.barConfig?.id, root)
|
||||
|
||||
property alias barVariants: barVariants
|
||||
property var hyprlandOverviewLoader: null
|
||||
property bool systemTrayMenuOpen: false
|
||||
@@ -161,14 +164,18 @@ Item {
|
||||
id: barVariants
|
||||
model: {
|
||||
const prefs = root.barConfig?.screenPreferences || ["all"];
|
||||
if (prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
|
||||
return Quickshell.screens;
|
||||
const wantsAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
||||
let base;
|
||||
if (wantsAll) {
|
||||
base = Quickshell.screens;
|
||||
} else {
|
||||
base = Quickshell.screens.filter(screen => SettingsData.isScreenInPreferences(screen, prefs));
|
||||
if (base.length === 0 && root.barConfig?.showOnLastDisplay && Quickshell.screens.length === 1)
|
||||
base = Quickshell.screens;
|
||||
}
|
||||
const filtered = Quickshell.screens.filter(screen => SettingsData.isScreenInPreferences(screen, prefs));
|
||||
if (filtered.length === 0 && root.barConfig?.showOnLastDisplay && Quickshell.screens.length === 1) {
|
||||
return Quickshell.screens;
|
||||
}
|
||||
return filtered;
|
||||
// Connected frame mode renders the bar inside the frame surface; skip the standalone window there
|
||||
// unless this bar wants the overlay layer, which the frame surface cannot provide.
|
||||
return base.filter(screen => !CompositorService.frameHostsBarForConfig(screen, root.barConfig));
|
||||
}
|
||||
|
||||
delegate: DankBarWindow {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,37 +38,44 @@ Item {
|
||||
readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1)
|
||||
readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin
|
||||
readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw
|
||||
// Connected-frame Bar Inset Padding: absolute free-end inset (auto < 0 = frameThickness, 0 = edge-to-edge).
|
||||
// FrameExclusions already moves a free bar end inward by frameThickness so use frame inset to mangage the gap
|
||||
// Connected-frame Bar Inset Padding: absolute free-end gap the hosted bar spans full-width into
|
||||
// (auto < 0 = frameThickness so widgets align with the interior cutout, 0 = edge-to-edge). The extra
|
||||
// beyond frameThickness is what an adjacent bar end adds on top of its corner alignment.
|
||||
readonly property real _frameInsetResolved: SettingsData.frameBarInsetPadding < 0 ? SettingsData.frameThickness : SettingsData.frameBarInsetPadding
|
||||
readonly property real _frameInsetExtra: Math.max(0, _frameInsetResolved - SettingsData.frameThickness)
|
||||
|
||||
// Horizontal bars span the full width and own the corners; the perpendicular vertical bar
|
||||
// tucks in below/above. Where they meet, inset the corner widget so it centres in the
|
||||
// frameBarSize corner cell, aligning it with the vertical bar's widget column.
|
||||
readonly property real _widgetThicknessValue: _hasBarWindow ? barWindow.widgetThickness : 30
|
||||
readonly property real _cornerAlignInset: Math.max(_frameInsetExtra, (SettingsData.frameBarSize - _widgetThicknessValue) / 2)
|
||||
|
||||
readonly property real _leftMargin: {
|
||||
if (_barIsVertical)
|
||||
return _edgeBaseMargin;
|
||||
if (_usesFrameBarChrome)
|
||||
return hasAdjacentLeftBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
|
||||
return hasAdjacentLeftBarLive ? _cornerAlignInset : _frameInsetResolved;
|
||||
return Math.max(0, _barInsetPadding);
|
||||
}
|
||||
readonly property real _rightMargin: {
|
||||
if (_barIsVertical)
|
||||
return _edgeBaseMargin;
|
||||
if (_usesFrameBarChrome)
|
||||
return hasAdjacentRightBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
|
||||
return hasAdjacentRightBarLive ? _cornerAlignInset : _frameInsetResolved;
|
||||
return Math.max(0, _barInsetPadding);
|
||||
}
|
||||
readonly property real _topMargin: {
|
||||
if (!_barIsVertical)
|
||||
return 0;
|
||||
if (_usesFrameBarChrome)
|
||||
return hasAdjacentTopBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
|
||||
return hasAdjacentTopBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
|
||||
return Math.max(0, _barInsetPadding);
|
||||
}
|
||||
readonly property real _bottomMargin: {
|
||||
if (!_barIsVertical)
|
||||
return 0;
|
||||
if (_usesFrameBarChrome)
|
||||
return hasAdjacentBottomBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
|
||||
return hasAdjacentBottomBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
|
||||
return Math.max(0, _barInsetPadding);
|
||||
}
|
||||
|
||||
@@ -677,6 +684,7 @@ Item {
|
||||
LeftSection {
|
||||
id: hLeftSection
|
||||
objectName: "leftSection"
|
||||
edgeIsScreenEdge: !topBarContent.hasAdjacentLeftBarLive
|
||||
overrideAxisLayout: true
|
||||
forceVerticalLayout: false
|
||||
anchors {
|
||||
@@ -710,6 +718,7 @@ Item {
|
||||
RightSection {
|
||||
id: hRightSection
|
||||
objectName: "rightSection"
|
||||
edgeIsScreenEdge: !topBarContent.hasAdjacentRightBarLive
|
||||
overrideAxisLayout: true
|
||||
forceVerticalLayout: false
|
||||
anchors {
|
||||
@@ -782,6 +791,7 @@ Item {
|
||||
LeftSection {
|
||||
id: vLeftSection
|
||||
objectName: "leftSection"
|
||||
edgeIsScreenEdge: !topBarContent.hasAdjacentTopBarLive
|
||||
overrideAxisLayout: true
|
||||
forceVerticalLayout: true
|
||||
width: parent.width
|
||||
@@ -850,6 +860,7 @@ Item {
|
||||
RightSection {
|
||||
id: vRightSection
|
||||
objectName: "rightSection"
|
||||
edgeIsScreenEdge: !topBarContent.hasAdjacentBottomBarLive
|
||||
overrideAxisLayout: true
|
||||
forceVerticalLayout: true
|
||||
width: parent.width
|
||||
|
||||
@@ -930,7 +930,8 @@ Item {
|
||||
if (_barHovered)
|
||||
return;
|
||||
const excludedBar = _barExitPending ? barWindow : null;
|
||||
if (cursorOverHoverChain(gx, gy, excludedBar))
|
||||
const staleOverExitedBar = excludedBar?.containsGlobalPoint?.(gx, gy, 0) ?? false;
|
||||
if (!staleOverExitedBar && cursorOverHoverChain(gx, gy, excludedBar))
|
||||
return;
|
||||
_barExitPending = false;
|
||||
closeHoverSurfaces();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,9 @@ Item {
|
||||
property real sectionAvailablePrimarySize: 0
|
||||
property bool overrideAxisLayout: false
|
||||
property bool forceVerticalLayout: false
|
||||
// False when a perpendicular bar occupies this edge, so the first widget's click area
|
||||
// doesn't extend past the bar into the neighbour (matters most in the frame surface).
|
||||
property bool edgeIsScreenEdge: true
|
||||
|
||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||
property alias widgetLayoutLoader: layoutLoader
|
||||
@@ -68,7 +71,7 @@ Item {
|
||||
isFirst: index === 0
|
||||
isLast: index === rowRepeater.count - 1
|
||||
sectionSpacing: parent.rowSpacing
|
||||
isLeftBarEdge: true
|
||||
isLeftBarEdge: root.edgeIsScreenEdge
|
||||
isRightBarEdge: false
|
||||
}
|
||||
}
|
||||
@@ -115,7 +118,7 @@ Item {
|
||||
isFirst: index === 0
|
||||
isLast: index === columnRepeater.count - 1
|
||||
sectionSpacing: parent.columnSpacing
|
||||
isTopBarEdge: true
|
||||
isTopBarEdge: root.edgeIsScreenEdge
|
||||
isBottomBarEdge: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ Item {
|
||||
property real sectionAvailablePrimarySize: 0
|
||||
property bool overrideAxisLayout: false
|
||||
property bool forceVerticalLayout: false
|
||||
// False when a perpendicular bar occupies this edge, so the last widget's click area
|
||||
// doesn't extend past the bar into the neighbour (matters most in the frame surface).
|
||||
property bool edgeIsScreenEdge: true
|
||||
|
||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||
property alias widgetLayoutLoader: layoutLoader
|
||||
@@ -71,7 +74,7 @@ Item {
|
||||
isLast: index === rowRepeater.count - 1
|
||||
sectionSpacing: parent.rowSpacing
|
||||
isLeftBarEdge: false
|
||||
isRightBarEdge: true
|
||||
isRightBarEdge: root.edgeIsScreenEdge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +121,7 @@ Item {
|
||||
isLast: index === columnRepeater.count - 1
|
||||
sectionSpacing: parent.columnSpacing
|
||||
isTopBarEdge: false
|
||||
isBottomBarEdge: true
|
||||
isBottomBarEdge: root.edgeIsScreenEdge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Card {
|
||||
id: root
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
readonly property var device: DankConnectService.primaryDevice
|
||||
readonly property var pairingRequest: DankConnectService.pairingRequest
|
||||
readonly property bool hasBattery: !!device && typeof device.batteryCharge === "number" && device.batteryCharge >= 0
|
||||
|
||||
function statusText() {
|
||||
if (!DankConnectService.connected)
|
||||
return I18n.tr("Disconnected");
|
||||
if (!device)
|
||||
return I18n.tr("No devices");
|
||||
const connection = device.isReachable ? I18n.tr("Connected") : I18n.tr("Disconnected");
|
||||
const pairing = device.isPaired ? I18n.tr("Paired") : I18n.tr("Not paired");
|
||||
return connection + " • " + pairing;
|
||||
}
|
||||
|
||||
function batteryText() {
|
||||
if (!hasBattery)
|
||||
return "";
|
||||
const charge = Math.round(device.batteryCharge) + "%";
|
||||
return device.batteryCharging ? charge + " • " + I18n.tr("Charging") : charge;
|
||||
}
|
||||
|
||||
function reportError(title, response) {
|
||||
if (response?.error)
|
||||
ToastService.showError(title, response.error);
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Item {
|
||||
width: Math.max(180, contentRow.width - actionRow.width - trailingPanel.width - contentRow.spacing * 2)
|
||||
height: parent.height
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "smartphone"
|
||||
size: 36
|
||||
color: root.device?.isReachable ? Theme.primary : Theme.surfaceTextSecondary
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 36 - parent.spacing
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.device?.name || "DankConnect"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.statusText()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
visible: root.hasBattery
|
||||
text: root.batteryText()
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: actionRow
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: implicitWidth
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 34
|
||||
iconSize: 17
|
||||
iconName: "ring_volume"
|
||||
iconColor: Theme.primary
|
||||
tooltipText: I18n.tr("Ring")
|
||||
enabled: DankConnectService.canRing(root.device)
|
||||
onClicked: DankConnectService.ringDevice(root.device.id, response => root.reportError(I18n.tr("Ring"), response))
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 34
|
||||
iconSize: 17
|
||||
iconName: "network_ping"
|
||||
iconColor: Theme.primary
|
||||
tooltipText: I18n.tr("Ping")
|
||||
enabled: DankConnectService.canPing(root.device)
|
||||
onClicked: DankConnectService.pingDevice(root.device.id, response => root.reportError(I18n.tr("Ping"), response))
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 34
|
||||
iconSize: 17
|
||||
iconName: "content_paste_go"
|
||||
iconColor: Theme.primary
|
||||
tooltipText: I18n.tr("Send Clipboard")
|
||||
enabled: DankConnectService.canSendClipboard(root.device)
|
||||
onClicked: DankConnectService.sendClipboard(root.device.id, response => root.reportError(I18n.tr("Send Clipboard"), response))
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
buttonSize: 34
|
||||
iconSize: 17
|
||||
iconName: "open_in_new"
|
||||
iconColor: Theme.primary
|
||||
tooltipText: I18n.tr("Open App")
|
||||
onClicked: DankConnectService.openApp()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: trailingPanel
|
||||
|
||||
width: root.pairingRequest ? 270 : 52
|
||||
height: parent.height
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.pairingRequest && root.hasBattery
|
||||
name: root.hasBattery ? Theme.getBatteryIcon(root.device.batteryCharge, root.device.batteryCharging, true) : "battery_std"
|
||||
size: 28
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.pairingRequest !== null
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Pairing request from %1").arg(root.pairingRequest?.name || I18n.tr("Device"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.pairingRequest?.verificationKey || I18n.tr("Unknown")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Bold
|
||||
color: Theme.primary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Reject")
|
||||
buttonHeight: 30
|
||||
horizontalPadding: Theme.spacingM
|
||||
onClicked: DankConnectService.rejectPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to reject pairing"), response))
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Accept")
|
||||
buttonHeight: 30
|
||||
horizontalPadding: Theme.spacingM
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.primaryText
|
||||
onClicked: DankConnectService.acceptPairing(root.pairingRequest.deviceId, response => root.reportError(I18n.tr("Failed to accept pairing"), response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,11 @@ Item {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
readonly property int overviewGridHeight: 410
|
||||
readonly property int dankConnectCardHeight: 124
|
||||
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: 410
|
||||
implicitHeight: overviewGridHeight + Theme.spacingM + dankConnectCardHeight
|
||||
|
||||
signal switchToWeatherTab
|
||||
signal switchToMediaTab
|
||||
@@ -89,5 +92,12 @@ Item {
|
||||
|
||||
onClicked: root.switchToMediaTab()
|
||||
}
|
||||
|
||||
DankConnectOverviewCard {
|
||||
x: 0
|
||||
y: root.overviewGridHeight + Theme.spacingM
|
||||
width: parent.width
|
||||
height: root.dankConnectCardHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -10,7 +8,8 @@ import qs.Widgets
|
||||
|
||||
Variants {
|
||||
id: dockVariants
|
||||
model: SettingsData.getFilteredScreens("dock")
|
||||
// Connected frame mode renders the dock inside the frame surface; skip the standalone window there.
|
||||
model: SettingsData.getFilteredScreens("dock").filter(screen => !CompositorService.frameHostsDockForScreen(screen))
|
||||
|
||||
property var contextMenu
|
||||
property var trashContextMenu
|
||||
@@ -18,864 +17,74 @@ Variants {
|
||||
delegate: PanelWindow {
|
||||
id: dock
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: dock
|
||||
blurEnabled: dock.effectiveBlurEnabled && !dock.usesConnectedFrameChrome
|
||||
blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
||||
blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
||||
blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0
|
||||
blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0
|
||||
blurRadius: dock.usesConnectedFrameChrome ? Theme.connectedCornerRadius : dock.surfaceRadius
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: "dms:dock"
|
||||
WlrLayershell.layer: dock.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
||||
|
||||
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
|
||||
anchors {
|
||||
top: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||
bottom: !isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||
left: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||
right: !isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
}
|
||||
|
||||
property var modelData: item
|
||||
property bool autoHide: SettingsData.dockAutoHide || SettingsData.dockSmartAutoHide
|
||||
property real backgroundTransparency: SettingsData.dockTransparency
|
||||
property bool groupByApp: SettingsData.dockGroupByApp
|
||||
readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0
|
||||
readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right"
|
||||
readonly property bool frameDockExclusionActive: dockGeometry.frameExclusionActive
|
||||
readonly property bool connectedBarActiveOnEdge: dockGeometry.connectedBarActiveOnEdge
|
||||
readonly property real connectedJoinInset: dockGeometry.connectedJoinInset
|
||||
readonly property real dockFrameInset: dockGeometry.frameInset
|
||||
readonly property real surfaceRadius: usesConnectedFrameChrome ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||
readonly property color surfaceColor: usesConnectedFrameChrome ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
||||
readonly property color surfaceBorderColor: usesConnectedFrameChrome ? Theme.withAlpha(BlurService.borderColor, 0) : BlurService.borderColor
|
||||
readonly property real surfaceBorderWidth: usesConnectedFrameChrome ? 0 : BlurService.borderWidth
|
||||
readonly property real surfaceTopLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
||||
readonly property real surfaceTopRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
||||
readonly property real surfaceBottomLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
||||
readonly property real surfaceBottomRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
||||
readonly property real horizontalConnectorExtent: usesConnectedFrameChrome && !isVertical ? Theme.connectedCornerRadius : 0
|
||||
readonly property real verticalConnectorExtent: usesConnectedFrameChrome && isVertical ? Theme.connectedCornerRadius : 0
|
||||
|
||||
readonly property int hasApps: dockApps.implicitWidth > 0 || dockApps.implicitHeight > 0
|
||||
|
||||
readonly property real widgetHeight: SettingsData.dockIconSize
|
||||
readonly property real effectiveBarHeight: dockGeometry.visualThickness
|
||||
function getBarHeight(barConfig) {
|
||||
if (!barConfig)
|
||||
return 0;
|
||||
const innerPadding = barConfig.innerPadding ?? 4;
|
||||
const widgetThickness = Math.max(20, 26 + innerPadding * 0.6);
|
||||
const barThickness = Math.max(widgetThickness + innerPadding + 4, Theme.barHeight - 4 - (8 - innerPadding));
|
||||
const spacing = barConfig.spacing ?? 4;
|
||||
const bottomGap = barConfig.bottomGap ?? 0;
|
||||
return barThickness + spacing + bottomGap;
|
||||
}
|
||||
|
||||
readonly property real barSpacing: {
|
||||
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
|
||||
if (!defaultBar)
|
||||
return 0;
|
||||
|
||||
const barPos = defaultBar.position ?? SettingsData.Position.Top;
|
||||
const barIsHorizontal = (barPos === SettingsData.Position.Top || barPos === SettingsData.Position.Bottom);
|
||||
const barIsVertical = (barPos === SettingsData.Position.Left || barPos === SettingsData.Position.Right);
|
||||
const samePosition = (SettingsData.dockPosition === barPos);
|
||||
const dockIsHorizontal = !isVertical;
|
||||
const dockIsVertical = isVertical;
|
||||
|
||||
if (!(defaultBar.visible ?? true))
|
||||
return 0;
|
||||
const spacing = defaultBar.spacing ?? 4;
|
||||
const bottomGap = defaultBar.bottomGap ?? 0;
|
||||
if (dockIsHorizontal && barIsHorizontal && samePosition) {
|
||||
return spacing + effectiveBarHeight + bottomGap;
|
||||
}
|
||||
if (dockIsVertical && barIsVertical && samePosition) {
|
||||
return spacing + effectiveBarHeight + bottomGap;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property real adjacentTopBarHeight: {
|
||||
if (!isVertical || autoHide)
|
||||
return 0;
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const topBar = SettingsData.barConfigs.find(bc => {
|
||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
||||
return false;
|
||||
if (bc.position !== SettingsData.Position.Top && bc.position !== 0)
|
||||
return false;
|
||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
||||
return onThisScreen;
|
||||
});
|
||||
return getBarHeight(topBar);
|
||||
}
|
||||
|
||||
readonly property real adjacentLeftBarWidth: {
|
||||
if (isVertical || autoHide)
|
||||
return 0;
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const leftBar = SettingsData.barConfigs.find(bc => {
|
||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
||||
return false;
|
||||
if (bc.position !== SettingsData.Position.Left && bc.position !== 2)
|
||||
return false;
|
||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
||||
return onThisScreen;
|
||||
});
|
||||
return getBarHeight(leftBar);
|
||||
}
|
||||
|
||||
readonly property real dockMargin: SettingsData.dockMargin
|
||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||
readonly property real effectiveDockBottomGap: dockGeometry.visualOffset
|
||||
readonly property real effectiveDockMargin: dockGeometry.effectiveMargin
|
||||
readonly property real positionSpacing: barSpacing + effectiveDockBottomGap + effectiveDockMargin
|
||||
readonly property real joinedEdgeMargin: dockGeometry.joinedEdgeMargin
|
||||
readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1
|
||||
function px(v) {
|
||||
return Math.round(v * _dpr) / _dpr;
|
||||
}
|
||||
|
||||
DockGeometry {
|
||||
id: dockGeometry
|
||||
|
||||
screen: dock.screen || dock.modelData
|
||||
edge: dock.connectedBarSide
|
||||
dockVisible: dock.visible
|
||||
autoHide: dock.autoHide
|
||||
iconSize: dock.widgetHeight
|
||||
spacing: SettingsData.dockSpacing
|
||||
borderThickness: dock.borderThickness
|
||||
offset: SettingsData.dockBottomGap
|
||||
margin: SettingsData.dockMargin
|
||||
barSpacing: dock.barSpacing
|
||||
dpr: dock._dpr
|
||||
}
|
||||
|
||||
// Dock window origin in screen-relative coordinates (FrameWindow space).
|
||||
function _dockWindowOriginX() {
|
||||
if (!dock.isVertical)
|
||||
return 0;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
return (dock.screen ? dock.screen.width : 0) - dock.width;
|
||||
return 0;
|
||||
}
|
||||
function _dockWindowOriginY() {
|
||||
if (dock.isVertical)
|
||||
return 0;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom)
|
||||
return (dock.screen ? dock.screen.height : 0) - dock.height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property string _dockScreenName: dock.modelData ? dock.modelData.name : (dock.screen ? dock.screen.name : "")
|
||||
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(dock._dockScreenName)
|
||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(dock._dockScreenName) || SettingsData.dockUseOverlayLayer
|
||||
|
||||
function _syncDockChromeState() {
|
||||
if (!dock._dockScreenName)
|
||||
return;
|
||||
if (!dock.usesConnectedFrameChrome) {
|
||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
||||
return;
|
||||
}
|
||||
|
||||
const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps;
|
||||
const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open"));
|
||||
const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x;
|
||||
const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y;
|
||||
const bodyW = dock.hasApps ? dockBackground.width : 0;
|
||||
const bodyH = dock.hasApps ? dockBackground.height : 0;
|
||||
ConnectedModeState.setDockState(dock._dockScreenName, {
|
||||
"kind": "dock",
|
||||
"screenName": dock._dockScreenName,
|
||||
"phase": phase,
|
||||
"visible": presented,
|
||||
"presented": presented,
|
||||
"reveal": presented,
|
||||
"barSide": dock.connectedBarSide,
|
||||
"bodyRect": {
|
||||
"x": bodyX,
|
||||
"y": bodyY,
|
||||
"width": bodyW,
|
||||
"height": bodyH
|
||||
},
|
||||
"animationOffset": {
|
||||
"x": dockSlide.x,
|
||||
"y": dockSlide.y
|
||||
},
|
||||
"scale": 1,
|
||||
"opacity": Theme.connectedSurfaceColor.a,
|
||||
"bodyX": bodyX,
|
||||
"bodyY": bodyY,
|
||||
"bodyW": bodyW,
|
||||
"bodyH": bodyH,
|
||||
"slideX": dockSlide.x,
|
||||
"slideY": dockSlide.y
|
||||
});
|
||||
}
|
||||
|
||||
function _syncDockSlide() {
|
||||
if (!dock._dockScreenName || !dock.usesConnectedFrameChrome)
|
||||
return;
|
||||
ConnectedModeState.setDockSlide(dock._dockScreenName, dockSlide.x, dockSlide.y);
|
||||
}
|
||||
|
||||
DeferredAction {
|
||||
id: dockSlideSync
|
||||
enabled: dock.usesConnectedFrameChrome
|
||||
onTriggered: dock._syncDockSlide()
|
||||
}
|
||||
|
||||
function _queueSlideSync() {
|
||||
if (!dock.usesConnectedFrameChrome)
|
||||
return;
|
||||
dockSlideSync.schedule();
|
||||
}
|
||||
|
||||
DeferredAction {
|
||||
id: dockChromeSync
|
||||
onTriggered: dock._syncDockChromeState()
|
||||
}
|
||||
|
||||
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
||||
property bool revealSticky: false
|
||||
|
||||
readonly property bool shouldHideForWindows: {
|
||||
if (!SettingsData.dockSmartAutoHide)
|
||||
return false;
|
||||
if (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
||||
return false;
|
||||
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const dockThickness = dockGeometry.motionThickness;
|
||||
const screenWidth = dock.screen?.width ?? 0;
|
||||
const screenHeight = dock.screen?.height ?? 0;
|
||||
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.windows;
|
||||
|
||||
let currentWorkspaceId = null;
|
||||
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||
const ws = NiriService.allWorkspaces[i];
|
||||
if (ws.output === screenName && ws.is_active) {
|
||||
currentWorkspaceId = ws.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorkspaceId === null)
|
||||
return false;
|
||||
|
||||
for (let i = 0; i < NiriService.windows.length; i++) {
|
||||
const win = NiriService.windows[i];
|
||||
if (win.workspace_id !== currentWorkspaceId)
|
||||
continue;
|
||||
|
||||
// Get window position and size from layout data
|
||||
const tilePos = win.layout?.tile_pos_in_workspace_view;
|
||||
const winSize = win.layout?.window_size || win.layout?.tile_size;
|
||||
|
||||
if (tilePos && winSize) {
|
||||
const winX = tilePos[0];
|
||||
const winY = tilePos[1];
|
||||
const winW = winSize[0];
|
||||
const winH = winSize[1];
|
||||
|
||||
switch (SettingsData.dockPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
if (winY < dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Bottom:
|
||||
if (winY + winH > screenHeight - dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Left:
|
||||
if (winX < dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Right:
|
||||
if (winX + winW > screenWidth - dockThickness)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else if (!win.is_floating) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CompositorService.isMango) {
|
||||
MangoService.windows;
|
||||
MangoService.outputs;
|
||||
return CompositorService.mangoDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
// Hyprland implementation (current workspace + visible special workspaces)
|
||||
Hyprland.focusedWorkspace;
|
||||
Hyprland.toplevels;
|
||||
return CompositorService.hyprlandDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: revealHold
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: dock.revealSticky = false
|
||||
}
|
||||
|
||||
// Flip `reveal` false when a modal claims this edge; reuses the slide animation
|
||||
readonly property bool _modalRetractActive: {
|
||||
if (!dock._dockScreenName)
|
||||
return false;
|
||||
return ConnectedModeState.dockRetractActiveForSide(dock._dockScreenName, dock.connectedBarSide);
|
||||
}
|
||||
|
||||
property bool startupRevealDone: false
|
||||
|
||||
Timer {
|
||||
id: startupRevealTimer
|
||||
interval: 200
|
||||
running: true
|
||||
onTriggered: dock.startupRevealDone = true
|
||||
}
|
||||
|
||||
property bool reveal: {
|
||||
if (!startupRevealDone)
|
||||
return false;
|
||||
|
||||
if (_modalRetractActive)
|
||||
return false;
|
||||
|
||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Smart auto-hide: show dock when no windows overlap, hide when they do
|
||||
if (SettingsData.dockSmartAutoHide) {
|
||||
if (shouldHideForWindows)
|
||||
return dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
||||
return true; // No overlapping windows - show dock
|
||||
}
|
||||
|
||||
// Regular auto-hide: always hide unless hovering
|
||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
||||
}
|
||||
|
||||
onContextMenuOpenChanged: {
|
||||
if (!contextMenuOpen && autoHide && !dockMouseArea.containsMouse) {
|
||||
revealSticky = true;
|
||||
revealHold.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: dockChromeSync.schedule()
|
||||
Component.onDestruction: {
|
||||
dockChromeSync.cancel();
|
||||
dockSlideSync.cancel();
|
||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
||||
}
|
||||
|
||||
onRevealChanged: dock._syncDockChromeState()
|
||||
onWidthChanged: dock._syncDockChromeState()
|
||||
onHeightChanged: dock._syncDockChromeState()
|
||||
onVisibleChanged: dock._syncDockChromeState()
|
||||
onHasAppsChanged: dock._syncDockChromeState()
|
||||
onConnectedBarSideChanged: dock._syncDockChromeState()
|
||||
onUsesConnectedFrameChromeChanged: dock._syncDockChromeState()
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onConnectedFrameModeActiveChanged() {
|
||||
dockSlideSync.cancel();
|
||||
dock._syncDockChromeState();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDockTransparencyChanged() {
|
||||
dock.backgroundTransparency = SettingsData.dockTransparency;
|
||||
}
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:dock"
|
||||
WlrLayershell.layer: body.usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
||||
|
||||
anchors {
|
||||
top: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||
bottom: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||
left: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||
right: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
}
|
||||
|
||||
visible: {
|
||||
if (CompositorService.isNiri && NiriService.inOverview) {
|
||||
return SettingsData.dockOpenOnOverview;
|
||||
}
|
||||
return SettingsData.showDock;
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
readonly property real dockReserveZone: dockGeometry.reserveZone
|
||||
readonly property bool shouldReserveDockSpace: dockGeometry.shouldReserveSpace
|
||||
implicitWidth: body.surfaceImplicitWidth
|
||||
implicitHeight: body.surfaceImplicitHeight
|
||||
exclusiveZone: body.surfaceExclusiveZone
|
||||
|
||||
exclusiveZone: {
|
||||
if (!dock.shouldReserveDockSpace)
|
||||
return -1;
|
||||
if (dock.frameDockExclusionActive)
|
||||
return -1;
|
||||
return dock.dockReserveZone;
|
||||
}
|
||||
|
||||
property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35)
|
||||
|
||||
implicitWidth: isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
implicitHeight: !isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
|
||||
Item {
|
||||
id: maskItem
|
||||
parent: dock.contentItem
|
||||
visible: false
|
||||
readonly property bool expanded: dock.reveal
|
||||
x: {
|
||||
const baseX = dockCore.x + dockMouseArea.x;
|
||||
if (isVertical && SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
||||
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
||||
}
|
||||
y: {
|
||||
const baseY = dockCore.y + dockMouseArea.y;
|
||||
if (!isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom)
|
||||
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
||||
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
||||
}
|
||||
width: dockMouseArea.width + (isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.horizontalConnectorExtent * 2 : 0)
|
||||
height: dockMouseArea.height + (!isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.verticalConnectorExtent * 2 : 0)
|
||||
WindowBlur {
|
||||
targetWindow: dock
|
||||
blurEnabled: body.effectiveBlurEnabled && !body.usesConnectedFrameChrome
|
||||
blurX: body.blurX
|
||||
blurY: body.blurY
|
||||
blurWidth: body.blurWidth
|
||||
blurHeight: body.blurHeight
|
||||
blurRadius: body.blurRadius
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: maskItem
|
||||
item: body.inputMaskItem
|
||||
}
|
||||
|
||||
DockBody {
|
||||
id: body
|
||||
anchors.fill: parent
|
||||
hostWindow: dock
|
||||
modelData: dock.modelData
|
||||
contextMenu: dockVariants.contextMenu
|
||||
trashContextMenu: dockVariants.trashContextMenu
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: dockExclusion
|
||||
|
||||
screen: dock.screen || dock.modelData
|
||||
visible: dock.frameDockExclusionActive && dock.shouldReserveDockSpace
|
||||
visible: body.frameDockExclusionActive && body.shouldReserveDockSpace
|
||||
color: "transparent"
|
||||
mask: Region {}
|
||||
implicitWidth: dock.isVertical ? dock.dockReserveZone : 1
|
||||
implicitHeight: dock.isVertical ? 1 : dock.dockReserveZone
|
||||
exclusiveZone: visible ? dock.dockReserveZone : -1
|
||||
implicitWidth: body.isVertical ? body.dockReserveZone : 1
|
||||
implicitHeight: body.isVertical ? 1 : body.dockReserveZone
|
||||
exclusiveZone: visible ? body.dockReserveZone : -1
|
||||
|
||||
WlrLayershell.namespace: "dms:dock-exclusion"
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||
left: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||
right: !dock.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
}
|
||||
}
|
||||
|
||||
property var hoveredButton: {
|
||||
if (!dockApps.children[0]) {
|
||||
return null;
|
||||
}
|
||||
const layoutItem = dockApps.children[0];
|
||||
const flowLayout = layoutItem.children[0];
|
||||
let repeater = null;
|
||||
for (var i = 0; i < flowLayout.children.length; i++) {
|
||||
const child = flowLayout.children[i];
|
||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
||||
repeater = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!repeater || !repeater.itemAt) {
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||
return item.dockButton;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
DankTooltip {
|
||||
id: dockTooltip
|
||||
targetScreen: dock.screen
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: tooltipRevealDelay
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: dock.showTooltipForHoveredButton()
|
||||
}
|
||||
|
||||
function showTooltipForHoveredButton() {
|
||||
dockTooltip.hide();
|
||||
if (!dock.hoveredButton || !dock.reveal || slideXAnimation.running || slideYAnimation.running)
|
||||
return;
|
||||
|
||||
const buttonLocalPos = dock.hoveredButton.mapToItem(null, 0, 0);
|
||||
const tooltipText = dock.hoveredButton.tooltipText || "";
|
||||
if (!tooltipText)
|
||||
return;
|
||||
|
||||
const screenHeight = dock.screen ? dock.screen.height : 0;
|
||||
|
||||
const gap = Theme.spacingS;
|
||||
const bgMargin = dockGeometry.bodyEdgeMargin;
|
||||
const btnW = dock.hoveredButton.width;
|
||||
const btnH = dock.hoveredButton.height;
|
||||
|
||||
if (!dock.isVertical) {
|
||||
const isBottom = SettingsData.dockPosition === SettingsData.Position.Bottom;
|
||||
const tooltipX = buttonLocalPos.x + btnW / 2 + adjacentLeftBarWidth;
|
||||
const tooltipHeight = 32;
|
||||
const totalFromEdge = bgMargin + dockBackground.height + dock.borderThickness + gap;
|
||||
const screenRelativeY = isBottom ? (screenHeight - totalFromEdge - tooltipHeight) : totalFromEdge;
|
||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isLeft = SettingsData.dockPosition === SettingsData.Position.Left;
|
||||
const screenWidth = dock.screen ? dock.screen.width : 0;
|
||||
const totalFromEdge = bgMargin + dockBackground.width + dock.borderThickness + gap;
|
||||
const tooltipX = isLeft ? totalFromEdge : (screenWidth - totalFromEdge);
|
||||
const screenRelativeY = buttonLocalPos.y + btnH / 2 + adjacentTopBarHeight;
|
||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, isLeft, !isLeft);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: dock
|
||||
function onRevealChanged() {
|
||||
if (!dock.reveal) {
|
||||
tooltipRevealDelay.stop();
|
||||
dockTooltip.hide();
|
||||
} else {
|
||||
tooltipRevealDelay.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function onHoveredButtonChanged() {
|
||||
dock.showTooltipForHoveredButton();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockCore
|
||||
anchors.fill: parent
|
||||
x: isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? animationHeadroom : 0
|
||||
y: !isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? animationHeadroom : 0
|
||||
|
||||
Connections {
|
||||
target: dockMouseArea
|
||||
function onContainsMouseChanged() {
|
||||
if (dockMouseArea.containsMouse) {
|
||||
dock.revealSticky = true;
|
||||
revealHold.stop();
|
||||
} else {
|
||||
if (dock.autoHide && !dock.contextMenuOpen) {
|
||||
revealHold.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
property real currentScreen: modelData ? modelData : dock.screen
|
||||
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
|
||||
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
|
||||
property real maxDockWidth: screenWidth * 0.98
|
||||
property real maxDockHeight: screenHeight * 0.98
|
||||
|
||||
height: {
|
||||
if (dock.isVertical) {
|
||||
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight);
|
||||
}
|
||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
||||
}
|
||||
width: {
|
||||
if (dock.isVertical) {
|
||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
||||
}
|
||||
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
|
||||
}
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
|
||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
}
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
opacity: dock.startupRevealDone ? 1 : 0
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
x: {
|
||||
if (!dock.isVertical)
|
||||
return 0;
|
||||
if (dock.reveal)
|
||||
return 0;
|
||||
if (dock.usesConnectedFrameChrome) {
|
||||
const retractDist = dockBackground.width + SettingsData.dockSpacing + 10;
|
||||
return SettingsData.dockPosition === SettingsData.Position.Right ? retractDist : -retractDist;
|
||||
}
|
||||
const hideDistance = dockGeometry.motionThickness + 10;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right) {
|
||||
return hideDistance;
|
||||
} else {
|
||||
return -hideDistance;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
if (dock.isVertical)
|
||||
return 0;
|
||||
if (dock.reveal)
|
||||
return 0;
|
||||
if (dock.usesConnectedFrameChrome) {
|
||||
const retractDist = dockBackground.height + SettingsData.dockSpacing + 10;
|
||||
return SettingsData.dockPosition === SettingsData.Position.Bottom ? retractDist : -retractDist;
|
||||
}
|
||||
const hideDistance = dockGeometry.motionThickness + 10;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
||||
return hideDistance;
|
||||
} else {
|
||||
return -hideDistance;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
id: slideXAnimation
|
||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
||||
onRunningChanged: if (!running)
|
||||
dock._syncDockChromeState()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
id: slideYAnimation
|
||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
||||
onRunningChanged: if (!running)
|
||||
dock._syncDockChromeState()
|
||||
}
|
||||
}
|
||||
|
||||
onXChanged: dock._queueSlideSync()
|
||||
onYChanged: dock._queueSlideSync()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockBackground
|
||||
objectName: "dockBackground"
|
||||
visible: dock.hasApps
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined) : undefined
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined) : undefined
|
||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
}
|
||||
anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? dockGeometry.bodyEdgeMargin : 0
|
||||
|
||||
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
|
||||
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// Avoid an offscreen texture seam where the connected dock meets the frame.
|
||||
layer.enabled: !usesConnectedFrameChrome
|
||||
clip: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
||||
color: dock.surfaceColor
|
||||
topLeftRadius: dock.surfaceTopLeftRadius
|
||||
topRightRadius: dock.surfaceTopRightRadius
|
||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
||||
color: "transparent"
|
||||
topLeftRadius: dock.surfaceTopLeftRadius
|
||||
topRightRadius: dock.surfaceTopRightRadius
|
||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
||||
border.color: dock.surfaceBorderColor
|
||||
border.width: dock.surfaceBorderWidth
|
||||
z: 100
|
||||
}
|
||||
|
||||
// Sync dockBackground geometry to ConnectedModeState
|
||||
onXChanged: dock._syncDockChromeState()
|
||||
onYChanged: dock._syncDockChromeState()
|
||||
onWidthChanged: dock._syncDockChromeState()
|
||||
onHeightChanged: dock._syncDockChromeState()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockConnectedChrome
|
||||
visible: Theme.isConnectedEffect && dock.reveal && dock.hasApps && !FrameTransitionState.effectiveConnectedFrameModeActive
|
||||
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
||||
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
||||
readonly property real bodyRadius: dock.surfaceRadius
|
||||
readonly property bool barTop: dock.connectedBarSide === "top"
|
||||
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
||||
readonly property bool barLeft: dock.connectedBarSide === "left"
|
||||
readonly property bool barRight: dock.connectedBarSide === "right"
|
||||
|
||||
x: dockBackground.x - extraLeft
|
||||
y: dockBackground.y - extraTop
|
||||
width: dockBackground.width + extraLeft * 2
|
||||
height: dockBackground.height + extraTop * 2
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
|
||||
|
||||
property real widthPx: width
|
||||
property real heightPx: height
|
||||
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
|
||||
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
|
||||
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
|
||||
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: dockBorderShape
|
||||
x: dockBackground.x - borderThickness
|
||||
y: dockBackground.y - borderThickness
|
||||
width: dockBackground.width + borderThickness * 2
|
||||
height: dockBackground.height + borderThickness * 2
|
||||
visible: SettingsData.dockBorderEnabled && dock.hasApps && !usesConnectedFrameChrome
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
readonly property real borderThickness: Math.max(1, dock.borderThickness)
|
||||
readonly property real i: borderThickness / 2
|
||||
readonly property real cr: dock.surfaceRadius
|
||||
readonly property real w: dockBackground.width
|
||||
readonly property real h: dockBackground.height
|
||||
|
||||
readonly property color borderColor: {
|
||||
const opacity = SettingsData.dockBorderOpacity;
|
||||
switch (SettingsData.dockBorderColor) {
|
||||
case "secondary":
|
||||
return Theme.withAlpha(Theme.secondary, opacity);
|
||||
case "primary":
|
||||
return Theme.withAlpha(Theme.primary, opacity);
|
||||
default:
|
||||
return Theme.withAlpha(Theme.surfaceText, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: dockBorderShape.borderColor
|
||||
strokeWidth: dockBorderShape.borderThickness
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
capStyle: ShapePath.FlatCap
|
||||
|
||||
PathSvg {
|
||||
path: {
|
||||
const bt = dockBorderShape.borderThickness;
|
||||
const i = dockBorderShape.i;
|
||||
const cr = dockBorderShape.cr + bt - i;
|
||||
const w = dockBorderShape.w;
|
||||
const h = dockBorderShape.h;
|
||||
|
||||
let d = `M ${i + cr} ${i}`;
|
||||
d += ` L ${i + w + 2 * (bt - i) - cr} ${i}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i)} ${i + cr}`;
|
||||
d += ` L ${i + w + 2 * (bt - i)} ${i + h + 2 * (bt - i) - cr}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i) - cr} ${i + h + 2 * (bt - i)}`;
|
||||
d += ` L ${i + cr} ${i + h + 2 * (bt - i)}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i} ${i + h + 2 * (bt - i) - cr}`;
|
||||
d += ` L ${i} ${i + cr}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + cr} ${i}`;
|
||||
d += " Z";
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DockApps {
|
||||
id: dockApps
|
||||
|
||||
anchors.top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? dockBackground.top : undefined) : undefined
|
||||
anchors.bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? dockBackground.bottom : undefined) : undefined
|
||||
anchors.horizontalCenter: !dock.isVertical ? dockBackground.horizontalCenter : undefined
|
||||
anchors.left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? dockBackground.left : undefined) : undefined
|
||||
anchors.right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? dockBackground.right : undefined) : undefined
|
||||
anchors.verticalCenter: dock.isVertical ? dockBackground.verticalCenter : undefined
|
||||
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
|
||||
contextMenu: dockVariants.contextMenu
|
||||
trashContextMenu: dockVariants.trashContextMenu
|
||||
groupByApp: dock.groupByApp
|
||||
isVertical: dock.isVertical
|
||||
dockScreen: dock.screen
|
||||
iconSize: dock.widgetHeight
|
||||
usesOverlayLayer: dock.usesOverlayLayer
|
||||
}
|
||||
}
|
||||
top: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top) : true
|
||||
bottom: !body.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom) : true
|
||||
left: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||
right: !body.isVertical ? true : (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +479,8 @@ Item {
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property var dockButton: {
|
||||
switch (itemData.type) {
|
||||
@@ -599,7 +601,7 @@ Item {
|
||||
height: delegateItem.height
|
||||
actualIconSize: root.iconSize
|
||||
dockApps: root
|
||||
index: model.index
|
||||
index: delegateItem.index
|
||||
}
|
||||
|
||||
DockTrashButton {
|
||||
@@ -624,7 +626,7 @@ Item {
|
||||
appData: itemData
|
||||
contextMenu: root.contextMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
index: delegateItem.index
|
||||
parentDockScreen: root.dockScreen
|
||||
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||
windowTitle: {
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: dock
|
||||
|
||||
required property var hostWindow
|
||||
required property var modelData
|
||||
readonly property var screen: modelData
|
||||
property var contextMenu
|
||||
property var trashContextMenu
|
||||
|
||||
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
|
||||
property bool autoHide: SettingsData.dockAutoHide || SettingsData.dockSmartAutoHide
|
||||
property real backgroundTransparency: SettingsData.dockTransparency
|
||||
property bool groupByApp: SettingsData.dockGroupByApp
|
||||
readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0
|
||||
readonly property string connectedBarSide: SettingsData.dockPosition === SettingsData.Position.Top ? "top" : SettingsData.dockPosition === SettingsData.Position.Bottom ? "bottom" : SettingsData.dockPosition === SettingsData.Position.Left ? "left" : "right"
|
||||
readonly property bool frameDockExclusionActive: dockGeometry.frameExclusionActive
|
||||
readonly property bool connectedBarActiveOnEdge: dockGeometry.connectedBarActiveOnEdge
|
||||
readonly property real connectedJoinInset: dockGeometry.connectedJoinInset
|
||||
readonly property real dockFrameInset: dockGeometry.frameInset
|
||||
readonly property real surfaceRadius: usesConnectedFrameChrome ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||
readonly property color surfaceColor: usesConnectedFrameChrome ? Theme.connectedSurfaceColor : Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
|
||||
readonly property color surfaceBorderColor: usesConnectedFrameChrome ? Theme.withAlpha(BlurService.borderColor, 0) : BlurService.borderColor
|
||||
readonly property real surfaceBorderWidth: usesConnectedFrameChrome ? 0 : BlurService.borderWidth
|
||||
readonly property real surfaceTopLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
||||
readonly property real surfaceTopRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Top || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
||||
readonly property real surfaceBottomLeftRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Left) ? 0 : surfaceRadius
|
||||
readonly property real surfaceBottomRightRadius: usesConnectedFrameChrome && (SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right) ? 0 : surfaceRadius
|
||||
readonly property real horizontalConnectorExtent: usesConnectedFrameChrome && !isVertical ? Theme.connectedCornerRadius : 0
|
||||
readonly property real verticalConnectorExtent: usesConnectedFrameChrome && isVertical ? Theme.connectedCornerRadius : 0
|
||||
|
||||
readonly property int hasApps: dockApps.implicitWidth > 0 || dockApps.implicitHeight > 0
|
||||
|
||||
readonly property real widgetHeight: SettingsData.dockIconSize
|
||||
readonly property real effectiveBarHeight: dockGeometry.visualThickness
|
||||
function getBarHeight(barConfig) {
|
||||
if (!barConfig)
|
||||
return 0;
|
||||
const innerPadding = barConfig.innerPadding ?? 4;
|
||||
const widgetThickness = Math.max(20, 26 + innerPadding * 0.6);
|
||||
const barThickness = Math.max(widgetThickness + innerPadding + 4, Theme.barHeight - 4 - (8 - innerPadding));
|
||||
const spacing = barConfig.spacing ?? 4;
|
||||
const bottomGap = barConfig.bottomGap ?? 0;
|
||||
return barThickness + spacing + bottomGap;
|
||||
}
|
||||
|
||||
readonly property real barSpacing: {
|
||||
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
|
||||
if (!defaultBar)
|
||||
return 0;
|
||||
|
||||
const barPos = defaultBar.position ?? SettingsData.Position.Top;
|
||||
const barIsHorizontal = (barPos === SettingsData.Position.Top || barPos === SettingsData.Position.Bottom);
|
||||
const barIsVertical = (barPos === SettingsData.Position.Left || barPos === SettingsData.Position.Right);
|
||||
const samePosition = (SettingsData.dockPosition === barPos);
|
||||
const dockIsHorizontal = !isVertical;
|
||||
const dockIsVertical = isVertical;
|
||||
|
||||
if (!(defaultBar.visible ?? true))
|
||||
return 0;
|
||||
const spacing = defaultBar.spacing ?? 4;
|
||||
const bottomGap = defaultBar.bottomGap ?? 0;
|
||||
if (dockIsHorizontal && barIsHorizontal && samePosition) {
|
||||
return spacing + effectiveBarHeight + bottomGap;
|
||||
}
|
||||
if (dockIsVertical && barIsVertical && samePosition) {
|
||||
return spacing + effectiveBarHeight + bottomGap;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property real adjacentTopBarHeight: {
|
||||
if (!isVertical || autoHide)
|
||||
return 0;
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const topBar = SettingsData.barConfigs.find(bc => {
|
||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
||||
return false;
|
||||
if (bc.position !== SettingsData.Position.Top && bc.position !== 0)
|
||||
return false;
|
||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
||||
return onThisScreen;
|
||||
});
|
||||
return getBarHeight(topBar);
|
||||
}
|
||||
|
||||
readonly property real adjacentLeftBarWidth: {
|
||||
if (isVertical || autoHide)
|
||||
return 0;
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const leftBar = SettingsData.barConfigs.find(bc => {
|
||||
if (!bc.enabled || bc.autoHide || !(bc.visible ?? true))
|
||||
return false;
|
||||
if (bc.position !== SettingsData.Position.Left && bc.position !== 2)
|
||||
return false;
|
||||
const onThisScreen = bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all") || bc.screenPreferences.includes(screenName);
|
||||
return onThisScreen;
|
||||
});
|
||||
return getBarHeight(leftBar);
|
||||
}
|
||||
|
||||
readonly property real dockMargin: SettingsData.dockMargin
|
||||
readonly property bool effectiveBlurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||
readonly property real effectiveDockBottomGap: dockGeometry.visualOffset
|
||||
readonly property real effectiveDockMargin: dockGeometry.effectiveMargin
|
||||
readonly property real positionSpacing: barSpacing + effectiveDockBottomGap + effectiveDockMargin
|
||||
readonly property real joinedEdgeMargin: dockGeometry.joinedEdgeMargin
|
||||
readonly property real _dpr: (dock.screen && dock.screen.devicePixelRatio) ? dock.screen.devicePixelRatio : 1
|
||||
function px(v) {
|
||||
return Math.round(v * _dpr) / _dpr;
|
||||
}
|
||||
|
||||
DockGeometry {
|
||||
id: dockGeometry
|
||||
|
||||
screen: dock.screen || dock.modelData
|
||||
edge: dock.connectedBarSide
|
||||
dockVisible: dock.visible
|
||||
autoHide: dock.autoHide
|
||||
iconSize: dock.widgetHeight
|
||||
spacing: SettingsData.dockSpacing
|
||||
borderThickness: dock.borderThickness
|
||||
offset: SettingsData.dockBottomGap
|
||||
margin: SettingsData.dockMargin
|
||||
barSpacing: dock.barSpacing
|
||||
dpr: dock._dpr
|
||||
}
|
||||
|
||||
// Dock window origin in screen-relative coordinates (FrameWindow space).
|
||||
function _dockWindowOriginX() {
|
||||
if (!dock.isVertical)
|
||||
return 0;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
return (dock.screen ? dock.screen.width : 0) - dock.width;
|
||||
return 0;
|
||||
}
|
||||
function _dockWindowOriginY() {
|
||||
if (dock.isVertical)
|
||||
return 0;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom)
|
||||
return (dock.screen ? dock.screen.height : 0) - dock.height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property string _dockScreenName: dock.modelData ? dock.modelData.name : (dock.screen ? dock.screen.name : "")
|
||||
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(dock._dockScreenName)
|
||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(dock._dockScreenName) || SettingsData.dockUseOverlayLayer
|
||||
|
||||
function _syncDockChromeState() {
|
||||
if (!dock._dockScreenName)
|
||||
return;
|
||||
if (!dock.usesConnectedFrameChrome) {
|
||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
||||
return;
|
||||
}
|
||||
|
||||
const presented = dock.visible && (dock.reveal || slideXAnimation.running || slideYAnimation.running) && dock.hasApps;
|
||||
const phase = !presented ? "hidden" : ((!dock.reveal && (slideXAnimation.running || slideYAnimation.running)) ? "closing" : ((slideXAnimation.running || slideYAnimation.running) ? "opening" : "open"));
|
||||
const bodyX = dock._dockWindowOriginX() + dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x;
|
||||
const bodyY = dock._dockWindowOriginY() + dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y;
|
||||
const bodyW = dock.hasApps ? dockBackground.width : 0;
|
||||
const bodyH = dock.hasApps ? dockBackground.height : 0;
|
||||
ConnectedModeState.setDockState(dock._dockScreenName, {
|
||||
"kind": "dock",
|
||||
"screenName": dock._dockScreenName,
|
||||
"phase": phase,
|
||||
"visible": presented,
|
||||
"presented": presented,
|
||||
"reveal": presented,
|
||||
"barSide": dock.connectedBarSide,
|
||||
"bodyRect": {
|
||||
"x": bodyX,
|
||||
"y": bodyY,
|
||||
"width": bodyW,
|
||||
"height": bodyH
|
||||
},
|
||||
"animationOffset": {
|
||||
"x": dockSlide.x,
|
||||
"y": dockSlide.y
|
||||
},
|
||||
"scale": 1,
|
||||
"opacity": Theme.connectedSurfaceColor.a,
|
||||
"bodyX": bodyX,
|
||||
"bodyY": bodyY,
|
||||
"bodyW": bodyW,
|
||||
"bodyH": bodyH,
|
||||
"slideX": dockSlide.x,
|
||||
"slideY": dockSlide.y
|
||||
});
|
||||
}
|
||||
|
||||
function _syncDockSlide() {
|
||||
if (!dock._dockScreenName || !dock.usesConnectedFrameChrome)
|
||||
return;
|
||||
ConnectedModeState.setDockSlide(dock._dockScreenName, dockSlide.x, dockSlide.y);
|
||||
}
|
||||
|
||||
DeferredAction {
|
||||
id: dockSlideSync
|
||||
enabled: dock.usesConnectedFrameChrome
|
||||
onTriggered: dock._syncDockSlide()
|
||||
}
|
||||
|
||||
function _queueSlideSync() {
|
||||
if (!dock.usesConnectedFrameChrome)
|
||||
return;
|
||||
dockSlideSync.schedule();
|
||||
}
|
||||
|
||||
DeferredAction {
|
||||
id: dockChromeSync
|
||||
onTriggered: dock._syncDockChromeState()
|
||||
}
|
||||
|
||||
property bool contextMenuOpen: (dock.contextMenu && dock.contextMenu.visible && dock.contextMenu.screen === modelData)
|
||||
property bool revealSticky: false
|
||||
|
||||
readonly property bool shouldHideForWindows: {
|
||||
if (!SettingsData.dockSmartAutoHide)
|
||||
return false;
|
||||
if (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango)
|
||||
return false;
|
||||
|
||||
const screenName = dock.modelData?.name ?? "";
|
||||
const dockThickness = dockGeometry.motionThickness;
|
||||
const screenWidth = dock.screen?.width ?? 0;
|
||||
const screenHeight = dock.screen?.height ?? 0;
|
||||
|
||||
if (CompositorService.isNiri) {
|
||||
NiriService.windows;
|
||||
|
||||
let currentWorkspaceId = null;
|
||||
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||
const ws = NiriService.allWorkspaces[i];
|
||||
if (ws.output === screenName && ws.is_active) {
|
||||
currentWorkspaceId = ws.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorkspaceId === null)
|
||||
return false;
|
||||
|
||||
for (let i = 0; i < NiriService.windows.length; i++) {
|
||||
const win = NiriService.windows[i];
|
||||
if (win.workspace_id !== currentWorkspaceId)
|
||||
continue;
|
||||
|
||||
// Get window position and size from layout data
|
||||
const tilePos = win.layout?.tile_pos_in_workspace_view;
|
||||
const winSize = win.layout?.window_size || win.layout?.tile_size;
|
||||
|
||||
if (tilePos && winSize) {
|
||||
const winX = tilePos[0];
|
||||
const winY = tilePos[1];
|
||||
const winW = winSize[0];
|
||||
const winH = winSize[1];
|
||||
|
||||
switch (SettingsData.dockPosition) {
|
||||
case SettingsData.Position.Top:
|
||||
if (winY < dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Bottom:
|
||||
if (winY + winH > screenHeight - dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Left:
|
||||
if (winX < dockThickness)
|
||||
return true;
|
||||
break;
|
||||
case SettingsData.Position.Right:
|
||||
if (winX + winW > screenWidth - dockThickness)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else if (!win.is_floating) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CompositorService.isMango) {
|
||||
MangoService.windows;
|
||||
MangoService.outputs;
|
||||
return CompositorService.mangoDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
// Hyprland implementation (current workspace + visible special workspaces)
|
||||
Hyprland.focusedWorkspace;
|
||||
Hyprland.toplevels;
|
||||
return CompositorService.hyprlandDockOverlapForSmartAutoHide(screenName, SettingsData.dockPosition, dockThickness, screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: revealHold
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: dock.revealSticky = false
|
||||
}
|
||||
|
||||
// Flip `reveal` false when a modal claims this edge; reuses the slide animation
|
||||
readonly property bool _modalRetractActive: {
|
||||
if (!dock._dockScreenName)
|
||||
return false;
|
||||
return ConnectedModeState.dockRetractActiveForSide(dock._dockScreenName, dock.connectedBarSide);
|
||||
}
|
||||
|
||||
property bool startupRevealDone: false
|
||||
|
||||
Timer {
|
||||
id: startupRevealTimer
|
||||
interval: 200
|
||||
running: true
|
||||
onTriggered: dock.startupRevealDone = true
|
||||
}
|
||||
|
||||
property bool reveal: {
|
||||
if (!startupRevealDone)
|
||||
return false;
|
||||
|
||||
if (_modalRetractActive)
|
||||
return false;
|
||||
|
||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Smart auto-hide: show dock when no windows overlap, hide when they do
|
||||
if (SettingsData.dockSmartAutoHide) {
|
||||
if (shouldHideForWindows)
|
||||
return dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
||||
return true; // No overlapping windows - show dock
|
||||
}
|
||||
|
||||
// Regular auto-hide: always hide unless hovering
|
||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
||||
}
|
||||
|
||||
onContextMenuOpenChanged: {
|
||||
if (!contextMenuOpen && autoHide && !dockMouseArea.containsMouse) {
|
||||
revealSticky = true;
|
||||
revealHold.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: dockChromeSync.schedule()
|
||||
Component.onDestruction: {
|
||||
dockChromeSync.cancel();
|
||||
dockSlideSync.cancel();
|
||||
ConnectedModeState.clearDockState(dock._dockScreenName);
|
||||
}
|
||||
|
||||
onRevealChanged: dock._syncDockChromeState()
|
||||
onWidthChanged: dock._syncDockChromeState()
|
||||
onHeightChanged: dock._syncDockChromeState()
|
||||
onVisibleChanged: dock._syncDockChromeState()
|
||||
onHasAppsChanged: dock._syncDockChromeState()
|
||||
onConnectedBarSideChanged: dock._syncDockChromeState()
|
||||
onUsesConnectedFrameChromeChanged: dock._syncDockChromeState()
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onConnectedFrameModeActiveChanged() {
|
||||
dockSlideSync.cancel();
|
||||
dock._syncDockChromeState();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onDockTransparencyChanged() {
|
||||
dock.backgroundTransparency = SettingsData.dockTransparency;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real dockReserveZone: dockGeometry.reserveZone
|
||||
readonly property bool shouldReserveDockSpace: dockGeometry.shouldReserveSpace
|
||||
|
||||
readonly property real surfaceExclusiveZone: {
|
||||
if (!dock.shouldReserveDockSpace)
|
||||
return -1;
|
||||
if (dock.frameDockExclusionActive)
|
||||
return -1;
|
||||
return dock.dockReserveZone;
|
||||
}
|
||||
|
||||
property real animationHeadroom: Math.ceil(SettingsData.dockIconSize * 0.35)
|
||||
|
||||
readonly property real surfaceImplicitWidth: isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
readonly property real surfaceImplicitHeight: !isVertical ? (px(dockGeometry.surfaceThickness + SettingsData.dockIconSize * 0.3) + animationHeadroom) : 0
|
||||
|
||||
readonly property real blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
||||
readonly property real blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
||||
readonly property real blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0
|
||||
readonly property real blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0
|
||||
readonly property real blurRadius: dock.usesConnectedFrameChrome ? Theme.connectedCornerRadius : dock.surfaceRadius
|
||||
|
||||
Item {
|
||||
id: maskItem
|
||||
visible: false
|
||||
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: {
|
||||
if (chrome) {
|
||||
const baseX = dockCore.x + dockMouseArea.x;
|
||||
if (isVertical && atEndEdge)
|
||||
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: {
|
||||
if (chrome) {
|
||||
const baseY = dockCore.y + dockMouseArea.y;
|
||||
if (!isVertical && atEndEdge)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
readonly property alias inputMaskItem: maskItem
|
||||
|
||||
property var hoveredButton: {
|
||||
if (!dockApps.children[0]) {
|
||||
return null;
|
||||
}
|
||||
const layoutItem = dockApps.children[0];
|
||||
const flowLayout = layoutItem.children[0];
|
||||
let repeater = null;
|
||||
for (var i = 0; i < flowLayout.children.length; i++) {
|
||||
const child = flowLayout.children[i];
|
||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
||||
repeater = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!repeater || !repeater.itemAt) {
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||
return item.dockButton;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
DankTooltip {
|
||||
id: dockTooltip
|
||||
targetScreen: dock.screen
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: tooltipRevealDelay
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: dock.showTooltipForHoveredButton()
|
||||
}
|
||||
|
||||
function showTooltipForHoveredButton() {
|
||||
dockTooltip.hide();
|
||||
if (!dock.hoveredButton || !dock.reveal || slideXAnimation.running || slideYAnimation.running)
|
||||
return;
|
||||
|
||||
const buttonLocalPos = dock.hoveredButton.mapToItem(null, 0, 0);
|
||||
const tooltipText = dock.hoveredButton.tooltipText || "";
|
||||
if (!tooltipText)
|
||||
return;
|
||||
|
||||
const screenHeight = dock.screen ? dock.screen.height : 0;
|
||||
|
||||
const gap = Theme.spacingS;
|
||||
const bgMargin = dockGeometry.bodyEdgeMargin;
|
||||
const btnW = dock.hoveredButton.width;
|
||||
const btnH = dock.hoveredButton.height;
|
||||
|
||||
if (!dock.isVertical) {
|
||||
const isBottom = SettingsData.dockPosition === SettingsData.Position.Bottom;
|
||||
const tooltipX = buttonLocalPos.x + btnW / 2 + adjacentLeftBarWidth;
|
||||
const tooltipHeight = 32;
|
||||
const totalFromEdge = bgMargin + dockBackground.height + dock.borderThickness + gap;
|
||||
const screenRelativeY = isBottom ? (screenHeight - totalFromEdge - tooltipHeight) : totalFromEdge;
|
||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isLeft = SettingsData.dockPosition === SettingsData.Position.Left;
|
||||
const screenWidth = dock.screen ? dock.screen.width : 0;
|
||||
const totalFromEdge = bgMargin + dockBackground.width + dock.borderThickness + gap;
|
||||
const tooltipX = isLeft ? totalFromEdge : (screenWidth - totalFromEdge);
|
||||
const screenRelativeY = buttonLocalPos.y + btnH / 2 + adjacentTopBarHeight;
|
||||
dockTooltip.show(tooltipText, tooltipX, screenRelativeY, dock.screen, isLeft, !isLeft);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: dock
|
||||
function onRevealChanged() {
|
||||
if (!dock.reveal) {
|
||||
tooltipRevealDelay.stop();
|
||||
dockTooltip.hide();
|
||||
} else {
|
||||
tooltipRevealDelay.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function onHoveredButtonChanged() {
|
||||
dock.showTooltipForHoveredButton();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockCore
|
||||
anchors.fill: parent
|
||||
x: isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? animationHeadroom : 0
|
||||
y: !isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? animationHeadroom : 0
|
||||
|
||||
Connections {
|
||||
target: dockMouseArea
|
||||
function onContainsMouseChanged() {
|
||||
if (dockMouseArea.containsMouse) {
|
||||
dock.revealSticky = true;
|
||||
revealHold.stop();
|
||||
} else {
|
||||
if (dock.autoHide && !dock.contextMenuOpen) {
|
||||
revealHold.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
property real currentScreen: modelData ? modelData : dock.screen
|
||||
property real screenWidth: currentScreen ? currentScreen.geometry.width : 1920
|
||||
property real screenHeight: currentScreen ? currentScreen.geometry.height : 1080
|
||||
property real maxDockWidth: screenWidth * 0.98
|
||||
property real maxDockHeight: screenHeight * 0.98
|
||||
|
||||
height: {
|
||||
if (dock.isVertical) {
|
||||
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(Math.max(dockBackground.height + 64, 200), maxDockHeight);
|
||||
}
|
||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
||||
}
|
||||
width: {
|
||||
if (dock.isVertical) {
|
||||
return dock.reveal ? px(dockGeometry.motionThickness) : 1;
|
||||
}
|
||||
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
|
||||
}
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? undefined : parent.top) : undefined
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.left) : undefined
|
||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
}
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
opacity: dock.startupRevealDone ? 1 : 0
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
x: {
|
||||
if (!dock.isVertical)
|
||||
return 0;
|
||||
if (dock.reveal)
|
||||
return 0;
|
||||
if (dock.usesConnectedFrameChrome) {
|
||||
const retractDist = dockBackground.width + SettingsData.dockSpacing + 10;
|
||||
return SettingsData.dockPosition === SettingsData.Position.Right ? retractDist : -retractDist;
|
||||
}
|
||||
const hideDistance = dockGeometry.motionThickness + 10;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right) {
|
||||
return hideDistance;
|
||||
} else {
|
||||
return -hideDistance;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
if (dock.isVertical)
|
||||
return 0;
|
||||
if (dock.reveal)
|
||||
return 0;
|
||||
if (dock.usesConnectedFrameChrome) {
|
||||
const retractDist = dockBackground.height + SettingsData.dockSpacing + 10;
|
||||
return SettingsData.dockPosition === SettingsData.Position.Bottom ? retractDist : -retractDist;
|
||||
}
|
||||
const hideDistance = dockGeometry.motionThickness + 10;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom) {
|
||||
return hideDistance;
|
||||
} else {
|
||||
return -hideDistance;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
id: slideXAnimation
|
||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
||||
onRunningChanged: if (!running)
|
||||
dock._syncDockChromeState()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
id: slideYAnimation
|
||||
duration: dock.usesConnectedFrameChrome ? Theme.variantDuration(Theme.popoutAnimationDuration, dock.reveal) : Theme.shortDuration
|
||||
easing.type: dock.usesConnectedFrameChrome ? Easing.BezierSpline : Easing.OutCubic
|
||||
easing.bezierCurve: dock.usesConnectedFrameChrome ? (dock.reveal ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve) : []
|
||||
onRunningChanged: if (!running)
|
||||
dock._syncDockChromeState()
|
||||
}
|
||||
}
|
||||
|
||||
onXChanged: dock._queueSlideSync()
|
||||
onYChanged: dock._queueSlideSync()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockBackground
|
||||
objectName: "dockBackground"
|
||||
visible: dock.hasApps
|
||||
anchors {
|
||||
top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? parent.top : undefined) : undefined
|
||||
bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? parent.bottom : undefined) : undefined
|
||||
horizontalCenter: !dock.isVertical ? parent.horizontalCenter : undefined
|
||||
left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? parent.left : undefined) : undefined
|
||||
right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? parent.right : undefined) : undefined
|
||||
verticalCenter: dock.isVertical ? parent.verticalCenter : undefined
|
||||
}
|
||||
anchors.topMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Top ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.bottomMargin: !dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.leftMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Left ? dockGeometry.bodyEdgeMargin : 0
|
||||
anchors.rightMargin: dock.isVertical && SettingsData.dockPosition === SettingsData.Position.Right ? dockGeometry.bodyEdgeMargin : 0
|
||||
|
||||
implicitWidth: dock.isVertical ? (dockApps.implicitHeight + SettingsData.dockSpacing * 2) : (dockApps.implicitWidth + SettingsData.dockSpacing * 2)
|
||||
implicitHeight: dock.isVertical ? (dockApps.implicitWidth + SettingsData.dockSpacing * 2) : (dockApps.implicitHeight + SettingsData.dockSpacing * 2)
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// Avoid an offscreen texture seam where the connected dock meets the frame.
|
||||
layer.enabled: !usesConnectedFrameChrome
|
||||
clip: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
||||
color: dock.surfaceColor
|
||||
topLeftRadius: dock.surfaceTopLeftRadius
|
||||
topRightRadius: dock.surfaceTopRightRadius
|
||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !usesConnectedFrameChrome && (!FrameTransitionState.effectiveConnectedFrameModeActive || dock.reveal)
|
||||
color: "transparent"
|
||||
topLeftRadius: dock.surfaceTopLeftRadius
|
||||
topRightRadius: dock.surfaceTopRightRadius
|
||||
bottomLeftRadius: dock.surfaceBottomLeftRadius
|
||||
bottomRightRadius: dock.surfaceBottomRightRadius
|
||||
border.color: dock.surfaceBorderColor
|
||||
border.width: dock.surfaceBorderWidth
|
||||
z: 100
|
||||
}
|
||||
|
||||
// Sync dockBackground geometry to ConnectedModeState
|
||||
onXChanged: dock._syncDockChromeState()
|
||||
onYChanged: dock._syncDockChromeState()
|
||||
onWidthChanged: dock._syncDockChromeState()
|
||||
onHeightChanged: dock._syncDockChromeState()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dockConnectedChrome
|
||||
visible: Theme.isConnectedEffect && dock.reveal && dock.hasApps && !FrameTransitionState.effectiveConnectedFrameModeActive
|
||||
readonly property real extraLeft: dock.isVertical ? 0 : Theme.connectedCornerRadius
|
||||
readonly property real extraTop: dock.isVertical ? Theme.connectedCornerRadius : 0
|
||||
readonly property real bodyRadius: dock.surfaceRadius
|
||||
readonly property bool barTop: dock.connectedBarSide === "top"
|
||||
readonly property bool barBottom: dock.connectedBarSide === "bottom"
|
||||
readonly property bool barLeft: dock.connectedBarSide === "left"
|
||||
readonly property bool barRight: dock.connectedBarSide === "right"
|
||||
|
||||
x: dockBackground.x - extraLeft
|
||||
y: dockBackground.y - extraTop
|
||||
width: dockBackground.width + extraLeft * 2
|
||||
height: dockBackground.height + extraTop * 2
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_chrome.frag.qsb")
|
||||
|
||||
property real widthPx: width
|
||||
property real heightPx: height
|
||||
property vector4d surfaceColor: Qt.vector4d(dock.surfaceColor.r, dock.surfaceColor.g, dock.surfaceColor.b, dock.surfaceColor.a)
|
||||
property vector4d shadowColor: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d shadowParam: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d ambientParam: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector4d bodyRect: Qt.vector4d(dockConnectedChrome.extraLeft, dockConnectedChrome.extraTop, dockBackground.width, dockBackground.height)
|
||||
property vector4d cornerRadius: Qt.vector4d(dockConnectedChrome.barTop || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barTop || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barRight ? 0 : dockConnectedChrome.bodyRadius, dockConnectedChrome.barBottom || dockConnectedChrome.barLeft ? 0 : dockConnectedChrome.bodyRadius)
|
||||
property vector4d edgeParam: Qt.vector4d(dockConnectedChrome.barTop ? 0 : (dockConnectedChrome.barBottom ? 1 : (dockConnectedChrome.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: dockBorderShape
|
||||
x: dockBackground.x - borderThickness
|
||||
y: dockBackground.y - borderThickness
|
||||
width: dockBackground.width + borderThickness * 2
|
||||
height: dockBackground.height + borderThickness * 2
|
||||
visible: SettingsData.dockBorderEnabled && dock.hasApps && !usesConnectedFrameChrome
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
readonly property real borderThickness: Math.max(1, dock.borderThickness)
|
||||
readonly property real i: borderThickness / 2
|
||||
readonly property real cr: dock.surfaceRadius
|
||||
readonly property real w: dockBackground.width
|
||||
readonly property real h: dockBackground.height
|
||||
|
||||
readonly property color borderColor: {
|
||||
const opacity = SettingsData.dockBorderOpacity;
|
||||
switch (SettingsData.dockBorderColor) {
|
||||
case "secondary":
|
||||
return Theme.withAlpha(Theme.secondary, opacity);
|
||||
case "primary":
|
||||
return Theme.withAlpha(Theme.primary, opacity);
|
||||
default:
|
||||
return Theme.withAlpha(Theme.surfaceText, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: dockBorderShape.borderColor
|
||||
strokeWidth: dockBorderShape.borderThickness
|
||||
joinStyle: ShapePath.RoundJoin
|
||||
capStyle: ShapePath.FlatCap
|
||||
|
||||
PathSvg {
|
||||
path: {
|
||||
const bt = dockBorderShape.borderThickness;
|
||||
const i = dockBorderShape.i;
|
||||
const cr = dockBorderShape.cr + bt - i;
|
||||
const w = dockBorderShape.w;
|
||||
const h = dockBorderShape.h;
|
||||
|
||||
let d = `M ${i + cr} ${i}`;
|
||||
d += ` L ${i + w + 2 * (bt - i) - cr} ${i}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i)} ${i + cr}`;
|
||||
d += ` L ${i + w + 2 * (bt - i)} ${i + h + 2 * (bt - i) - cr}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + w + 2 * (bt - i) - cr} ${i + h + 2 * (bt - i)}`;
|
||||
d += ` L ${i + cr} ${i + h + 2 * (bt - i)}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i} ${i + h + 2 * (bt - i) - cr}`;
|
||||
d += ` L ${i} ${i + cr}`;
|
||||
if (cr > 0)
|
||||
d += ` A ${cr} ${cr} 0 0 1 ${i + cr} ${i}`;
|
||||
d += " Z";
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DockApps {
|
||||
id: dockApps
|
||||
|
||||
anchors.top: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Top ? dockBackground.top : undefined) : undefined
|
||||
anchors.bottom: !dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Bottom ? dockBackground.bottom : undefined) : undefined
|
||||
anchors.horizontalCenter: !dock.isVertical ? dockBackground.horizontalCenter : undefined
|
||||
anchors.left: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Left ? dockBackground.left : undefined) : undefined
|
||||
anchors.right: dock.isVertical ? (SettingsData.dockPosition === SettingsData.Position.Right ? dockBackground.right : undefined) : undefined
|
||||
anchors.verticalCenter: dock.isVertical ? dockBackground.verticalCenter : undefined
|
||||
anchors.topMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.bottomMargin: !dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.leftMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
anchors.rightMargin: dock.isVertical ? SettingsData.dockSpacing : 0
|
||||
|
||||
contextMenu: dock.contextMenu
|
||||
trashContextMenu: dock.trashContextMenu
|
||||
groupByApp: dock.groupByApp
|
||||
isVertical: dock.isVertical
|
||||
dockScreen: dock.screen
|
||||
iconSize: dock.widgetHeight
|
||||
usesOverlayLayer: dock.usesOverlayLayer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ PanelWindow {
|
||||
const actualDockHeight = dockBackground ? dockBackground.height : root.dockVisibleHeight;
|
||||
const actualDockWidth = dockBackground ? dockBackground.width : dockWindow.width;
|
||||
const dockMargin = SettingsData.dockMargin + 16;
|
||||
|
||||
// Connected mode hosts the dock inside the fullscreen frame surface, inset from the
|
||||
// screen edge. Anchor off the dock body's real position instead of the screen edge.
|
||||
const dockBgPos = dockBackground ? dockBackground.mapToItem(dockWindow.contentItem, 0, 0) : null;
|
||||
const frameHosted = dockBgPos && dockWindow.width >= screen.width - 1 && dockWindow.height >= screen.height - 1;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
@@ -68,14 +73,14 @@ PanelWindow {
|
||||
case SettingsData.Position.Left:
|
||||
{
|
||||
const dockTopMargin = Math.round((screen.height - dockWindow.height) / 2);
|
||||
x = actualDockWidth + dockMargin + 20;
|
||||
x = frameHosted ? (dockBgPos.x + actualDockWidth + dockMargin + 20) : (actualDockWidth + dockMargin + 20);
|
||||
y = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2;
|
||||
break;
|
||||
}
|
||||
case SettingsData.Position.Right:
|
||||
{
|
||||
const dockTopMargin = Math.round((screen.height - dockWindow.height) / 2);
|
||||
x = screen.width - actualDockWidth - dockMargin - 20;
|
||||
x = frameHosted ? (dockBgPos.x - dockMargin - 20) : (screen.width - actualDockWidth - dockMargin - 20);
|
||||
y = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2;
|
||||
break;
|
||||
}
|
||||
@@ -83,7 +88,7 @@ PanelWindow {
|
||||
{
|
||||
const dockLeftMargin = Math.round((screen.width - dockWindow.width) / 2);
|
||||
x = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2;
|
||||
y = actualDockHeight + dockMargin + 20;
|
||||
y = frameHosted ? (dockBgPos.y + actualDockHeight + dockMargin + 20) : (actualDockHeight + dockMargin + 20);
|
||||
break;
|
||||
}
|
||||
case SettingsData.Position.Bottom:
|
||||
@@ -91,7 +96,7 @@ PanelWindow {
|
||||
{
|
||||
const dockLeftMargin = Math.round((screen.width - dockWindow.width) / 2);
|
||||
x = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2;
|
||||
y = screen.height - actualDockHeight - dockMargin - 20;
|
||||
y = frameHosted ? (dockBgPos.y - dockMargin - 20) : (screen.height - actualDockHeight - dockMargin - 20);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.DankBar
|
||||
import qs.Services
|
||||
|
||||
// Renders the bar(s) inside the frame surface in connected mode: one DankBarBody per
|
||||
// active bar edge, positioned in the frame's cutout band. Reuses the existing DankBar
|
||||
// item (per bar config) as rootWindow so colour-picker, overview loader and widget
|
||||
// models are shared with the standalone path.
|
||||
Item {
|
||||
id: host
|
||||
|
||||
required property var frameWindow
|
||||
required property var targetScreen
|
||||
|
||||
readonly property string screenName: targetScreen ? targetScreen.name : ""
|
||||
|
||||
readonly property var barSlots: {
|
||||
SettingsData.barConfigs;
|
||||
const out = [];
|
||||
const configs = SettingsData.barConfigs || [];
|
||||
for (let i = 0; i < configs.length; i++) {
|
||||
const bc = configs[i];
|
||||
if (!bc.enabled || (bc.useOverlayLayer ?? false))
|
||||
continue;
|
||||
const prefs = bc.screenPreferences || ["all"];
|
||||
if (!prefs.includes("all") && !SettingsData.isScreenInPreferences(host.targetScreen, prefs))
|
||||
continue;
|
||||
let edge = "top";
|
||||
switch (bc.position ?? 0) {
|
||||
case SettingsData.Position.Bottom:
|
||||
edge = "bottom";
|
||||
break;
|
||||
case SettingsData.Position.Left:
|
||||
edge = "left";
|
||||
break;
|
||||
case SettingsData.Position.Right:
|
||||
edge = "right";
|
||||
break;
|
||||
}
|
||||
out.push({
|
||||
"barId": bc.id,
|
||||
"edge": edge
|
||||
});
|
||||
}
|
||||
// Horizontal bars own the corners, so render them last (on top) — their edge widget
|
||||
// must receive the corner click over the vertical bar's hover area beneath it.
|
||||
out.sort((a, b) => {
|
||||
const rank = e => (e === "left" || e === "right") ? 0 : 1;
|
||||
return rank(a.edge) - rank(b.edge);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: host.barSlots
|
||||
|
||||
delegate: Item {
|
||||
id: slot
|
||||
|
||||
required property var modelData
|
||||
|
||||
readonly property string edge: modelData.edge
|
||||
readonly property var dankBarItem: BarWidgetService.dankBarItems[modelData.barId] ?? null
|
||||
readonly property var slotBarConfig: dankBarItem?.barConfig ?? SettingsData.getBarConfig(modelData.barId)
|
||||
|
||||
// Each bar spans its full edge (matching the standalone window extent) so
|
||||
// DankBarContent's own adjacency margins inset it, rather than double-insetting
|
||||
// against a pre-carved strip.
|
||||
x: edge === "right" ? host.frameWindow._windowRegionWidth - host.frameWindow.cutoutRightInset : 0
|
||||
y: edge === "bottom" ? host.frameWindow._windowRegionHeight - host.frameWindow.cutoutBottomInset : 0
|
||||
width: {
|
||||
switch (edge) {
|
||||
case "left":
|
||||
return host.frameWindow.cutoutLeftInset;
|
||||
case "right":
|
||||
return host.frameWindow.cutoutRightInset;
|
||||
default:
|
||||
return host.frameWindow._windowRegionWidth;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
switch (edge) {
|
||||
case "top":
|
||||
return host.frameWindow.cutoutTopInset;
|
||||
case "bottom":
|
||||
return host.frameWindow.cutoutBottomInset;
|
||||
default:
|
||||
return host.frameWindow._windowRegionHeight;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: slot.dankBarItem !== null && slot.slotBarConfig !== null
|
||||
|
||||
sourceComponent: DankBarBody {
|
||||
hostWindow: host.frameWindow
|
||||
modelData: host.targetScreen
|
||||
rootWindow: slot.dankBarItem
|
||||
barConfig: slot.slotBarConfig
|
||||
leftWidgetsModel: slot.dankBarItem?.leftWidgetsModel ?? null
|
||||
centerWidgetsModel: slot.dankBarItem?.centerWidgetsModel ?? null
|
||||
rightWidgetsModel: slot.dankBarItem?.rightWidgetsModel ?? null
|
||||
|
||||
Component.onCompleted: BarWidgetService.registerFrameBar(host.screenName, this)
|
||||
Component.onDestruction: BarWidgetService.unregisterFrameBar(host.screenName, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Dock
|
||||
import qs.Services
|
||||
|
||||
// Renders the dock inside the frame surface in connected mode: a single DockBody
|
||||
// positioned by SettingsData.dockPosition. It fills the frame and positions its own
|
||||
// content via its internal anchors/geometry using hostWindow.
|
||||
Item {
|
||||
id: host
|
||||
|
||||
required property var frameWindow
|
||||
required property var targetScreen
|
||||
|
||||
readonly property string screenName: targetScreen ? targetScreen.name : ""
|
||||
|
||||
// Exposed so FrameWindow can add the dock's hover strip to its input region.
|
||||
readonly property alias dockMaskItem: dockBody.inputMaskItem
|
||||
|
||||
DockBody {
|
||||
id: dockBody
|
||||
anchors.fill: parent
|
||||
hostWindow: host.frameWindow
|
||||
modelData: host.targetScreen
|
||||
contextMenu: BarWidgetService.dockContextMenu
|
||||
trashContextMenu: BarWidgetService.dockTrashContextMenu
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,33 @@ Scope {
|
||||
return SettingsData.getActiveBarEdgesForScreen(screen);
|
||||
}
|
||||
|
||||
// One thin invisible PanelWindow per edge.
|
||||
// Skips any edge where a bar already provides its own exclusiveZone.
|
||||
// Overlay-layer bars stay standalone even in connected mode and reserve their own edge.
|
||||
readonly property var overlayBarEdges: {
|
||||
SettingsData.barConfigs;
|
||||
return SettingsData.getOverlayBarEdgesForScreen(root.screen);
|
||||
}
|
||||
|
||||
// One thin invisible PanelWindow per edge. A standalone bar reserves its own edge, so
|
||||
// that edge is skipped — unless the frame hosts the bar (connected mode), where no bar
|
||||
// window exists and the exclusion must reserve the full bar thickness itself.
|
||||
|
||||
readonly property bool screenEnabled: CompositorService.frameWindowVisibleForScreen(root.screen)
|
||||
readonly property bool hostsBar: CompositorService.frameHostsSurfacesForScreen(root.screen)
|
||||
|
||||
function hostsBandForEdge(edge) {
|
||||
return root.hostsBar && !root.overlayBarEdges.includes(edge);
|
||||
}
|
||||
|
||||
function exclusionSizeForEdge(edge) {
|
||||
return root.barEdges.includes(edge) ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.screenEnabled && !root.barEdges.includes("top")
|
||||
readonly property bool isBarEdge: root.barEdges.includes("top")
|
||||
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("top"))
|
||||
sourceComponent: EdgeExclusion {
|
||||
targetScreen: root.screen
|
||||
exclusionSize: root.exclusionSizeForEdge("top")
|
||||
anchorTop: true
|
||||
anchorLeft: true
|
||||
anchorRight: true
|
||||
@@ -32,9 +50,11 @@ Scope {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.screenEnabled && !root.barEdges.includes("bottom")
|
||||
readonly property bool isBarEdge: root.barEdges.includes("bottom")
|
||||
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("bottom"))
|
||||
sourceComponent: EdgeExclusion {
|
||||
targetScreen: root.screen
|
||||
exclusionSize: root.exclusionSizeForEdge("bottom")
|
||||
anchorBottom: true
|
||||
anchorLeft: true
|
||||
anchorRight: true
|
||||
@@ -42,9 +62,11 @@ Scope {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.screenEnabled && !root.barEdges.includes("left")
|
||||
readonly property bool isBarEdge: root.barEdges.includes("left")
|
||||
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("left"))
|
||||
sourceComponent: EdgeExclusion {
|
||||
targetScreen: root.screen
|
||||
exclusionSize: root.exclusionSizeForEdge("left")
|
||||
anchorLeft: true
|
||||
anchorTop: true
|
||||
anchorBottom: true
|
||||
@@ -52,9 +74,11 @@ Scope {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.screenEnabled && !root.barEdges.includes("right")
|
||||
readonly property bool isBarEdge: root.barEdges.includes("right")
|
||||
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("right"))
|
||||
sourceComponent: EdgeExclusion {
|
||||
targetScreen: root.screen
|
||||
exclusionSize: root.exclusionSizeForEdge("right")
|
||||
anchorRight: true
|
||||
anchorTop: true
|
||||
anchorBottom: true
|
||||
@@ -63,6 +87,7 @@ Scope {
|
||||
|
||||
component EdgeExclusion: PanelWindow {
|
||||
required property var targetScreen
|
||||
property real exclusionSize: SettingsData.frameThickness
|
||||
|
||||
screen: targetScreen
|
||||
property bool anchorTop: false
|
||||
@@ -72,7 +97,7 @@ Scope {
|
||||
|
||||
WlrLayershell.namespace: "dms:frame-exclusion"
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
exclusiveZone: SettingsData.frameThickness
|
||||
exclusiveZone: exclusionSize
|
||||
color: "transparent"
|
||||
mask: Region {}
|
||||
implicitWidth: 1
|
||||
|
||||
@@ -32,7 +32,41 @@ PanelWindow {
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
mask: Region {}
|
||||
// Click-through everywhere except the bar strips it hosts in connected mode.
|
||||
mask: Region {
|
||||
Region {
|
||||
readonly property bool present: win._connectedActive && win.barEdges.includes("top")
|
||||
x: 0
|
||||
y: 0
|
||||
width: present ? win._windowRegionWidth : 0
|
||||
height: present ? win.cutoutTopInset : 0
|
||||
}
|
||||
Region {
|
||||
readonly property bool present: win._connectedActive && win.barEdges.includes("bottom")
|
||||
x: 0
|
||||
y: present ? win._windowRegionHeight - win.cutoutBottomInset : 0
|
||||
width: present ? win._windowRegionWidth : 0
|
||||
height: present ? win.cutoutBottomInset : 0
|
||||
}
|
||||
Region {
|
||||
readonly property bool present: win._connectedActive && win.barEdges.includes("left")
|
||||
x: 0
|
||||
y: 0
|
||||
width: present ? win.cutoutLeftInset : 0
|
||||
height: present ? win._windowRegionHeight : 0
|
||||
}
|
||||
Region {
|
||||
readonly property bool present: win._connectedActive && win.barEdges.includes("right")
|
||||
x: present ? win._windowRegionWidth - win.cutoutRightInset : 0
|
||||
y: 0
|
||||
width: present ? win.cutoutRightInset : 0
|
||||
height: present ? win._windowRegionHeight : 0
|
||||
}
|
||||
// The frame-hosted dock's hover strip, so hover-reveal works through the frame surface.
|
||||
Region {
|
||||
item: frameDockHostLoader.item ? frameDockHostLoader.item.dockMaskItem : null
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var barEdges: {
|
||||
SettingsData.barConfigs;
|
||||
@@ -51,6 +85,16 @@ PanelWindow {
|
||||
readonly property var _modalDescriptor: ConnectedModeState.surfaceDescriptor(win._screenName, "modal")
|
||||
|
||||
readonly property bool _connectedActive: CompositorService.usesConnectedFrameChromeForScreen(win.targetScreen)
|
||||
readonly property bool _dockHostedHere: {
|
||||
if (!win._connectedActive || !SettingsData.showDock || SettingsData.dockUseOverlayLayer)
|
||||
return false;
|
||||
const screens = SettingsData.getFilteredScreens("dock");
|
||||
for (let i = 0; i < screens.length; i++) {
|
||||
if (screens[i] && screens[i].name === win._screenName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
readonly property string _barSide: {
|
||||
const edges = win.barEdges;
|
||||
if (edges.includes("top"))
|
||||
@@ -191,6 +235,33 @@ PanelWindow {
|
||||
return Math.max(0, Math.round(Theme.px(value, win._dpr)));
|
||||
}
|
||||
|
||||
function _pointInBand(lx, ly, bx, by, bw, bh, pad) {
|
||||
return lx >= bx - pad && lx < bx + bw + pad && ly >= by - pad && ly < by + bh + pad;
|
||||
}
|
||||
|
||||
function containsGlobalPoint(gx, gy, padding) {
|
||||
if (!win._connectedActive || !win.contentItem)
|
||||
return false;
|
||||
const origin = win.contentItem.mapToItem(null, 0, 0);
|
||||
if (!origin)
|
||||
return false;
|
||||
const pad = padding !== undefined ? padding : 16;
|
||||
const lx = gx - origin.x;
|
||||
const ly = gy - origin.y;
|
||||
const w = win._windowRegionWidth;
|
||||
const h = win._windowRegionHeight;
|
||||
const edges = win.barEdges;
|
||||
if (edges.includes("top") && win._pointInBand(lx, ly, 0, 0, w, win.cutoutTopInset, pad))
|
||||
return true;
|
||||
if (edges.includes("bottom") && win._pointInBand(lx, ly, 0, h - win.cutoutBottomInset, w, win.cutoutBottomInset, pad))
|
||||
return true;
|
||||
if (edges.includes("left") && win._pointInBand(lx, ly, 0, 0, win.cutoutLeftInset, h, pad))
|
||||
return true;
|
||||
if (edges.includes("right") && win._pointInBand(lx, ly, w - win.cutoutRightInset, 0, win.cutoutRightInset, h, pad))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property int cutoutTopInset: win._regionInt(barEdges.includes("top") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutBottomInset: win._regionInt(barEdges.includes("bottom") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutLeftInset: win._regionInt(barEdges.includes("left") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
@@ -1049,10 +1120,12 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
KeyboardFocus.registerBarWindow(win);
|
||||
win._scheduleBlurRebuild();
|
||||
win._scheduleSurfaceRefresh();
|
||||
}
|
||||
Component.onDestruction: {
|
||||
KeyboardFocus.unregisterBarWindow(win);
|
||||
blurRebuildAction.cancel();
|
||||
surfaceRefreshAction.cancel();
|
||||
win._teardownBlur();
|
||||
@@ -1101,4 +1174,25 @@ PanelWindow {
|
||||
property vector4d chromeK3: win._sdfSlots[3].k
|
||||
property vector4d chromeParam3: win._sdfSlots[3].param
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
active: win._connectedActive
|
||||
sourceComponent: FrameBarHost {
|
||||
frameWindow: win
|
||||
targetScreen: win.targetScreen
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: frameDockHostLoader
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
active: win._dockHostedHere
|
||||
sourceComponent: FrameDockHost {
|
||||
frameWindow: win
|
||||
targetScreen: win.targetScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool active: false
|
||||
property bool _completed: false
|
||||
|
||||
signal fadeCompleted
|
||||
signal fadeCancelled
|
||||
@@ -36,7 +37,8 @@ PanelWindow {
|
||||
opacity: 0
|
||||
|
||||
onOpacityChanged: {
|
||||
if (opacity >= 0.99 && root.active) {
|
||||
if (opacity >= 0.99 && root.active && !root._completed) {
|
||||
root._completed = true;
|
||||
root.fadeCompleted();
|
||||
}
|
||||
}
|
||||
@@ -59,6 +61,7 @@ PanelWindow {
|
||||
function startFade() {
|
||||
if (!SettingsData.fadeToDpmsEnabled)
|
||||
return;
|
||||
_completed = false;
|
||||
active = true;
|
||||
fadeOverlay.opacity = 0.0;
|
||||
fadeSeq.stop();
|
||||
@@ -74,6 +77,7 @@ PanelWindow {
|
||||
fadeSeq.stop();
|
||||
fadeOverlay.opacity = 0.0;
|
||||
active = false;
|
||||
_completed = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -113,6 +113,8 @@ Item {
|
||||
}
|
||||
|
||||
function sendLockerReadyOnce() {
|
||||
if (root.demoMode)
|
||||
return;
|
||||
if (lockerReadySent)
|
||||
return;
|
||||
if (root.unlocking)
|
||||
|
||||
@@ -45,6 +45,14 @@ PanelWindow {
|
||||
blurWidth: !win._finalized && !win.connectedFrameMode ? innerW * s : 0
|
||||
blurHeight: !win._finalized && !win.connectedFrameMode ? innerH * s : 0
|
||||
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"
|
||||
|
||||
@@ -42,6 +42,11 @@ DankOSD {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 40
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
@@ -125,6 +130,11 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
property int gap: Theme.spacingS
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
|
||||
@@ -81,6 +81,11 @@ DankOSD {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 40
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
@@ -147,6 +152,11 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
property int gap: Theme.spacingS
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
|
||||
@@ -65,6 +65,11 @@ DankOSD {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 40
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
@@ -137,6 +142,11 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
property int gap: Theme.spacingS
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
|
||||
@@ -60,6 +60,11 @@ DankOSD {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 40
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
@@ -132,6 +137,11 @@ DankOSD {
|
||||
anchors.fill: parent
|
||||
property int gap: Theme.spacingS
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
|
||||
@@ -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 {
|
||||
width: parent.width
|
||||
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
|
||||
|
||||
@@ -1256,6 +1256,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleCard {
|
||||
settingKey: "hoverPopouts"
|
||||
tags: ["bar", "hover", "popout", "reveal", "widget", "delay"]
|
||||
iconName: "touch_app"
|
||||
title: I18n.tr("Hover Popouts")
|
||||
description: I18n.tr("Open widget popouts by hovering over the bar. Moving to another widget switches the popout.")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -12,6 +12,50 @@ Singleton {
|
||||
property var widgetRegistry: ({})
|
||||
property var dankBarRepeater: null
|
||||
|
||||
// Bars rendered inside the frame surface (connected mode) have no DankBarWindow in the
|
||||
// repeater, so they self-register here (screenName -> DankBarBody) for trigger routing.
|
||||
property var frameHostedBars: ({})
|
||||
|
||||
// Shared dock context-menu windows (created in DMSShell) so a frame-hosted dock can reach them.
|
||||
property var dockContextMenu: null
|
||||
property var dockTrashContextMenu: null
|
||||
|
||||
function registerFrameBar(screenName, body) {
|
||||
if (!screenName || !body)
|
||||
return;
|
||||
const next = Object.assign({}, frameHostedBars);
|
||||
next[screenName] = body;
|
||||
frameHostedBars = next;
|
||||
}
|
||||
|
||||
function unregisterFrameBar(screenName, body) {
|
||||
if (!screenName || frameHostedBars[screenName] !== body)
|
||||
return;
|
||||
const next = Object.assign({}, frameHostedBars);
|
||||
delete next[screenName];
|
||||
frameHostedBars = next;
|
||||
}
|
||||
|
||||
// DankBar items self-register here (barConfig id -> DankBar) so frame-hosted bars can
|
||||
// resolve their models/rootWindow reactively, independent of repeater load ordering.
|
||||
property var dankBarItems: ({})
|
||||
|
||||
function registerDankBarItem(barId, item) {
|
||||
if (!barId || !item)
|
||||
return;
|
||||
const next = Object.assign({}, dankBarItems);
|
||||
next[barId] = item;
|
||||
dankBarItems = next;
|
||||
}
|
||||
|
||||
function unregisterDankBarItem(barId, item) {
|
||||
if (!barId || dankBarItems[barId] !== item)
|
||||
return;
|
||||
const next = Object.assign({}, dankBarItems);
|
||||
delete next[barId];
|
||||
dankBarItems = next;
|
||||
}
|
||||
|
||||
signal widgetRegistered(string widgetId, string screenName)
|
||||
signal widgetUnregistered(string widgetId, string screenName)
|
||||
|
||||
@@ -141,6 +185,9 @@ Singleton {
|
||||
}
|
||||
|
||||
function getBarWindowForScreen(screenName) {
|
||||
if (frameHostedBars[screenName])
|
||||
return frameHostedBars[screenName];
|
||||
|
||||
if (!dankBarRepeater)
|
||||
return null;
|
||||
|
||||
@@ -170,17 +217,18 @@ Singleton {
|
||||
}
|
||||
|
||||
function getFirstBarWindow() {
|
||||
if (!dankBarRepeater || dankBarRepeater.count === 0)
|
||||
return null;
|
||||
if (dankBarRepeater) {
|
||||
for (var i = 0; i < dankBarRepeater.count; i++) {
|
||||
const barItem = dankBarRepeater.itemAt(i)?.item;
|
||||
if (barItem?.barVariants?.instances?.length > 0)
|
||||
return barItem.barVariants.instances[0];
|
||||
}
|
||||
}
|
||||
|
||||
const loader = dankBarRepeater.itemAt(0);
|
||||
if (!loader?.item)
|
||||
return null;
|
||||
|
||||
const barItem = loader.item;
|
||||
if (!barItem.barVariants?.instances || barItem.barVariants.instances.length === 0)
|
||||
return null;
|
||||
|
||||
return barItem.barVariants.instances[0];
|
||||
for (const screenName in frameHostedBars) {
|
||||
if (frameHostedBars[screenName])
|
||||
return frameHostedBars[screenName];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ Item {
|
||||
property string socketPath: ""
|
||||
readonly property bool socketFound: socketPath.length > 0
|
||||
property bool connected: false
|
||||
property bool binaryExists: false
|
||||
readonly property bool binaryExists: launchKind.length > 0
|
||||
property bool binaryChecked: false
|
||||
// "dcal" for a host install, "flatpak" for a sandboxed one, "" for neither.
|
||||
property string launchKind: ""
|
||||
|
||||
property var calendars: []
|
||||
property var events: []
|
||||
@@ -55,18 +57,20 @@ Item {
|
||||
|
||||
Process {
|
||||
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
|
||||
onExited: code => {
|
||||
root.binaryExists = (code === 0);
|
||||
root.binaryChecked = true;
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.launchKind = text.trim();
|
||||
root.binaryChecked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: discoverProcess
|
||||
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 {
|
||||
onStreamFinished: {
|
||||
@@ -98,9 +102,16 @@ Item {
|
||||
}
|
||||
|
||||
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;
|
||||
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
|
||||
}
|
||||
if (enabled && !connected)
|
||||
discoverProcess.running = true;
|
||||
}
|
||||
|
||||
@@ -533,43 +533,6 @@ Singleton {
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasFullscreenToplevelOnScreen(screenOrName) {
|
||||
const screenName = _screenName(screenOrName);
|
||||
if (!screenName)
|
||||
return false;
|
||||
|
||||
if (isNiri) {
|
||||
const active = ToplevelManager.activeToplevel;
|
||||
if (active?.fullscreen && active?.activated && _toplevelOnScreen(active, screenName))
|
||||
return true;
|
||||
|
||||
const filtered = filterCurrentWorkspace(sortedToplevels, screenName);
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
if (filtered[i]?.fullscreen)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isHyprland) {
|
||||
const filtered = filterCurrentWorkspace(sortedToplevels, screenName);
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
if (filtered[i]?.fullscreen)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ToplevelManager.toplevels?.values)
|
||||
return false;
|
||||
|
||||
for (const toplevel of ToplevelManager.toplevels.values) {
|
||||
if (toplevel?.fullscreen && _toplevelOnScreen(toplevel, screenName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _hyprlandToplevelMapped(hyprToplevel) {
|
||||
if (!hyprToplevel)
|
||||
return false;
|
||||
@@ -630,12 +593,6 @@ Singleton {
|
||||
// Per-screen cache for connectedFrameBlockedOnScreen to avoid recomputing on every consumer binding.
|
||||
property var frameBlockedByScreen: ({})
|
||||
|
||||
function _computeConnectedFrameBlocked(screenName) {
|
||||
if (hasFullscreenToplevelOnScreen(screenName))
|
||||
return true;
|
||||
return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenName);
|
||||
}
|
||||
|
||||
function _recomputeFrameBlocked() {
|
||||
const screens = Quickshell.screens || [];
|
||||
const next = {};
|
||||
@@ -644,7 +601,7 @@ Singleton {
|
||||
const name = screens[i]?.name;
|
||||
if (!name)
|
||||
continue;
|
||||
const blocked = _computeConnectedFrameBlocked(name);
|
||||
const blocked = hyprlandSpecialWorkspaceBlocksConnectedFrame(name);
|
||||
next[name] = blocked;
|
||||
if (frameBlockedByScreen[name] !== blocked)
|
||||
changed = true;
|
||||
@@ -668,7 +625,7 @@ Singleton {
|
||||
const cached = frameBlockedByScreen[screenName];
|
||||
if (cached !== undefined)
|
||||
return cached;
|
||||
return _computeConnectedFrameBlocked(screenName);
|
||||
return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenName);
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -730,6 +687,23 @@ Singleton {
|
||||
return FrameTransitionState.effectiveConnectedFrameModeActive && frameWindowVisibleForScreen(screenOrName);
|
||||
}
|
||||
|
||||
// Connected mode renders the bar inside the frame surface. True whenever connected
|
||||
// chrome is configured for the screen, independent of the fullscreen block, so the
|
||||
// standalone bar surface stays suppressed while the frame-hosted bar hides with the frame.
|
||||
function frameHostsSurfacesForScreen(screenOrName) {
|
||||
return FrameTransitionState.effectiveConnectedFrameModeActive && frameConfiguredForScreen(screenOrName);
|
||||
}
|
||||
|
||||
// A surface set to the overlay layer cannot live inside the frame's single top-layer window,
|
||||
// so it stays a standalone window (which resolves itself onto the overlay layer) even in connected mode.
|
||||
function frameHostsBarForConfig(screenOrName, barConfig) {
|
||||
return frameHostsSurfacesForScreen(screenOrName) && !(barConfig?.useOverlayLayer ?? false);
|
||||
}
|
||||
|
||||
function frameHostsDockForScreen(screenOrName) {
|
||||
return frameHostsSurfacesForScreen(screenOrName) && !SettingsData.dockUseOverlayLayer;
|
||||
}
|
||||
|
||||
function framePeerSurfacesUseOverlayForScreen(screenOrName) {
|
||||
return frameWindowVisibleForScreen(screenOrName);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ Singleton {
|
||||
return "wifi_1_bar";
|
||||
}
|
||||
|
||||
property string userPreference: "auto"
|
||||
readonly property string userPreference: SettingsData.networkPreference
|
||||
property bool isConnecting: false
|
||||
property string connectingSSID: ""
|
||||
property string connectionError: ""
|
||||
@@ -210,7 +210,6 @@ Singleton {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.userPreference = SettingsData.networkPreference;
|
||||
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
checkDMSCapabilities();
|
||||
@@ -732,7 +731,6 @@ Singleton {
|
||||
function setNetworkPreference(preference) {
|
||||
if (!networkAvailable)
|
||||
return;
|
||||
userPreference = preference;
|
||||
changingPreference = true;
|
||||
targetPreference = preference;
|
||||
SettingsData.set("networkPreference", preference);
|
||||
|
||||
@@ -335,6 +335,7 @@ Singleton {
|
||||
const data = response.result.data;
|
||||
|
||||
if (service === "server") {
|
||||
const prevCapabilities = capabilities;
|
||||
apiVersion = data.apiVersion || 0;
|
||||
cliVersion = data.cliVersion || "";
|
||||
capabilities = data.capabilities || [];
|
||||
@@ -346,6 +347,12 @@ Singleton {
|
||||
}
|
||||
|
||||
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") {
|
||||
networkStateUpdate(data);
|
||||
} else if (service === "network.credentials") {
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
// Canonical DankConnect IPC client. Targets daemon apiVersion 1.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// dankgo omits zero-valued response IDs, so the subscription handshake
|
||||
// must use a non-zero ID that can be matched in the response.
|
||||
readonly property int subscriptionRequestId: 1
|
||||
|
||||
property string socketPath: {
|
||||
const override = Quickshell.env("DANKCONNECT_SOCKET");
|
||||
if (override && override.length > 0)
|
||||
return override;
|
||||
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
|
||||
return runtimeDir ? runtimeDir + "/dankconnect.sock" : "";
|
||||
}
|
||||
|
||||
readonly property bool connected: requestSocket.linkUp && ready
|
||||
property bool ready: false
|
||||
property bool initialized: false
|
||||
property bool subscribed: false
|
||||
property int apiVersion: 0
|
||||
property var capabilities: []
|
||||
property var subscribedTopics: []
|
||||
property var devices: []
|
||||
property var devicesById: ({})
|
||||
property string lastError: ""
|
||||
property bool launchPending: false
|
||||
|
||||
property int _requestId: 0
|
||||
property var _pendingRequests: ({})
|
||||
|
||||
signal eventReceived(string topic, var data)
|
||||
|
||||
function _nextId() {
|
||||
_requestId = _requestId >= 2147483647 ? 1 : _requestId + 1;
|
||||
return _requestId;
|
||||
}
|
||||
|
||||
function _parse(line) {
|
||||
if (!line || line.length === 0)
|
||||
return null;
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _isHello(message) {
|
||||
return message && message.id === undefined && message.apiVersion !== undefined && Array.isArray(message.capabilities);
|
||||
}
|
||||
|
||||
function _acceptHello(message) {
|
||||
apiVersion = Number(message.apiVersion) || 0;
|
||||
capabilities = message.capabilities.slice();
|
||||
ready = apiVersion === 1;
|
||||
if (!ready) {
|
||||
lastError = "Unsupported DankConnect API version " + apiVersion;
|
||||
return;
|
||||
}
|
||||
lastError = "";
|
||||
launchDelayTimer.stop();
|
||||
launchPending = false;
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
function _flushPending(message) {
|
||||
const callbacks = _pendingRequests;
|
||||
_pendingRequests = {};
|
||||
for (const id in callbacks) {
|
||||
const callback = callbacks[id];
|
||||
if (!callback)
|
||||
continue;
|
||||
callback({
|
||||
"id": Number(id),
|
||||
"error": message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _applyDevices(snapshots) {
|
||||
const nextDevices = [];
|
||||
const nextById = {};
|
||||
for (const device of (snapshots || [])) {
|
||||
if (!device?.id)
|
||||
continue;
|
||||
nextDevices.push(device);
|
||||
nextById[device.id] = device;
|
||||
}
|
||||
devicesById = nextById;
|
||||
devices = nextDevices;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
function _resolveDevice(deviceOrId) {
|
||||
if (typeof deviceOrId === "string")
|
||||
return devicesById[deviceOrId] || null;
|
||||
return deviceOrId || null;
|
||||
}
|
||||
|
||||
function getDevice(deviceId) {
|
||||
return devicesById[deviceId] || null;
|
||||
}
|
||||
|
||||
// supportedPlugins is the peer's incoming view: actions this client sends.
|
||||
function supports(deviceOrId, packetType) {
|
||||
const device = _resolveDevice(deviceOrId);
|
||||
return !!device && (device.supportedPlugins || []).includes(packetType);
|
||||
}
|
||||
|
||||
// outgoingCapabilities is the peer's producing view: data this client consumes.
|
||||
function produces(deviceOrId, packetType) {
|
||||
const device = _resolveDevice(deviceOrId);
|
||||
return !!device && (device.outgoingCapabilities || []).includes(packetType);
|
||||
}
|
||||
|
||||
function hasCapability(family) {
|
||||
return capabilities.includes(family);
|
||||
}
|
||||
|
||||
function request(method, params, callback) {
|
||||
if (!connected) {
|
||||
callback?.({
|
||||
"error": "daemon not connected"
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
const family = method.split(".")[0];
|
||||
if (!hasCapability(family)) {
|
||||
callback?.({
|
||||
"error": "daemon does not support " + family
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
|
||||
const id = _nextId();
|
||||
const message = {
|
||||
"id": id,
|
||||
"method": method,
|
||||
"params": params || {}
|
||||
};
|
||||
if (callback)
|
||||
_pendingRequests[id] = callback;
|
||||
requestSocket.send(message);
|
||||
return id;
|
||||
}
|
||||
|
||||
function refreshDevices(callback) {
|
||||
return request("device.list", {}, response => {
|
||||
if (!response.error)
|
||||
_applyDevices(response.result || []);
|
||||
callback?.(response);
|
||||
});
|
||||
}
|
||||
|
||||
function launch() {
|
||||
if (connected || requestSocket.linkUp || launchPending)
|
||||
return;
|
||||
launchPending = true;
|
||||
launchDelayTimer.restart();
|
||||
}
|
||||
|
||||
function _handleRequestLine(line) {
|
||||
const message = _parse(line);
|
||||
if (!message)
|
||||
return;
|
||||
if (_isHello(message)) {
|
||||
_acceptHello(message);
|
||||
return;
|
||||
}
|
||||
if (message.id === undefined)
|
||||
return;
|
||||
|
||||
const callback = _pendingRequests[message.id];
|
||||
if (!callback)
|
||||
return;
|
||||
delete _pendingRequests[message.id];
|
||||
callback(message);
|
||||
}
|
||||
|
||||
function _handleSubscriptionLine(line) {
|
||||
const message = _parse(line);
|
||||
if (!message)
|
||||
return;
|
||||
if (_isHello(message)) {
|
||||
if (Number(message.apiVersion) !== 1) {
|
||||
lastError = "Unsupported DankConnect subscription API version " + message.apiVersion;
|
||||
return;
|
||||
}
|
||||
subscriptionSocket.send({
|
||||
"id": root.subscriptionRequestId,
|
||||
"method": "subscribe",
|
||||
"params": {}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (message.id === root.subscriptionRequestId) {
|
||||
subscribed = !message.error;
|
||||
subscribedTopics = message.result?.topics || [];
|
||||
if (message.error)
|
||||
lastError = message.error;
|
||||
return;
|
||||
}
|
||||
const topic = message.event || message.topic || "";
|
||||
if (!topic)
|
||||
return;
|
||||
if (topic === "devices")
|
||||
_applyDevices(message.data || []);
|
||||
eventReceived(topic, message.data);
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: requestSocket
|
||||
|
||||
path: root.socketPath
|
||||
connected: root.enabled && root.socketPath.length > 0
|
||||
reconnectBaseMs: 3000
|
||||
reconnectMaxMs: 3000
|
||||
|
||||
onConnectionStateChanged: {
|
||||
if (linkUp)
|
||||
return;
|
||||
root.ready = false;
|
||||
root.initialized = false;
|
||||
root.subscribed = false;
|
||||
root.subscribedTopics = [];
|
||||
root.apiVersion = 0;
|
||||
root.capabilities = [];
|
||||
root._flushPending("daemon disconnected");
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => root._handleRequestLine(line)
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: subscriptionSocket
|
||||
|
||||
path: root.socketPath
|
||||
connected: root.enabled && root.ready
|
||||
reconnectBaseMs: 3000
|
||||
reconnectMaxMs: 3000
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscribed = false;
|
||||
root.subscribedTopics = [];
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => root._handleSubscriptionLine(line)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: launchDelayTimer
|
||||
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.connected || requestSocket.linkUp) {
|
||||
root.launchPending = false;
|
||||
return;
|
||||
}
|
||||
Quickshell.execDetached(["sh", "-c", "if command -v dankconnect >/dev/null 2>&1; then exec dankconnect daemon; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankconnect >/dev/null 2>&1; then exec flatpak run --command=dankconnect com.danklinux.dankconnect daemon; fi"]);
|
||||
launchResetTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: launchResetTimer
|
||||
|
||||
interval: 5000
|
||||
repeat: false
|
||||
onTriggered: root.launchPending = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property bool connected: backend.connected && backend.subscribed
|
||||
readonly property bool initialized: backend.initialized
|
||||
readonly property int apiVersion: backend.apiVersion
|
||||
readonly property var capabilities: backend.capabilities
|
||||
readonly property var devices: backend.devices
|
||||
readonly property var devicesById: backend.devicesById
|
||||
readonly property string lastError: backend.lastError
|
||||
readonly property var primaryDevice: _selectPrimaryDevice()
|
||||
property var pairingRequest: null
|
||||
property bool _openAppPending: false
|
||||
|
||||
Component.onCompleted: backend.launch()
|
||||
|
||||
onConnectedChanged: {
|
||||
if (!connected) {
|
||||
pairingRequest = null;
|
||||
return;
|
||||
}
|
||||
if (_openAppPending) {
|
||||
_openAppPending = false;
|
||||
_launchAttachedApp();
|
||||
}
|
||||
}
|
||||
|
||||
function _selectPrimaryDevice() {
|
||||
let selected = null;
|
||||
let selectedScore = -1;
|
||||
for (const device of devices) {
|
||||
const score = (device.isPaired ? 2 : 0) + (device.isReachable ? 1 : 0);
|
||||
if (score <= selectedScore)
|
||||
continue;
|
||||
selected = device;
|
||||
selectedScore = score;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
function _syncPairingRequest() {
|
||||
for (const device of devices) {
|
||||
if (!device.isPairRequestedByPeer)
|
||||
continue;
|
||||
pairingRequest = {
|
||||
"deviceId": device.id,
|
||||
"name": device.name || "",
|
||||
"verificationKey": device.verificationKey || ""
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (initialized)
|
||||
pairingRequest = null;
|
||||
}
|
||||
|
||||
function _handleEvent(topic, data) {
|
||||
if (topic !== "pairing" || !data?.deviceId)
|
||||
return;
|
||||
pairingRequest = {
|
||||
"deviceId": data.deviceId,
|
||||
"name": data.name || "",
|
||||
"verificationKey": data.verificationKey || ""
|
||||
};
|
||||
}
|
||||
|
||||
function supports(deviceOrId, packetType) {
|
||||
return backend.supports(deviceOrId, packetType);
|
||||
}
|
||||
|
||||
function canRing(deviceOrId) {
|
||||
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("device") && supports(device, "kdeconnect.findmyphone.request");
|
||||
}
|
||||
|
||||
function canPing(deviceOrId) {
|
||||
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("ping") && supports(device, "kdeconnect.ping");
|
||||
}
|
||||
|
||||
function canSendClipboard(deviceOrId) {
|
||||
const device = typeof deviceOrId === "string" ? backend.getDevice(deviceOrId) : deviceOrId;
|
||||
return connected && !!device?.isPaired && !!device?.isReachable && backend.hasCapability("clipboard") && supports(device, "kdeconnect.clipboard");
|
||||
}
|
||||
|
||||
function ringDevice(deviceId, callback) {
|
||||
if (!canRing(deviceId)) {
|
||||
callback?.({
|
||||
"error": "ring unavailable"
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
return backend.request("device.ring", {
|
||||
"deviceId": deviceId
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function pingDevice(deviceId, callback) {
|
||||
if (!canPing(deviceId)) {
|
||||
callback?.({
|
||||
"error": "ping unavailable"
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
return backend.request("ping.send", {
|
||||
"deviceId": deviceId
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function sendClipboard(deviceId, callback) {
|
||||
if (!canSendClipboard(deviceId)) {
|
||||
callback?.({
|
||||
"error": "clipboard unavailable"
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
return backend.request("clipboard.send", {
|
||||
"deviceId": deviceId,
|
||||
"content": ""
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function acceptPairing(deviceId, callback) {
|
||||
return _respondToPairing("pairing.accept", deviceId, callback);
|
||||
}
|
||||
|
||||
function rejectPairing(deviceId, callback) {
|
||||
return _respondToPairing("pairing.reject", deviceId, callback);
|
||||
}
|
||||
|
||||
function openApp() {
|
||||
if (!connected) {
|
||||
_openAppPending = true;
|
||||
backend.launch();
|
||||
return;
|
||||
}
|
||||
_launchAttachedApp();
|
||||
}
|
||||
|
||||
function _launchAttachedApp() {
|
||||
Quickshell.execDetached(["sh", "-c", "if command -v dankconnect >/dev/null 2>&1; then exec dankconnect app; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankconnect >/dev/null 2>&1; then exec flatpak run --command=dankconnect com.danklinux.dankconnect app; fi"]);
|
||||
}
|
||||
|
||||
function _respondToPairing(method, deviceId, callback) {
|
||||
if (!connected || !backend.hasCapability("pairing") || !deviceId) {
|
||||
callback?.({
|
||||
"error": "pairing unavailable"
|
||||
});
|
||||
return -1;
|
||||
}
|
||||
return backend.request(method, {
|
||||
"deviceId": deviceId
|
||||
}, response => {
|
||||
if (!response.error && pairingRequest?.deviceId === deviceId)
|
||||
pairingRequest = null;
|
||||
callback?.(response);
|
||||
});
|
||||
}
|
||||
|
||||
DankConnectBackend {
|
||||
id: backend
|
||||
|
||||
enabled: true
|
||||
onDevicesChanged: root._syncPairingRequest()
|
||||
onEventReceived: (topic, data) => root._handleEvent(topic, data)
|
||||
}
|
||||
}
|
||||
@@ -473,7 +473,7 @@ Singleton {
|
||||
function reloadConfig(showToast, suppressWatch) {
|
||||
if (!CompositorService.isMango || !root.available)
|
||||
return;
|
||||
const shouldShowToast = showToast !== false;
|
||||
const shouldShowToast = showToast !== false && SessionData.showConfigReloadToast;
|
||||
const shouldSuppressWatch = suppressWatch !== false;
|
||||
if (shouldSuppressWatch)
|
||||
suppressWatchedConfigReloads(1500);
|
||||
|
||||
@@ -73,6 +73,7 @@ Singleton {
|
||||
Component.onCompleted: {
|
||||
fetchOutputs();
|
||||
Paths.mkdir(screenshotsDir);
|
||||
generateNiriInputConfig();
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -105,6 +106,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onIsNiriChanged() {
|
||||
if (CompositorService.isNiri) {
|
||||
generateNiriInputConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: validateProcess
|
||||
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 {
|
||||
id: writeCursorProcess
|
||||
property string cursorContent: ""
|
||||
@@ -609,7 +633,7 @@ Singleton {
|
||||
configGenerationAction.schedule();
|
||||
configReloaded();
|
||||
|
||||
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
|
||||
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression && SessionData.showConfigReloadToast) {
|
||||
ToastService.showInfo(I18n.tr("niri: config reloaded"), "", "", "niri-config");
|
||||
} else if (suppressNextConfigToast) {
|
||||
suppressNextConfigToast = false;
|
||||
@@ -1261,7 +1285,7 @@ window-rule {
|
||||
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";
|
||||
Proc.runCommand("niri-ensure-" + name, ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${path}" ] && touch "${path}" || true`], (output, exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
@@ -1641,6 +1665,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 {
|
||||
function screenshot(): string {
|
||||
if (!CompositorService.isNiri) {
|
||||
|
||||
@@ -777,7 +777,7 @@ Singleton {
|
||||
return 5000;
|
||||
// expireTimeout is in milliseconds; -1 defers to our settings.
|
||||
const appTimeout = wrapper.notification.expireTimeout;
|
||||
if (appTimeout >= 0)
|
||||
if (appTimeout >= 0 && !SettingsData.notificationIgnoreAppTimeout)
|
||||
return Math.round(appTimeout);
|
||||
switch (wrapper.urgency) {
|
||||
case NotificationUrgency.Low:
|
||||
|
||||
@@ -425,7 +425,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
settingsModal?.close();
|
||||
settingsModal?.hide();
|
||||
}
|
||||
|
||||
function toggleSettings() {
|
||||
|
||||
@@ -117,7 +117,7 @@ Singleton {
|
||||
|
||||
Timer {
|
||||
id: colorSchemeSettleTimer
|
||||
interval: 1000
|
||||
interval: 750
|
||||
onTriggered: {
|
||||
if (!root.canSyncColorScheme())
|
||||
return;
|
||||
|
||||
@@ -215,6 +215,9 @@ Item {
|
||||
// keep the bg window in active-update mode.
|
||||
property bool _bgCommitWindow: false
|
||||
|
||||
// True only while the overlay is actually on screen, not merely declared.
|
||||
readonly property bool _overlayActive: overlayLoader.item?.visible ?? false
|
||||
|
||||
Timer {
|
||||
id: bgCommitSettleTimer
|
||||
interval: 250
|
||||
@@ -236,6 +239,9 @@ Item {
|
||||
return;
|
||||
_blurCommitSuppress = true;
|
||||
blurCommitPulseTimer.restart();
|
||||
// Land the off->on toggle on a commit even after _overlayActive has dropped.
|
||||
_bgCommitWindow = true;
|
||||
bgCommitSettleTimer.restart();
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -563,10 +569,9 @@ Item {
|
||||
screen: root.surfaceScreen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
// Skip buffer updates when there's nothing to render. Briefly flipped
|
||||
// true via _bgCommitWindow when _surfaceBodyW/H changes so the
|
||||
// contentHoleRect mask carve-out actually commits to the compositor.
|
||||
updatesEnabled: !root._blurCommitSuppress && (root.overlayContent !== null || root._bgCommitWindow)
|
||||
// Idle full-screen surface skips buffer updates, else it damages the whole screen
|
||||
// every frame. Live while an overlay shows or _bgCommitWindow flushes a mask change.
|
||||
updatesEnabled: !root._blurCommitSuppress && (root._overlayActive || root._bgCommitWindow)
|
||||
|
||||
WlrLayershell.namespace: root.layerNamespace + ":background"
|
||||
WlrLayershell.layer: root.effectivePopoutLayer
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* BEGIN DMS OVERRIDE */
|
||||
|
||||
/*
|
||||
* GTK Colors
|
||||
* 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_selected_bg_color @accent_bg_color;
|
||||
@define-color theme_unfocused_selected_fg_color @accent_fg_color;
|
||||
|
||||
/* END DMS OVERRIDE */
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* BEGIN DMS OVERRIDE */
|
||||
|
||||
/*
|
||||
* GTK Colors
|
||||
* 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_border_color @window_bg_color;
|
||||
@define-color sidebar_backdrop_color @window_bg_color;
|
||||
|
||||
/* END DMS OVERRIDE */
|
||||
|
||||
+222
-85
@@ -1,114 +1,251 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CONFIG_DIR="$1"
|
||||
SHELL_DIR="$3"
|
||||
# apply: setup symlinks and config dirs
|
||||
# patch: refresh overrides in adw-gtk3
|
||||
# remove: remove all overrides
|
||||
MODE="${2:-apply}"
|
||||
IS_LIGHT="${3:-light}"
|
||||
|
||||
if [ -z "$CONFIG_DIR" ]; then
|
||||
echo "Usage: $0 <config_dir> [is_light] [shell_dir]" >&2
|
||||
exit 1
|
||||
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The light template references adw-gtk3 image assets relative to gtk.css
|
||||
# (check/radio glyphs, slider knobs); without them checked boxes render as
|
||||
# solid blocks.
|
||||
link_gtk3_assets() {
|
||||
local gtk3_dir="$1"
|
||||
local assets_link="$gtk3_dir/assets"
|
||||
get_adw_gtk3_dir() {
|
||||
local variant="$1"
|
||||
local name=""
|
||||
[ "$variant" == "dark" ] && name="-$variant"
|
||||
|
||||
if [ -e "$assets_link" ] && [ ! -L "$assets_link" ]; then
|
||||
echo "Leaving user-managed $assets_link in place"
|
||||
return
|
||||
fi
|
||||
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"
|
||||
}
|
||||
|
||||
local candidates=(
|
||||
"$HOME/.local/share/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/local/share/themes/adw-gtk3/gtk-3.0/assets"
|
||||
)
|
||||
local target=""
|
||||
for c in "${candidates[@]}"; do
|
||||
if [ -d "$c" ]; then
|
||||
target="$c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$target" ] && [ -n "$SHELL_DIR" ] && [ -d "$SHELL_DIR/matugen/gtk3-assets" ]; then
|
||||
target="$SHELL_DIR/matugen/gtk3-assets"
|
||||
fi
|
||||
if [ -z "$target" ]; then
|
||||
return
|
||||
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 $?
|
||||
}
|
||||
|
||||
ln -sfn "$target" "$assets_link"
|
||||
echo "Linked GTK3 assets: $assets_link -> $target"
|
||||
remove_gtk3_colors() {
|
||||
local config_dir="$1"
|
||||
|
||||
local gtk3_dir="$config_dir/gtk-3.0"
|
||||
|
||||
# remove global override
|
||||
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 form '${gtk3_dir}'"
|
||||
else
|
||||
echo "Failed to removed 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" ]; then
|
||||
echo "Error: No version of adw-gtk3 ${variant} was found" >&2
|
||||
exit 1
|
||||
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"
|
||||
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" ]; then
|
||||
echo "Warning: No version of adw-gtk3 ${variant} was found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
|
||||
echo "Warning: No user version of adw-gtk3 ${variant} was found." >&2
|
||||
exit 1
|
||||
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() {
|
||||
local config_dir="$1"
|
||||
local config_dir="$1"
|
||||
|
||||
local gtk3_dir="$config_dir/gtk-3.0"
|
||||
local dank_colors="$gtk3_dir/dank-colors.css"
|
||||
local gtk_css="$gtk3_dir/gtk.css"
|
||||
local gtk3_dir="$config_dir/gtk-3.0"
|
||||
local gtk3_override="$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
|
||||
echo "Error: dank-colors.css not found at $dank_colors" >&2
|
||||
echo "Run matugen first to generate theme files" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$dank_colors" ]; then
|
||||
echo "Error: dank-colors.css not found at $dank_colors" >&2
|
||||
echo "Run matugen first to generate theme files" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -L "$gtk_css" ]; then
|
||||
rm "$gtk_css"
|
||||
elif [ -f "$gtk_css" ]; then
|
||||
mv "$gtk_css" "$gtk_css.backup.$(date +%s)"
|
||||
echo "Backed up existing gtk.css"
|
||||
fi
|
||||
if [ -L "$gtk3_override" ]; then
|
||||
rm "$gtk3_override"
|
||||
elif [ -f "$gtk3_override" ]; then
|
||||
mv "$gtk3_override" "$gtk3_override.backup.$(date +%s)"
|
||||
echo "Backed up existing gtk.css"
|
||||
fi
|
||||
|
||||
ln -s "dank-colors.css" "$gtk_css"
|
||||
echo "Created symlink: $gtk_css -> dank-colors.css"
|
||||
ln -s "dank-colors.css" "$gtk3_override"
|
||||
echo "Created symlink: $gtk3_override -> dank-colors.css"
|
||||
|
||||
link_gtk3_assets "$gtk3_dir"
|
||||
|
||||
return
|
||||
fi
|
||||
|
||||
# Else ensure there's no global override
|
||||
if [ -L "$gtk3_override" ]; then
|
||||
rm "$gtk3_override"
|
||||
elif [ -f "$gtk3_override" ]; then
|
||||
mv "$gtk3_override" "$gtk3_override.backup.$(date +%s)"
|
||||
echo "Backed up and removed existing gtk.css"
|
||||
fi
|
||||
|
||||
# Backup adw-gtk3 stylesheets
|
||||
for variant in light dark; do
|
||||
local adw_gtk3_dir && adw_gtk3_dir="$(get_adw_gtk3_dir "$variant")"
|
||||
cp "$adw_gtk3_dir/gtk-3.0/gtk.css" "$adw_gtk3_dir/gtk-3.0/gtk.css.backup.$(date +%s)"
|
||||
cp "$adw_gtk3_dir/gtk-3.0/gtk-dark.css" "$adw_gtk3_dir/gtk-3.0/gtk-dark.css.backup.$(date +%s)"
|
||||
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"
|
||||
|
||||
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'"
|
||||
|
||||
local gtk4_import="@import url(\"dank-colors.css\");"
|
||||
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
|
||||
sed -i "/$gtk4_import/d" "$gtk_css"
|
||||
echo "Removed gtk4 import in '$gtk_css'"
|
||||
fi
|
||||
|
||||
link_gtk3_assets "$gtk3_dir"
|
||||
}
|
||||
|
||||
apply_gtk4_colors() {
|
||||
local config_dir="$1"
|
||||
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"
|
||||
local gtk4_import="@import url(\"dank-colors.css\");"
|
||||
local gtk4_dir="$config_dir/gtk-4.0"
|
||||
local dank_colors="$gtk4_dir/dank-colors.css"
|
||||
local gtk_css="$gtk4_dir/gtk.css"
|
||||
local gtk4_import="@import url(\"dank-colors.css\");"
|
||||
|
||||
if [ ! -f "$dank_colors" ]; then
|
||||
echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2
|
||||
echo "Run matugen first to generate theme files" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$dank_colors" ]; then
|
||||
echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2
|
||||
echo "Run matugen first to generate theme files" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
|
||||
echo "GTK4 import already exists"
|
||||
return
|
||||
fi
|
||||
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
|
||||
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"
|
||||
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
|
||||
# existed; only acts when DMS already manages gtk.css.
|
||||
if [ "$4" = "assets-only" ]; then
|
||||
if [ -L "$CONFIG_DIR/gtk-3.0/gtk.css" ]; then
|
||||
link_gtk3_assets "$CONFIG_DIR/gtk-3.0"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
case "$MODE" in
|
||||
patch)
|
||||
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"
|
||||
apply_gtk4_colors "$CONFIG_DIR"
|
||||
|
||||
echo "GTK colors applied successfully"
|
||||
echo "GTK colors applied successfully"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -772,6 +772,34 @@
|
||||
],
|
||||
"icon": "display_settings"
|
||||
},
|
||||
{
|
||||
"section": "hoverPopouts",
|
||||
"label": "Hover Popouts",
|
||||
"tabIndex": 3,
|
||||
"category": "Dank Bar",
|
||||
"keywords": [
|
||||
"another",
|
||||
"bar",
|
||||
"dank",
|
||||
"delay",
|
||||
"hover",
|
||||
"hovering",
|
||||
"moving",
|
||||
"open",
|
||||
"over",
|
||||
"panel",
|
||||
"popout",
|
||||
"popouts",
|
||||
"reveal",
|
||||
"statusbar",
|
||||
"switches",
|
||||
"taskbar",
|
||||
"topbar",
|
||||
"widget"
|
||||
],
|
||||
"icon": "touch_app",
|
||||
"description": "Open widget popouts by hovering over the bar. Moving to another widget switches the popout."
|
||||
},
|
||||
{
|
||||
"section": "barPosition",
|
||||
"label": "Position",
|
||||
@@ -6122,6 +6150,34 @@
|
||||
"icon": "history",
|
||||
"description": "Save dismissed notifications to history"
|
||||
},
|
||||
{
|
||||
"section": "notificationIgnoreAppTimeout",
|
||||
"label": "Ignore App-Requested Timeout",
|
||||
"tabIndex": 17,
|
||||
"category": "Notifications",
|
||||
"keywords": [
|
||||
"above",
|
||||
"alerts",
|
||||
"always",
|
||||
"app",
|
||||
"duration",
|
||||
"durations",
|
||||
"even",
|
||||
"expire",
|
||||
"ignore",
|
||||
"longer",
|
||||
"messages",
|
||||
"notification",
|
||||
"notifications",
|
||||
"override",
|
||||
"requested",
|
||||
"requests",
|
||||
"shorter",
|
||||
"timeout",
|
||||
"toast"
|
||||
],
|
||||
"description": "Always use the durations above, even if an app requests a shorter or longer one"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenNotifications",
|
||||
"label": "Lock Screen",
|
||||
@@ -6515,18 +6571,26 @@
|
||||
"tabIndex": 17,
|
||||
"category": "Notifications",
|
||||
"keywords": [
|
||||
"above",
|
||||
"alerts",
|
||||
"always",
|
||||
"duration",
|
||||
"durations",
|
||||
"even",
|
||||
"longer",
|
||||
"low",
|
||||
"messages",
|
||||
"notification",
|
||||
"notifications",
|
||||
"priority",
|
||||
"requests",
|
||||
"shorter",
|
||||
"timeout",
|
||||
"timeouts",
|
||||
"toast"
|
||||
],
|
||||
"icon": "timer"
|
||||
"icon": "timer",
|
||||
"description": "Always use the durations above, even if an app requests a shorter or longer one"
|
||||
},
|
||||
{
|
||||
"section": "osdAlwaysShowValue",
|
||||
@@ -9069,6 +9133,32 @@
|
||||
],
|
||||
"description": "Space between windows"
|
||||
},
|
||||
{
|
||||
"section": "compositorNotifications",
|
||||
"label": "Notifications",
|
||||
"tabIndex": 37,
|
||||
"category": "Personalization",
|
||||
"keywords": [
|
||||
"alert",
|
||||
"alerts",
|
||||
"appearance",
|
||||
"compositor",
|
||||
"config",
|
||||
"custom",
|
||||
"customize",
|
||||
"notif",
|
||||
"notifications",
|
||||
"notifs",
|
||||
"personal",
|
||||
"personalization",
|
||||
"reloaded",
|
||||
"show",
|
||||
"toast"
|
||||
],
|
||||
"icon": "notifications",
|
||||
"description": "Show a toast when the compositor config is reloaded",
|
||||
"conditionKey": "isNiri"
|
||||
},
|
||||
{
|
||||
"section": "hyprlandLayoutGapsOutOverride",
|
||||
"label": "Outer Gaps",
|
||||
@@ -9280,6 +9370,26 @@
|
||||
],
|
||||
"description": "Resize windows by dragging their edges with the mouse"
|
||||
},
|
||||
{
|
||||
"section": "showConfigReloadToast",
|
||||
"label": "Show \\",
|
||||
"tabIndex": 37,
|
||||
"category": "Personalization",
|
||||
"keywords": [
|
||||
"appearance",
|
||||
"compositor",
|
||||
"config",
|
||||
"custom",
|
||||
"customize",
|
||||
"personal",
|
||||
"personalization",
|
||||
"reload",
|
||||
"reloaded",
|
||||
"show",
|
||||
"toast"
|
||||
],
|
||||
"description": "Show a toast when the compositor config is reloaded"
|
||||
},
|
||||
{
|
||||
"section": "hyprlandLayoutRadiusOverride",
|
||||
"label": "Window Corner Radius",
|
||||
@@ -9916,5 +10026,18 @@
|
||||
"widgets"
|
||||
],
|
||||
"icon": "dashboard"
|
||||
},
|
||||
{
|
||||
"section": "_tab_44",
|
||||
"label": "Mouse & Touchpad",
|
||||
"tabIndex": 44,
|
||||
"category": "Settings",
|
||||
"keywords": [
|
||||
"mouse",
|
||||
"settings",
|
||||
"touchpad"
|
||||
],
|
||||
"icon": "mouse",
|
||||
"conditionKey": "isNiri"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user