1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-05 13:08:29 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
bbedward edcad60786 rebase KDE platform compat 2026-07-21 11:41:04 -04:00
78 changed files with 2405 additions and 4139 deletions
-6
View File
@@ -14,12 +14,6 @@ git clone --recurse-submodules https://github.com/AvengeMedia/DankMaterialShell.
git submodule update --init
```
To have `git pull` keep the submodule in sync automatically (moving it to the commit this repo points at, no separate `git submodule update` step), set:
```bash
git config submodule.recurse true
```
Install [prek](https://prek.j178.dev/) then activate pre-commit hooks:
```bash
-1
View File
@@ -40,7 +40,6 @@ var resolveIncludeCmd = &cobra.Command{
"layout.kdl",
"outputs.kdl",
"binds.kdl",
"input.kdl",
"cursor.conf",
"layout.conf",
"outputs.conf",
-4
View File
@@ -143,10 +143,6 @@ 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
View File
@@ -69,7 +69,7 @@ require (
)
require (
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05
github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599
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
View File
@@ -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-20260724133713-a4ef23371e05 h1:Ij/yzOT8y2HL7V5Rkec1GxJV0rUvhKqfAzyGxVRTk1o=
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05/go.mod h1:xt8RldAfti0QCWidwYIzsSSoJWsE61WgEhTu2H9UpD4=
github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599 h1:hxy5SRGREsQ6v4xUOxWnWVPWD6ZU+H0jA+LHcTwXbCg=
github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599/go.mod h1:7p7cfydr4WM1G6eOPFlANXF3IV5du3FoA4CbDPprHAo=
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=
-1
View File
@@ -264,7 +264,6 @@ 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", ""},
@@ -1,15 +0,0 @@
// ! 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
}
}
+25 -2
View File
@@ -34,6 +34,31 @@ 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
@@ -52,7 +77,6 @@ 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".
@@ -247,4 +271,3 @@ 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"
-3
View File
@@ -16,6 +16,3 @@ var NiriAlttabConfig string
//go:embed embedded/niri-binds.kdl
var NiriBindsConfig string
//go:embed embedded/niri-input.kdl
var NiriInputConfig string
+9 -9
View File
@@ -386,8 +386,8 @@ func buildOnce(opts *Options) (bool, error) {
default:
syncAccentColor(primaryDark)
}
refreshGTKTheme(opts.Mode)
refreshGTKColorScheme()
refreshGTK(opts.Mode)
refreshGTK4()
}
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-4.0", "gtk.css")
gtkCSS := filepath.Join(configDir, "gtk-3.0", "gtk.css")
info, err := os.Lstat(gtkCSS)
if err != nil {
@@ -968,7 +968,7 @@ func applyKDEColorScheme(mode ColorMode) {
}
}
func refreshGTKTheme(mode ColorMode) {
func refreshGTK(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 refreshGTKColorScheme() {
if os.Getenv("DMS_ENABLE_GTK_REFRESH") != "1" {
func refreshGTK4() {
if os.Getenv("DMS_ENABLE_GTK4_REFRESH") != "1" {
return
}
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
@@ -1012,13 +1012,13 @@ func refreshGTKColorScheme() {
expectColorSchemeEcho(toggle)
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
log.Warnf("Failed to toggle color-scheme for GTK refresh: %v", err)
log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err)
return
}
time.Sleep(50 * time.Millisecond)
time.Sleep(400 * 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 GTK refresh: %v", err)
log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err)
}
}
+6 -11
View File
@@ -43,10 +43,10 @@ func NewManager() (*Manager, error) {
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
m.promptBroker = broker
adapter, err := findAdapter(conn)
adapter, err := m.findAdapter()
if err != nil {
conn.Close()
return nil, err
return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
}
m.adapterPath = adapter
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
return m, nil
}
func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
obj := conn.Object(bluezService, dbus.ObjectPath("/"))
func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
obj := m.dbusConn.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 "", fmt.Errorf("%w: %v", ErrNoAdapter, err)
return "", err
}
for path, interfaces := range objects {
@@ -89,7 +89,7 @@ func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
}
}
return "", ErrNoAdapter
return "", fmt.Errorf("no adapter found")
}
func (m *Manager) initialize() error {
@@ -487,11 +487,6 @@ 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
}
@@ -1,5 +0,0 @@
package bluez
func rfkillUnblockBluetooth() error {
return nil
}
@@ -1,19 +0,0 @@
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
}
-46
View File
@@ -1,46 +0,0 @@
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")
}
+2 -3
View File
@@ -116,12 +116,11 @@ func RouteRequest(conn *models.Conn, req models.Request) {
}
if strings.HasPrefix(req.Method, "cups.") {
mgr, err := ensureCupsManager()
if err != nil {
if cupsManager == nil {
models.RespondError(conn, req.ID, "CUPS manager not initialized")
return
}
cups.HandleRequest(conn, req, mgr)
cups.HandleRequest(conn, req, cupsManager)
return
}
+36 -80
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"runtime/debug"
"sync"
"sync/atomic"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation"
@@ -79,8 +80,8 @@ var geoClientInstance geolocation.Client
const dbusClientID = "dms-dbus-client"
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
var cupsMu sync.Mutex
var cupsSubscriberCount int
var cupsSubscribers syncmap.Map[string, bool]
var cupsSubscriberCount atomic.Int32
var appPaths = paths.New("danklinux")
@@ -173,55 +174,17 @@ func InitializeAppPickerManager() error {
return nil
}
func initializeCupsManagerLocked() (bool, error) {
if cupsManager != nil {
return false, nil
}
func InitializeCupsManager() error {
manager, err := cups.NewManager()
if err != nil {
log.Warnf("Failed to initialize cups manager: %v", err)
return false, err
return err
}
cupsManager = manager
log.Info("CUPS manager initialized")
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()
return nil
}
func InitializeBrightnessManager() error {
@@ -416,7 +379,7 @@ func getCapabilities() Capabilities {
caps = append(caps, "browser")
}
if cupsAvailable() {
if cupsManager != nil {
caps = append(caps, "cups")
}
@@ -486,7 +449,7 @@ func getServerInfo() ServerInfo {
caps = append(caps, "browser")
}
if cupsAvailable() {
if cupsManager != nil {
caps = append(caps, "cups")
}
@@ -950,31 +913,38 @@ func handleSubscribe(conn *models.Conn, req models.Request) {
}
if shouldSubscribe("cups") {
cupsMu.Lock()
cupsSubscriberCount++
created, err := initializeCupsManagerLocked()
mgr := cupsManager
cupsMu.Unlock()
cupsSubscribers.Store(clientID+"-cups", true)
count := cupsSubscriberCount.Add(1)
if err != nil {
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
} else if created {
notifyCapabilityChange()
if count == 1 {
if err := InitializeCupsManager(); err != nil {
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
} else {
notifyCapabilityChange()
}
}
if mgr == nil {
releaseCupsSubscriber()
} else {
if cupsManager != nil {
wg.Add(1)
cupsChan := mgr.Subscribe(clientID + "-cups")
cupsChan := cupsManager.Subscribe(clientID + "-cups")
go func() {
defer wg.Done()
defer func() {
mgr.Unsubscribe(clientID + "-cups")
releaseCupsSubscriber()
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()
}
}
}()
initialState := mgr.GetState()
initialState := cupsManager.GetState()
select {
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
case <-stopChan:
@@ -1284,12 +1254,9 @@ func cleanupManagers() {
if appPickerManager != nil {
appPickerManager.Close()
}
cupsMu.Lock()
if cupsManager != nil {
cupsManager.Close()
cupsManager = nil
}
cupsMu.Unlock()
if brightnessManager != nil {
brightnessManager.Close()
}
@@ -1668,21 +1635,10 @@ func (s *Server) Serve(printDocs bool) error {
}()
go func() {
for {
err := InitializeBluezManager()
if err == nil {
notifyCapabilityChange()
return
}
if err := InitializeBluezManager(); err != nil {
log.Warnf("Bluez manager unavailable: %v", err)
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")
} else {
notifyCapabilityChange()
}
}()
+22 -6
View File
@@ -12,7 +12,6 @@ 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"
@@ -24,6 +23,9 @@ 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
@@ -750,8 +752,7 @@ func (m *Manager) schedulerLoop() {
m.post(func() { m.applyCurrentTemp("startup") })
}
timer := boottimer.New(24 * time.Hour)
defer timer.Stop()
var timer *time.Timer
for {
m.configMutex.RLock()
enabled := m.config.Enabled
@@ -760,19 +761,31 @@ func (m *Manager) schedulerLoop() {
now := time.Now()
m.recalcSchedule(now)
waitDur := 24 * time.Hour
var waitDur time.Duration
if enabled {
deadline := m.getNextDeadline(now)
if waitDur = time.Until(deadline); waitDur < time.Second {
waitDur = time.Until(deadline)
switch {
case waitDur < time.Second:
waitDur = time.Second
case waitDur > maxScheduleWait:
waitDur = maxScheduleWait
}
} else {
waitDur = 24 * time.Hour
}
timer.Reset(waitDur)
if timer != nil {
timer.Stop()
}
timer = time.NewTimer(waitDur)
select {
case <-m.stopChan:
timer.Stop()
return
case <-m.updateTrigger:
timer.Stop()
m.scheduleMutex.Lock()
m.schedule.calcDay = time.Time{}
m.scheduleMutex.Unlock()
@@ -1073,11 +1086,14 @@ 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
View File
@@ -16,7 +16,7 @@ revision=1
create_wrksrc=yes
build_style=go
build_wrksrc="core"
hostmakedepends="make tar"
hostmakedepends="make"
go_import_path="github.com/AvengeMedia/DankMaterialShell/core"
go_package="${go_import_path}/cmd/dms"
go_build_tags="distro_binary withshell"
Generated
+3 -3
View File
@@ -3,11 +3,11 @@
"dank-qml-common": {
"flake": false,
"locked": {
"lastModified": 1784935106,
"narHash": "sha256-aVgOBRynme6XNKYCZlf/oCjPDZFwddyUQPRRkEupC3c=",
"lastModified": 1784647395,
"narHash": "sha256-kcLSJC8XBzmWoSBc90Zn81qrNsqWMkwQ1m/zZixOw30=",
"owner": "AvengeMedia",
"repo": "dank-qml-common",
"rev": "a172b39841d8f42bac46ac133b76cade1fae91b4",
"rev": "1919595f0dde4f5bf5ac0baa1902ff6d070971d5",
"type": "github"
},
"original": {
+1 -1
View File
@@ -111,7 +111,7 @@
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-ZvaOPC92ZFRPqSyLJa2TA9OUKQ3QnWCIMxrnYLGnC58=";
vendorHash = "sha256-//ya3Yb25/kEBeT6M4bO8Egi+Tb7iiUwevXlzTyIgqA=";
subPackages = [ "cmd/dms" ];
+2 -21
View File
@@ -65,35 +65,16 @@ Singleton {
function _openPopout(popout) {
if (popout.dashVisible !== undefined) {
let flagStayedTrue = popout.dashVisible === true;
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing) {
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing)
popout.dashVisible = false;
flagStayedTrue = false;
}
popout.dashVisible = true;
// Flag already true (e.g. retargeting to another monitor) won't re-fire
// the change handler, so drive the surface open explicitly.
if (flagStayedTrue)
_ensureSurfaceOpen(popout);
return;
}
if (popout.notificationHistoryVisible !== undefined) {
const flagStayedTrue = popout.notificationHistoryVisible === true;
popout.notificationHistoryVisible = true;
if (flagStayedTrue)
_ensureSurfaceOpen(popout);
return;
}
_ensureSurfaceOpen(popout);
}
function _ensureSurfaceOpen(popout) {
if (typeof popout.present === "function") {
popout.present();
return;
}
if (typeof popout.open === "function")
popout.open();
popout.open();
}
function _closePopout(popout) {
-1
View File
@@ -224,7 +224,6 @@ Singleton {
property string niriOverviewLastMode: "apps"
property string settingsSidebarExpandedIds: ","
property string settingsSidebarCollapsedIds: ","
property bool showConfigReloadToast: true
Component.onCompleted: {
loadSettings();
-56
View File
@@ -184,24 +184,6 @@ 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
@@ -934,7 +916,6 @@ 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
@@ -1498,13 +1479,6 @@ 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") {
@@ -1733,7 +1707,6 @@ Singleton {
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateCompositorLayout": updateCompositorLayout,
"updateCompositorInput": updateCompositorInput,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs,
"updateCompositorCursor": updateCompositorCursor,
@@ -2762,35 +2735,6 @@ 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;
+3 -43
View File
@@ -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]);
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "", shellDir, "assets-only"]);
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen;
@@ -1926,41 +1926,6 @@ 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") {
@@ -1970,7 +1935,7 @@ Singleton {
}
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false";
Proc.runCommand("gtkApplier", ["bash", shellDir + "/scripts/gtk.sh", configDir, "apply", isLight, shellDir], (output, exitCode) => {
Proc.runCommand("gtkApplier", ["bash", shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir], (output, exitCode) => {
if (exitCode === 0) {
if (typeof ToastService !== "undefined" && typeof NiriService !== "undefined" && !NiriService.matugenSuppression) {
ToastService.showInfo(I18n.tr("GTK colors applied successfully"));
@@ -2166,13 +2131,8 @@ Singleton {
root.matugenCompleted(currentMode, "error");
}
if (!pendingThemeRequest) {
if (SettingsData.matugenTemplateGtk) {
patchGtk3colors();
refreshGtkTheme();
}
if (!pendingThemeRequest)
return;
}
const req = pendingThemeRequest;
pendingThemeRequest = null;
+1 -2
View File
@@ -98,8 +98,7 @@ var SPEC = {
niriOverviewLastMode: { def: "apps" },
settingsSidebarExpandedIds: { def: "," },
settingsSidebarCollapsedIds: { def: "," },
showConfigReloadToast: { def: true }
settingsSidebarCollapsedIds: { def: "," }
};
function getValidKeys() {
@@ -38,24 +38,6 @@ 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 },
@@ -495,7 +477,6 @@ var SPEC = {
notificationTimeoutLow: { def: 5000 },
notificationTimeoutNormal: { def: 5000 },
notificationTimeoutCritical: { def: 0 },
notificationIgnoreAppTimeout: { def: false },
notificationCompactMode: { def: false },
notificationShowTimeoutBar: { def: false },
notificationDedupeEnabled: { def: true },
-11
View File
@@ -314,17 +314,6 @@ Item {
}
}
Binding {
target: BarWidgetService
property: "dockContextMenu"
value: dockContextMenuLoader.item
}
Binding {
target: BarWidgetService
property: "dockTrashContextMenu"
value: dockTrashContextMenuLoader.item
}
Connections {
target: SettingsData
function onDockShowTrashChanged() {
@@ -718,20 +718,5 @@ 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());
}
}
}
}
+5 -11
View File
@@ -41,9 +41,6 @@ FloatingWindow {
signal closingModal
function show() {
if (visible && !backingWindowVisible) {
visible = false;
}
visible = true;
}
@@ -52,11 +49,7 @@ FloatingWindow {
}
function toggle() {
if (visible && backingWindowVisible) {
hide();
return;
}
show();
visible = !visible;
}
function setTabIndex(tabIndex: int) {
@@ -68,12 +61,13 @@ FloatingWindow {
function showWithTab(tabIndex: int) {
setTabIndex(tabIndex);
show();
visible = true;
}
function showWithTabName(tabName: string) {
setTabIndex(sidebar.resolveTabIndex(tabName));
show();
var idx = sidebar.resolveTabIndex(tabName);
setTabIndex(idx);
visible = true;
}
function resolveTabIndex(tabName: string): int {
@@ -319,13 +319,6 @@ 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"),
@@ -77,12 +77,6 @@ DankPopout {
readonly property color _containerBg: Theme.nestedSurface
// Defer open one tick so screen-change geometry settles before the surface
// maps; a synchronous open churns the surface and loses the blur on a switch.
function present() {
Qt.callLater(open);
}
function openWithSection(section) {
StateUtils.openWithSection(root, section);
}
+7 -14
View File
@@ -13,9 +13,6 @@ 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
@@ -164,18 +161,14 @@ Item {
id: barVariants
model: {
const prefs = root.barConfig?.screenPreferences || ["all"];
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;
if (prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all")) {
return Quickshell.screens;
}
// 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));
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;
}
delegate: DankBarWindow {
File diff suppressed because it is too large Load Diff
+6 -17
View File
@@ -38,44 +38,37 @@ 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 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.
// 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
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 ? _cornerAlignInset : _frameInsetResolved;
return hasAdjacentLeftBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
return Math.max(0, _barInsetPadding);
}
readonly property real _rightMargin: {
if (_barIsVertical)
return _edgeBaseMargin;
if (_usesFrameBarChrome)
return hasAdjacentRightBarLive ? _cornerAlignInset : _frameInsetResolved;
return hasAdjacentRightBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetExtra;
return Math.max(0, _barInsetPadding);
}
readonly property real _topMargin: {
if (!_barIsVertical)
return 0;
if (_usesFrameBarChrome)
return hasAdjacentTopBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
return hasAdjacentTopBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
return Math.max(0, _barInsetPadding);
}
readonly property real _bottomMargin: {
if (!_barIsVertical)
return 0;
if (_usesFrameBarChrome)
return hasAdjacentBottomBarLive ? (_edgeBaseMargin + SettingsData.frameBarSize + _frameInsetExtra) : _frameInsetResolved;
return hasAdjacentBottomBarLive ? (outlineThickness + SettingsData.frameThickness + _frameInsetExtra) : _frameInsetExtra;
return Math.max(0, _barInsetPadding);
}
@@ -684,7 +677,6 @@ Item {
LeftSection {
id: hLeftSection
objectName: "leftSection"
edgeIsScreenEdge: !topBarContent.hasAdjacentLeftBarLive
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
@@ -718,7 +710,6 @@ Item {
RightSection {
id: hRightSection
objectName: "rightSection"
edgeIsScreenEdge: !topBarContent.hasAdjacentRightBarLive
overrideAxisLayout: true
forceVerticalLayout: false
anchors {
@@ -791,7 +782,6 @@ Item {
LeftSection {
id: vLeftSection
objectName: "leftSection"
edgeIsScreenEdge: !topBarContent.hasAdjacentTopBarLive
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
@@ -860,7 +850,6 @@ Item {
RightSection {
id: vRightSection
objectName: "rightSection"
edgeIsScreenEdge: !topBarContent.hasAdjacentBottomBarLive
overrideAxisLayout: true
forceVerticalLayout: true
width: parent.width
@@ -930,8 +930,7 @@ Item {
if (_barHovered)
return;
const excludedBar = _barExitPending ? barWindow : null;
const staleOverExitedBar = excludedBar?.containsGlobalPoint?.(gx, gy, 0) ?? false;
if (!staleOverExitedBar && cursorOverHoverChain(gx, gy, excludedBar))
if (cursorOverHoverChain(gx, gy, excludedBar))
return;
_barExitPending = false;
closeHoverSurfaces();
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -17,9 +17,6 @@ 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
@@ -71,7 +68,7 @@ Item {
isFirst: index === 0
isLast: index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: root.edgeIsScreenEdge
isLeftBarEdge: true
isRightBarEdge: false
}
}
@@ -118,7 +115,7 @@ Item {
isFirst: index === 0
isLast: index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: root.edgeIsScreenEdge
isTopBarEdge: true
isBottomBarEdge: false
}
}
+2 -5
View File
@@ -17,9 +17,6 @@ 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
@@ -74,7 +71,7 @@ Item {
isLast: index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
isLeftBarEdge: false
isRightBarEdge: root.edgeIsScreenEdge
isRightBarEdge: true
}
}
}
@@ -121,7 +118,7 @@ Item {
isLast: index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing
isTopBarEdge: false
isBottomBarEdge: root.edgeIsScreenEdge
isBottomBarEdge: true
}
}
}
@@ -63,8 +63,8 @@ BasePill {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onPressed: function (mouse) {
root.triggerRipple(this, mouse.x, mouse.y);
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
onClicked: function (mouse) {
switch (mouse.button) {
case Qt.RightButton:
openContextMenu();
+835 -44
View File
@@ -1,6 +1,8 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import qs.Common
import qs.Services
@@ -8,8 +10,7 @@ import qs.Widgets
Variants {
id: dockVariants
// Connected frame mode renders the dock inside the frame surface; skip the standalone window there.
model: SettingsData.getFilteredScreens("dock").filter(screen => !CompositorService.frameHostsDockForScreen(screen))
model: SettingsData.getFilteredScreens("dock")
property var contextMenu
property var trashContextMenu
@@ -17,74 +18,864 @@ Variants {
delegate: PanelWindow {
id: dock
property var modelData: item
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)
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
visible: {
if (CompositorService.isNiri && NiriService.inOverview) {
return SettingsData.dockOpenOnOverview;
}
return SettingsData.showDock;
}
color: "transparent"
implicitWidth: body.surfaceImplicitWidth
implicitHeight: body.surfaceImplicitHeight
exclusiveZone: body.surfaceExclusiveZone
readonly property real dockReserveZone: dockGeometry.reserveZone
readonly property bool shouldReserveDockSpace: dockGeometry.shouldReserveSpace
WindowBlur {
targetWindow: dock
blurEnabled: body.effectiveBlurEnabled && !body.usesConnectedFrameChrome
blurX: body.blurX
blurY: body.blurY
blurWidth: body.blurWidth
blurHeight: body.blurHeight
blurRadius: body.blurRadius
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)
}
mask: Region {
item: body.inputMaskItem
}
DockBody {
id: body
anchors.fill: parent
hostWindow: dock
modelData: dock.modelData
contextMenu: dockVariants.contextMenu
trashContextMenu: dockVariants.trashContextMenu
item: maskItem
}
PanelWindow {
id: dockExclusion
screen: dock.screen || dock.modelData
visible: body.frameDockExclusionActive && body.shouldReserveDockSpace
visible: dock.frameDockExclusionActive && dock.shouldReserveDockSpace
color: "transparent"
mask: Region {}
implicitWidth: body.isVertical ? body.dockReserveZone : 1
implicitHeight: body.isVertical ? 1 : body.dockReserveZone
exclusiveZone: visible ? body.dockReserveZone : -1
implicitWidth: dock.isVertical ? dock.dockReserveZone : 1
implicitHeight: dock.isVertical ? 1 : dock.dockReserveZone
exclusiveZone: visible ? dock.dockReserveZone : -1
WlrLayershell.namespace: "dms:dock-exclusion"
WlrLayershell.layer: 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)
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
}
}
}
}
}
+2 -4
View File
@@ -479,8 +479,6 @@ Item {
delegate: Item {
id: delegateItem
required property var modelData
required property int index
property var dockButton: {
switch (itemData.type) {
@@ -601,7 +599,7 @@ Item {
height: delegateItem.height
actualIconSize: root.iconSize
dockApps: root
index: delegateItem.index
index: model.index
}
DockTrashButton {
@@ -626,7 +624,7 @@ Item {
appData: itemData
contextMenu: root.contextMenu
dockApps: root
index: delegateItem.index
index: model.index
parentDockScreen: root.dockScreen
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
windowTitle: {
-864
View File
@@ -1,864 +0,0 @@
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,11 +61,6 @@ 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;
@@ -73,14 +68,14 @@ PanelWindow {
case SettingsData.Position.Left:
{
const dockTopMargin = Math.round((screen.height - dockWindow.height) / 2);
x = frameHosted ? (dockBgPos.x + actualDockWidth + dockMargin + 20) : (actualDockWidth + dockMargin + 20);
x = 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 = frameHosted ? (dockBgPos.x - dockMargin - 20) : (screen.width - actualDockWidth - dockMargin - 20);
x = screen.width - actualDockWidth - dockMargin - 20;
y = dockTopMargin + buttonPosInDock.y + anchorItem.height / 2;
break;
}
@@ -88,7 +83,7 @@ PanelWindow {
{
const dockLeftMargin = Math.round((screen.width - dockWindow.width) / 2);
x = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2;
y = frameHosted ? (dockBgPos.y + actualDockHeight + dockMargin + 20) : (actualDockHeight + dockMargin + 20);
y = actualDockHeight + dockMargin + 20;
break;
}
case SettingsData.Position.Bottom:
@@ -96,7 +91,7 @@ PanelWindow {
{
const dockLeftMargin = Math.round((screen.width - dockWindow.width) / 2);
x = dockLeftMargin + buttonPosInDock.x + anchorItem.width / 2;
y = frameHosted ? (dockBgPos.y - dockMargin - 20) : (screen.height - actualDockHeight - dockMargin - 20);
y = screen.height - actualDockHeight - dockMargin - 20;
break;
}
}
-114
View File
@@ -1,114 +0,0 @@
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)
}
}
}
}
}
@@ -1,30 +0,0 @@
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
}
}
+7 -32
View File
@@ -16,33 +16,15 @@ Scope {
return SettingsData.getActiveBarEdgesForScreen(screen);
}
// 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.
// One thin invisible PanelWindow per edge.
// Skips any edge where a bar already provides its own exclusiveZone.
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 {
readonly property bool isBarEdge: root.barEdges.includes("top")
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("top"))
active: root.screenEnabled && !root.barEdges.includes("top")
sourceComponent: EdgeExclusion {
targetScreen: root.screen
exclusionSize: root.exclusionSizeForEdge("top")
anchorTop: true
anchorLeft: true
anchorRight: true
@@ -50,11 +32,9 @@ Scope {
}
Loader {
readonly property bool isBarEdge: root.barEdges.includes("bottom")
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("bottom"))
active: root.screenEnabled && !root.barEdges.includes("bottom")
sourceComponent: EdgeExclusion {
targetScreen: root.screen
exclusionSize: root.exclusionSizeForEdge("bottom")
anchorBottom: true
anchorLeft: true
anchorRight: true
@@ -62,11 +42,9 @@ Scope {
}
Loader {
readonly property bool isBarEdge: root.barEdges.includes("left")
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("left"))
active: root.screenEnabled && !root.barEdges.includes("left")
sourceComponent: EdgeExclusion {
targetScreen: root.screen
exclusionSize: root.exclusionSizeForEdge("left")
anchorLeft: true
anchorTop: true
anchorBottom: true
@@ -74,11 +52,9 @@ Scope {
}
Loader {
readonly property bool isBarEdge: root.barEdges.includes("right")
active: root.screenEnabled && (!isBarEdge || root.hostsBandForEdge("right"))
active: root.screenEnabled && !root.barEdges.includes("right")
sourceComponent: EdgeExclusion {
targetScreen: root.screen
exclusionSize: root.exclusionSizeForEdge("right")
anchorRight: true
anchorTop: true
anchorBottom: true
@@ -87,7 +63,6 @@ Scope {
component EdgeExclusion: PanelWindow {
required property var targetScreen
property real exclusionSize: SettingsData.frameThickness
screen: targetScreen
property bool anchorTop: false
@@ -97,7 +72,7 @@ Scope {
WlrLayershell.namespace: "dms:frame-exclusion"
WlrLayershell.layer: WlrLayer.Top
exclusiveZone: exclusionSize
exclusiveZone: SettingsData.frameThickness
color: "transparent"
mask: Region {}
implicitWidth: 1
+1 -95
View File
@@ -32,41 +32,7 @@ PanelWindow {
}
color: "transparent"
// 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
}
}
mask: Region {}
readonly property var barEdges: {
SettingsData.barConfigs;
@@ -85,16 +51,6 @@ 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"))
@@ -235,33 +191,6 @@ 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)
@@ -1120,12 +1049,10 @@ PanelWindow {
}
Component.onCompleted: {
KeyboardFocus.registerBarWindow(win);
win._scheduleBlurRebuild();
win._scheduleSurfaceRefresh();
}
Component.onDestruction: {
KeyboardFocus.unregisterBarWindow(win);
blurRebuildAction.cancel();
surfaceRefreshAction.cancel();
win._teardownBlur();
@@ -1174,25 +1101,4 @@ 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
}
}
}
+1 -5
View File
@@ -10,7 +10,6 @@ PanelWindow {
id: root
property bool active: false
property bool _completed: false
signal fadeCompleted
signal fadeCancelled
@@ -37,8 +36,7 @@ PanelWindow {
opacity: 0
onOpacityChanged: {
if (opacity >= 0.99 && root.active && !root._completed) {
root._completed = true;
if (opacity >= 0.99 && root.active) {
root.fadeCompleted();
}
}
@@ -61,7 +59,6 @@ PanelWindow {
function startFade() {
if (!SettingsData.fadeToDpmsEnabled)
return;
_completed = false;
active = true;
fadeOverlay.opacity = 0.0;
fadeSeq.stop();
@@ -77,7 +74,6 @@ PanelWindow {
fadeSeq.stop();
fadeOverlay.opacity = 0.0;
active = false;
_completed = false;
}
Connections {
+1 -19
View File
@@ -36,7 +36,6 @@ Item {
property int hyprlandLayoutCount: 0
property bool lockerReadySent: false
property bool lockerReadyArmed: false
property var sessionLock: null
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
readonly property string lockFontFamily: SettingsData.lockScreenFontFamily
@@ -113,8 +112,6 @@ Item {
}
function sendLockerReadyOnce() {
if (root.demoMode)
return;
if (lockerReadySent)
return;
if (root.unlocking)
@@ -137,27 +134,12 @@ Item {
return;
if (!root.visible || root.opacity <= 0)
return;
// Don't report ready until the compositor has confirmed the session is
// locked (ext-session-lock `locked` event). Qt's afterRendering fires
// before the lock surface is committed/presented, so releasing the sleep
// inhibitor on it lets the machine freeze with the desktop still on screen,
// which then flashes on resume. secure=true guarantees the desktop is hidden.
if (root.sessionLock && !root.sessionLock.secure)
return;
Qt.callLater(() => {
if (root.visible && root.opacity > 0 && !root.unlocking)
sendLockerReadyOnce();
});
}
Connections {
target: root.sessionLock
enabled: target !== null
function onSecureChanged() {
root.maybeSend();
}
}
Connections {
target: root.Window.window
enabled: target !== null
@@ -797,7 +779,7 @@ Item {
return "passkey";
if (pam.fprint.tries >= SettingsData.maxFprintTries)
return "fingerprint_off";
if (pam.fprint.active || pam.fprint.retrying)
if (pam.fprint.active)
return "fingerprint";
if (pam.u2f.active)
return "passkey";
-1
View File
@@ -33,7 +33,6 @@ FocusScope {
anchors.fill: parent
demoMode: false
sessionLock: root.lock
pam: root.pam
passwordBuffer: root.sharedPasswordBuffer
screenName: root.screenName
+1 -8
View File
@@ -262,11 +262,9 @@ Scope {
property bool available: SettingsData.lockFingerprintReady
property int tries
property int errorTries
property bool retrying: false
function checkAvail(): void {
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam) {
retrying = false;
abort();
return;
}
@@ -275,7 +273,6 @@ Scope {
tries = 0;
errorTries = 0;
retrying = false;
start();
}
@@ -288,7 +285,6 @@ Scope {
switch (res) {
case PamResult.Success:
retrying = false;
if (!root.unlockInProgress) {
passwd.abort();
root.proceedAfterPrimaryAuth();
@@ -297,16 +293,13 @@ Scope {
case PamResult.Error:
errorTries++;
if (errorTries < 200) {
retrying = true;
abort();
errorRetry.restart();
return;
}
retrying = false;
abort();
return;
case PamResult.MaxTries:
retrying = false;
tries++;
if (tries < SettingsData.maxFprintTries) {
root.fprintState = "fail";
@@ -425,7 +418,7 @@ Scope {
Timer {
id: errorRetry
interval: Math.min(1500 * Math.pow(2, Math.max(0, fprint.errorTries - 1)), 30000)
interval: 1500
onTriggered: fprint.start()
}
@@ -61,11 +61,6 @@ DankPopout {
notificationHistoryVisible = !notificationHistoryVisible;
}
// Re-open without toggling the flag (used when retargeting to another monitor).
function present() {
openSized();
}
function openSized() {
if (!notificationHistoryVisible)
return;
@@ -45,14 +45,6 @@ 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"
-10
View File
@@ -42,11 +42,6 @@ DankOSD {
width: parent.width - Theme.spacingS * 2
height: 40
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
@@ -130,11 +125,6 @@ DankOSD {
anchors.fill: parent
property int gap: Theme.spacingS
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
-10
View File
@@ -81,11 +81,6 @@ DankOSD {
width: parent.width - Theme.spacingS * 2
height: 40
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
@@ -152,11 +147,6 @@ DankOSD {
anchors.fill: parent
property int gap: Theme.spacingS
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
-10
View File
@@ -65,11 +65,6 @@ DankOSD {
width: parent.width - Theme.spacingS * 2
height: 40
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
@@ -142,11 +137,6 @@ DankOSD {
anchors.fill: parent
property int gap: Theme.spacingS
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
-10
View File
@@ -60,11 +60,6 @@ DankOSD {
width: parent.width - Theme.spacingS * 2
height: 40
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
@@ -137,11 +132,6 @@ DankOSD {
anchors.fill: parent
property int gap: Theme.spacingS
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
@@ -267,24 +267,6 @@ 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,8 +1256,6 @@ 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.")
@@ -1,412 +0,0 @@
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,15 +844,6 @@ 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 {
+11 -59
View File
@@ -12,50 +12,6 @@ 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)
@@ -185,9 +141,6 @@ Singleton {
}
function getBarWindowForScreen(screenName) {
if (frameHostedBars[screenName])
return frameHostedBars[screenName];
if (!dankBarRepeater)
return null;
@@ -217,18 +170,17 @@ Singleton {
}
function getFirstBarWindow() {
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];
}
}
if (!dankBarRepeater || dankBarRepeater.count === 0)
return null;
for (const screenName in frameHostedBars) {
if (frameHostedBars[screenName])
return frameHostedBars[screenName];
}
return null;
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];
}
}
+8 -19
View File
@@ -15,10 +15,8 @@ Item {
property string socketPath: ""
readonly property bool socketFound: socketPath.length > 0
property bool connected: false
readonly property bool binaryExists: launchKind.length > 0
property bool binaryExists: false
property bool binaryChecked: false
// "dcal" for a host install, "flatpak" for a sandboxed one, "" for neither.
property string launchKind: ""
property var calendars: []
property var events: []
@@ -57,20 +55,18 @@ Item {
Process {
id: binaryCheck
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"]
command: ["sh", "-c", "command -v dcal"]
running: false
stdout: StdioCollector {
onStreamFinished: {
root.launchKind = text.trim();
root.binaryChecked = true;
}
onExited: code => {
root.binaryExists = (code === 0);
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}\"/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"]
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"]
stdout: StdioCollector {
onStreamFinished: {
@@ -102,16 +98,9 @@ Item {
}
function launch() {
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:
if (!binaryExists)
return;
}
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
if (enabled && !connected)
discoverProcess.running = true;
}
+45 -19
View File
@@ -533,6 +533,43 @@ 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;
@@ -593,6 +630,12 @@ 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 = {};
@@ -601,7 +644,7 @@ Singleton {
const name = screens[i]?.name;
if (!name)
continue;
const blocked = hyprlandSpecialWorkspaceBlocksConnectedFrame(name);
const blocked = _computeConnectedFrameBlocked(name);
next[name] = blocked;
if (frameBlockedByScreen[name] !== blocked)
changed = true;
@@ -625,7 +668,7 @@ Singleton {
const cached = frameBlockedByScreen[screenName];
if (cached !== undefined)
return cached;
return hyprlandSpecialWorkspaceBlocksConnectedFrame(screenName);
return _computeConnectedFrameBlocked(screenName);
}
Connections {
@@ -687,23 +730,6 @@ 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);
}
+3 -1
View File
@@ -56,7 +56,7 @@ Singleton {
return "wifi_1_bar";
}
readonly property string userPreference: SettingsData.networkPreference
property string userPreference: "auto"
property bool isConnecting: false
property string connectingSSID: ""
property string connectionError: ""
@@ -210,6 +210,7 @@ Singleton {
}
Component.onCompleted: {
root.userPreference = SettingsData.networkPreference;
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
if (socketPath && socketPath.length > 0) {
checkDMSCapabilities();
@@ -731,6 +732,7 @@ Singleton {
function setNetworkPreference(preference) {
if (!networkAvailable)
return;
userPreference = preference;
changingPreference = true;
targetPreference = preference;
SettingsData.set("networkPreference", preference);
+1 -8
View File
@@ -69,7 +69,7 @@ Singleton {
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate"]
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
@@ -335,7 +335,6 @@ Singleton {
const data = response.result.data;
if (service === "server") {
const prevCapabilities = capabilities;
apiVersion = data.apiVersion || 0;
cliVersion = data.cliVersion || "";
capabilities = data.capabilities || [];
@@ -347,12 +346,6 @@ 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") {
+5 -37
View File
@@ -3,13 +3,10 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Singleton {
id: root
readonly property bool wantsLocation: SettingsData.weatherEnabled && SettingsData.useAutoLocation
readonly property bool locationAvailable: DMSService.isConnected && DMSService.capabilities.includes("location")
readonly property bool valid: latitude !== 0 || longitude !== 0
@@ -18,48 +15,21 @@ Singleton {
signal locationChanged(var data)
onWantsLocationChanged: {
if (wantsLocation) {
ensureSubscription();
} else if (DMSService.activeSubscriptions.includes("location")) {
DMSService.removeSubscription("location");
}
onLocationAvailableChanged: {
if (locationAvailable && !valid)
getState();
}
onLocationAvailableChanged: ensureSubscription()
Component.onCompleted: ensureSubscription()
Connections {
target: DMSService
function onConnectionStateChanged() {
if (DMSService.isConnected)
root.ensureSubscription();
}
function onLocationStateUpdate(data) {
if (!root.wantsLocation)
if (!locationAvailable)
return;
root.handleStateUpdate(data);
handleStateUpdate(data);
}
}
function ensureSubscription() {
if (!wantsLocation)
return;
if (!locationAvailable)
return;
if (DMSService.activeSubscriptions.includes("location"))
return;
if (DMSService.activeSubscriptions.includes("all"))
return;
DMSService.addSubscription("location");
if (!valid)
getState();
}
function handleStateUpdate(data) {
const lat = data.latitude;
const lon = data.longitude;
@@ -72,8 +42,6 @@ Singleton {
}
function getState() {
if (!wantsLocation)
return;
if (!locationAvailable)
return;
+1 -1
View File
@@ -473,7 +473,7 @@ Singleton {
function reloadConfig(showToast, suppressWatch) {
if (!CompositorService.isMango || !root.available)
return;
const shouldShowToast = showToast !== false && SessionData.showConfigReloadToast;
const shouldShowToast = showToast !== false;
const shouldSuppressWatch = suppressWatch !== false;
if (shouldSuppressWatch)
suppressWatchedConfigReloads(1500);
+2 -149
View File
@@ -73,7 +73,6 @@ Singleton {
Component.onCompleted: {
fetchOutputs();
Paths.mkdir(screenshotsDir);
generateNiriInputConfig();
}
Timer {
@@ -106,15 +105,6 @@ Singleton {
}
}
Connections {
target: CompositorService
function onIsNiriChanged() {
if (CompositorService.isNiri) {
generateNiriInputConfig();
}
}
}
Process {
id: validateProcess
command: ["niri", "validate"]
@@ -185,20 +175,6 @@ 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: ""
@@ -633,7 +609,7 @@ Singleton {
configGenerationAction.schedule();
configReloaded();
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression && SessionData.showConfigReloadToast) {
if (hasInitialConnection && !suppressConfigToast && !suppressNextConfigToast && !matugenSuppression) {
ToastService.showInfo(I18n.tr("niri: config reloaded"), "", "", "niri-config");
} else if (suppressNextConfigToast) {
suppressNextConfigToast = false;
@@ -1285,7 +1261,7 @@ window-rule {
writeAlttabProcess.running = true;
}
for (const name of ["outputs", "binds", "cursor", "windowrules", "colors", "alttab", "layout", "input"]) {
for (const name of ["outputs", "binds", "cursor", "windowrules", "colors", "alttab", "layout"]) {
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)
@@ -1665,129 +1641,6 @@ 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) {
+1 -1
View File
@@ -777,7 +777,7 @@ Singleton {
return 5000;
// expireTimeout is in milliseconds; -1 defers to our settings.
const appTimeout = wrapper.notification.expireTimeout;
if (appTimeout >= 0 && !SettingsData.notificationIgnoreAppTimeout)
if (appTimeout >= 0)
return Math.round(appTimeout);
switch (wrapper.urgency) {
case NotificationUrgency.Low:
+1 -1
View File
@@ -425,7 +425,7 @@ Singleton {
}
function closeSettings() {
settingsModal?.hide();
settingsModal?.close();
}
function toggleSettings() {
+1 -1
View File
@@ -117,7 +117,7 @@ Singleton {
Timer {
id: colorSchemeSettleTimer
interval: 750
interval: 1000
onTriggered: {
if (!root.canSyncColorScheme())
return;
+12 -18
View File
@@ -79,9 +79,6 @@ Item {
"rightBar": 0
})
property var screen: null
// Output the surface binds to; retargeted only in open() while hidden. A live
// screen change on a mapped surface loses the popup blur on niri.
property var surfaceScreen: null
readonly property bool frameGapStandaloneActive: CompositorService.frameConfiguredForScreen(screen) && !CompositorService.usesConnectedFrameChromeForScreen(screen)
readonly property bool fluidStandaloneActive: Theme.isDirectionalEffect
readonly property bool backgroundDismissWindowRequired: backgroundInteractive
@@ -136,6 +133,8 @@ Item {
signal popoutClosed
signal backgroundClicked
property var _lastOpenedScreen: null
property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0
readonly property string autoBarShadowDirection: {
@@ -215,9 +214,6 @@ 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
@@ -239,9 +235,6 @@ Item {
return;
_blurCommitSuppress = true;
blurCommitPulseTimer.restart();
// Land the off->on toggle on a commit even after _overlayActive has dropped.
_bgCommitWindow = true;
bgCommitSettleTimer.restart();
}
Connections {
@@ -345,14 +338,14 @@ Item {
_frozenMaskWidth = maskWidth;
_frozenMaskHeight = maskHeight;
const screenChanged = surfaceScreen !== null && surfaceScreen !== screen;
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Unmap before retargeting so the surface is destroyed and recreated
// on the new output rather than live-migrated.
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false;
backgroundWindow.visible = false;
}
surfaceScreen = screen;
_lastOpenedScreen = screen;
if (contentContainer && !shouldBeVisible) {
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
@@ -566,12 +559,13 @@ Item {
PanelWindow {
id: backgroundWindow
screen: root.surfaceScreen
screen: root.screen
visible: false
color: "transparent"
// 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)
// 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)
WlrLayershell.namespace: root.layerNamespace + ":background"
WlrLayershell.layer: root.effectivePopoutLayer
@@ -631,7 +625,7 @@ Item {
PanelWindow {
id: contentWindow
screen: root.surfaceScreen
screen: root.screen
visible: false
color: "transparent"
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
@@ -1,5 +1,3 @@
/* BEGIN DMS OVERRIDE */
/*
* GTK Colors
* Generated with Matugen
@@ -48,5 +46,3 @@ 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,5 +1,3 @@
/* BEGIN DMS OVERRIDE */
/*
* GTK Colors
* Generated with Matugen
@@ -7822,5 +7820,3 @@ 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 */
+85 -222
View File
@@ -1,251 +1,114 @@
#!/usr/bin/env bash
CONFIG_DIR="$1"
# apply: setup symlinks and config dirs
# patch: refresh overrides in adw-gtk3
# remove: remove all overrides
MODE="${2:-apply}"
IS_LIGHT="${3:-light}"
SHELL_DIR="$3"
if [ -z "$CONFIG_DIR" ]; then
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2
exit 1
echo "Usage: $0 <config_dir> [is_light] [shell_dir]" >&2
exit 1
fi
get_adw_gtk3_dir() {
local variant="$1"
local name=""
[ "$variant" == "dark" ] && name="-$variant"
# 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"
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"
}
if [ -e "$assets_link" ] && [ ! -L "$assets_link" ]; then
echo "Leaving user-managed $assets_link in place"
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 $?
}
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_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
ln -sfn "$target" "$assets_link"
echo "Linked GTK3 assets: $assets_link -> $target"
}
apply_gtk3_colors() {
local config_dir="$1"
local config_dir="$1"
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"
local gtk3_dir="$config_dir/gtk-3.0"
local dank_colors="$gtk3_dir/dank-colors.css"
local gtk_css="$gtk3_dir/gtk.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 "$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
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
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
ln -s "dank-colors.css" "$gtk_css"
echo "Created symlink: $gtk_css -> dank-colors.css"
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"
}
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"
# 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
apply_gtk3_colors "$CONFIG_DIR"
apply_gtk4_colors "$CONFIG_DIR"
mkdir -p "$CONFIG_DIR/gtk-3.0" "$CONFIG_DIR/gtk-4.0"
echo "GTK colors applied successfully"
;;
*)
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2
exit 1
;;
esac
apply_gtk3_colors "$CONFIG_DIR"
apply_gtk4_colors "$CONFIG_DIR"
echo "GTK colors applied successfully"
@@ -772,34 +772,6 @@
],
"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",
@@ -6150,34 +6122,6 @@
"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",
@@ -6571,26 +6515,18 @@
"tabIndex": 17,
"category": "Notifications",
"keywords": [
"above",
"alerts",
"always",
"duration",
"durations",
"even",
"longer",
"low",
"messages",
"notification",
"notifications",
"priority",
"requests",
"shorter",
"timeout",
"timeouts",
"toast"
],
"icon": "timer",
"description": "Always use the durations above, even if an app requests a shorter or longer one"
"icon": "timer"
},
{
"section": "osdAlwaysShowValue",
@@ -9133,32 +9069,6 @@
],
"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",
@@ -9370,26 +9280,6 @@
],
"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",
@@ -10026,18 +9916,5 @@
"widgets"
],
"icon": "dashboard"
},
{
"section": "_tab_44",
"label": "Mouse & Touchpad",
"tabIndex": 44,
"category": "Settings",
"keywords": [
"mouse",
"settings",
"touchpad"
],
"icon": "mouse",
"conditionKey": "isNiri"
}
]