1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 07:22:50 -05:00

Compare commits

...

69 Commits

Author SHA1 Message Date
Flux
a7cdb39b0b labwc patch (#1391) 2026-01-16 09:52:13 -05:00
bbedward
0ceba92a23 i18n: more RTL fixes across settings 2026-01-16 09:52:13 -05:00
bbedward
4daa7a4c88 popout: fix cross-monitor handling of widgets fixes #1364 2026-01-16 09:52:13 -05:00
bbedward
cc4a6a5899 doctor: add mango and labwc to compositors fixes #1394 2026-01-16 09:52:13 -05:00
bbedward
994947477c greeter: remove WLR_DRM_DEVICES setting fixes #1393 2026-01-16 09:52:13 -05:00
bbedward
311817ee97 dankbar: fix property preservation in widgets fixes #1392 2026-01-16 09:52:13 -05:00
bbedward
b80c73f9b9 weather: fix precipitationw weekly propability fixes #1395 2026-01-16 09:52:13 -05:00
bbedward
a85101c099 plugins: ensure daemon plugins not instantiated twice 2026-01-16 09:52:13 -05:00
bbedward
3513d57e06 cc: fixed width column, remove anchoring from individual icons on vbar maybe #1376 2026-01-16 09:52:13 -05:00
Lucas
1234847abb nix: fix home module (#1387) 2026-01-16 09:52:13 -05:00
Bailey
0ed595b43d nix: Support specifying systemd target (#1385) 2026-01-16 09:52:13 -05:00
Ivan Molodetskikh
060cbefc79 Add screencast indicator for niri (#1361)
* niri: Handle new Cast events

* bar: Add screen sharing indicator

Configurable like other icons; on by default.

* lockscreen: Add screen sharing indicator
2026-01-16 09:52:10 -05:00
bbedward
e022c04519 bump version 2026-01-15 23:45:21 -05:00
bbedward
f534384e5e cc: wrap icons in fixed size containers
maybe #1376
2026-01-15 23:45:09 -05:00
bbedward
a25cdb43d5 controlcenter: fix visibility condition of no icons fixes #1377 2026-01-15 23:45:09 -05:00
purian23
4e9b4ca400 Fix fedora version format 2026-01-15 23:44:19 -05:00
bbedward
5bab1c98b1 plugins: fix plugin confirm third part repo window 2026-01-15 23:44:19 -05:00
purian23
2284bb002f distro: Update Fedora dynamic versioning 2026-01-15 23:44:19 -05:00
purian23
b0611d6104 feat: Allow more pinned services in Control Center/Settings 2026-01-15 23:44:19 -05:00
purian23
27965862d6 core: Update ghostty on dankinstall 2026-01-15 23:44:19 -05:00
Abhinav Chalise
e74a901e05 fix volume osd sliding ui update for vertical layout (#1382) 2026-01-15 23:44:19 -05:00
bbedward
77794deb2c widgets: add fallback for steam apps 2026-01-15 23:44:19 -05:00
Lucas
1c10746e50 doctor: use dbus for checking on services (#1384)
* doctor: use dbus for checking on services

* doctor: show docs URL for failed checks

* core: remove unused function
2026-01-15 23:44:19 -05:00
bbedward
8ecb7282b9 dankdash: fix weather open IPC fixes #1367 2026-01-15 23:44:19 -05:00
bbedward
9b3fa804ab matugen: fix nvim ID in skipTemplates 2026-01-15 23:44:19 -05:00
purian23
b2ad31a27e dankdash: Center Media Art & Controls 2026-01-15 23:44:19 -05:00
bbedward
db17e4cb14 i18n: update terms 2026-01-15 23:44:02 -05:00
bbedward
1b7dcf56a8 bump VERSION 2026-01-14 08:00:15 -05:00
bbedward
502bb88e92 modals: fix wifi passowrd, polkit, and VPN import 2026-01-14 08:00:05 -05:00
bbedward
b76d0ce97d settings: fix child windows on newer quickshell-git 2026-01-13 16:58:08 -05:00
bbedward
fa66d330cf bump VERSION 2026-01-13 16:42:49 -05:00
Lucas
157eab2d07 settings: fix modal not opening on latest quickshell (#1357) 2026-01-13 16:42:38 -05:00
Lucas
f50ad2dc22 nix: escape version string (#1353) 2026-01-13 16:42:38 -05:00
bbedward
cd9d92d884 update changelog link and VERSION 2026-01-13 08:31:50 -05:00
Lucas
1b69a5e62b nix: add wtype dependency (#1346) 2026-01-13 08:27:46 -05:00
bbedward
61d311b157 widgets: fix running apps positioning and popup manager 2026-01-13 08:26:29 -05:00
bbedward
6b76b86930 notifications: remove redundant trimStored and add null safety 2026-01-12 23:37:49 -05:00
bbedward
dcfb947c36 desktop widgets: sync position across screens option, clickthrough
option, grouping in settings, repositioning, new IPCs for control
fixes #1300
fixes #1301
2026-01-12 15:31:34 -05:00
bbedward
59893b7f44 notifications: use Theme.primary to represent do not distrub in bar 2026-01-12 11:57:42 -05:00
bbedward
d2c62f5533 matugen: add support for vscode-insiders 2026-01-12 11:46:29 -05:00
bbedward
2bbe9a0c45 core/wlcontext: use infinite poll timeout 2026-01-12 11:26:35 -05:00
bbedward
4e2ce82c0a notifications: swipe to dismiss on history 2026-01-12 11:08:22 -05:00
bbedward
104762186f widgets: respect radius for inactive DankButtonGroup i tems 2026-01-12 10:26:50 -05:00
bbedward
f1233ab1e3 matugen: add post_hook for mango 2026-01-12 10:05:19 -05:00
bbedward
d6b407ec37 settings: fix wallpaper preview cache update on per-mode change 2026-01-12 09:58:58 -05:00
bbedward
022b4b4bb3 enable changelog 2026-01-12 09:46:50 -05:00
bbedward
49b322582d keybinds: fix sh, fix screenshot-window options, empty args
part of #914
2026-01-12 09:35:30 -05:00
bbedward
1280bd047d settings: fix sidebar binding when clicked by emitting signal 2026-01-11 22:43:29 -05:00
bbedward
6f206d7523 dankdash: fix 24H format in weather tab
fixes #1283
2026-01-11 21:45:28 -05:00
bbedward
2e58283859 dgop: use used mem directly from API
- conditionally because it depends on newer dgop
2026-01-11 17:32:36 -05:00
Marcus Ramberg
99a5721fe8 settings: extract tab headings for search (#1333)
* settings: extract tab headings for search

* fix pre-commit

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2026-01-11 17:14:45 -05:00
bbedward
5302ebd840 notifications: spacing improvements
fixes #1241
2026-01-11 14:35:34 -05:00
bbedward
fa427ea1ac settings: fix clipping of generic color selector
fixes #1242
2026-01-11 14:04:48 -05:00
bbedward
7027bd1646 systemtray: use Theme radius for menu options
fixes #1331
2026-01-11 14:03:23 -05:00
bbedward
3c38e17472 notifications: add compact mode, expansion in history, expansion in
popup
fixes #1282
2026-01-11 12:11:44 -05:00
shalevc1098
510ea5d2e4 feat: configurable app id substitutions (#1317)
* feat: add configurable app ID substitutions setting

* feat: add live icon updates when substitutions change

* fix: cursor not showing on headerActions in non-collapsible cards

* fix: address PR review feedback

- add tags for search index
- remove hardcoded height from text fields
2026-01-10 21:00:15 -05:00
bbedward
bb2234d328 cc: dont show preference flip if not on ethernet and wifi 2026-01-10 10:35:48 -05:00
bbedward
edbdeb0fb8 widgets: add artix and void NF mappings 2026-01-10 10:18:09 -05:00
Kostiantyn To
19541fc573 update-service: add Artix Linux to supported distributions list (#1318) 2026-01-10 10:18:00 -05:00
bbedward
7c936cacfb niri: fix effectiveScreenAssignment in modal 2026-01-10 10:13:41 -05:00
bbedward
c60cd3a341 modals/auth: add show password option
fixes #1311
2026-01-09 22:20:18 -05:00
shalevc1098
e37135f80d feat: map steam_app_ID to steam_icon_ID for actual game icons (#1312)
Steam Proton games use window class steam_app_XXXXX. Steam installs
icons as steam_icon_XXXXX. This maps between them so actual game
icons display instead of generic controller fallback.
2026-01-09 21:40:35 -05:00
bbedward
aac937cbcc settingns: fix missing help text on desktop widgets 2026-01-09 19:07:37 -05:00
bbedward
4b46d022af workspaces: add color options, add focus follows monitor, remove
per-monitor option (was misleading)
relevant to #1207
2026-01-09 14:10:57 -05:00
bbedward
7f0181b310 matugen/vscode: fix selection contrast 2026-01-09 10:16:03 -05:00
bbedward
6a109274f8 hyprland: always use single window 2026-01-09 09:57:31 -05:00
bbedward
0f09cc693a lock: handle case where session lock is rejected 2026-01-09 09:46:39 -05:00
bbedward
af0166a553 dankbar: add bar get/setPosition IPC 2026-01-09 00:09:49 -05:00
bbedward
a283017f26 audio: recreate media players on pipewire device change 2026-01-08 23:35:42 -05:00
117 changed files with 6919 additions and 3193 deletions

1
.gitignore vendored
View File

@@ -109,3 +109,4 @@ bin/
.envrc .envrc
.direnv/ .direnv/
quickshell/dms-plugins quickshell/dms-plugins
__pycache__

View File

@@ -87,6 +87,8 @@ var (
swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`) swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`)
riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`) riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`)
wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`) wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`)
labwcVersionRegex = regexp.MustCompile(`labwc (\d+\.\d+\.\d+)`)
mangowcVersionRegex = regexp.MustCompile(`mango (\d+\.\d+\.\d+)`)
) )
var doctorCmd = &cobra.Command{ var doctorCmd = &cobra.Command{
@@ -448,11 +450,13 @@ func checkWindowManagers() []checkResult {
versionRegex *regexp.Regexp versionRegex *regexp.Regexp
commands []string commands []string
}{ }{
{"Hyprland", "hyprctl", "version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}}, {"Hyprland", "Hyprland", "--version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}},
{"niri", "niri", "--version", niriVersionRegex, []string{"niri"}}, {"niri", "niri", "--version", niriVersionRegex, []string{"niri"}},
{"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}}, {"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}},
{"River", "river", "-version", riverVersionRegex, []string{"river"}}, {"River", "river", "-version", riverVersionRegex, []string{"river"}},
{"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}}, {"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}},
{"labwc", "labwc", "--version", labwcVersionRegex, []string{"labwc"}},
{"mangowc", "mango", "-v", mangowcVersionRegex, []string{"mango"}},
} }
var results []checkResult var results []checkResult
@@ -477,7 +481,7 @@ func checkWindowManagers() []checkResult {
results = append(results, checkResult{ results = append(results, checkResult{
catCompositor, c.name, statusOK, catCompositor, c.name, statusOK,
getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details, getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details,
doctorDocsURL + "#compositor", doctorDocsURL + "#compositor-checks",
}) })
} }
@@ -486,7 +490,7 @@ func checkWindowManagers() []checkResult {
catCompositor, "Compositor", statusError, catCompositor, "Compositor", statusError,
"No supported Wayland compositor found", "No supported Wayland compositor found",
"Install Hyprland, niri, Sway, River, or Wayfire", "Install Hyprland, niri, Sway, River, or Wayfire",
doctorDocsURL + "#compositor", doctorDocsURL + "#compositor-checks",
}) })
} }
@@ -498,8 +502,8 @@ func checkWindowManagers() []checkResult {
} }
func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string { func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string {
output, err := exec.Command(cmd, arg).Output() output, err := exec.Command(cmd, arg).CombinedOutput()
if err != nil { if err != nil && len(output) == 0 {
return "installed" return "installed"
} }
@@ -634,19 +638,14 @@ func checkI2CAvailability() checkResult {
return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"} return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"}
} }
func detectNetworkBackend() string { func detectNetworkBackend(stackResult *network.DetectResult) string {
result, err := network.DetectNetworkStack() switch stackResult.Backend {
if err != nil {
return ""
}
switch result.Backend {
case network.BackendNetworkManager: case network.BackendNetworkManager:
return "NetworkManager" return "NetworkManager"
case network.BackendIwd: case network.BackendIwd:
return "iwd" return "iwd"
case network.BackendNetworkd: case network.BackendNetworkd:
if result.HasIwd { if stackResult.HasIwd {
return "iwd + systemd-networkd" return "iwd + systemd-networkd"
} }
return "systemd-networkd" return "systemd-networkd"
@@ -657,75 +656,73 @@ func detectNetworkBackend() string {
} }
} }
func getOptionalDBusStatus(busName string) (status, string) {
if utils.IsDBusServiceAvailable(busName) {
return statusOK, "Available"
} else {
return statusWarn, "Not available"
}
}
func checkOptionalDependencies() []checkResult { func checkOptionalDependencies() []checkResult {
var results []checkResult var results []checkResult
if utils.IsServiceActive("accounts-daemon", false) { optionalFeaturesURL := doctorDocsURL + "#optional-features"
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusOK, "Running", "User accounts", doctorDocsURL + "#optional-features"})
} else {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusWarn, "Not running", "User accounts", doctorDocsURL + "#optional-features"})
}
if utils.IsServiceActive("power-profiles-daemon", false) { accountsStatus, accountsMsg := getOptionalDBusStatus("org.freedesktop.Accounts")
results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusOK, "Running", "Power profile management", doctorDocsURL + "#optional-features"}) results = append(results, checkResult{catOptionalFeatures, "accountsservice", accountsStatus, accountsMsg, "User accounts", optionalFeaturesURL})
} else {
results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusInfo, "Not running", "Power profile management", doctorDocsURL + "#optional-features"}) ppdStatus, ppdMsg := getOptionalDBusStatus("org.freedesktop.UPower.PowerProfiles")
} results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", ppdStatus, ppdMsg, "Power profile management", optionalFeaturesURL})
logindStatus, logindMsg := getOptionalDBusStatus("org.freedesktop.login1")
results = append(results, checkResult{catOptionalFeatures, "logind", logindStatus, logindMsg, "Session management", optionalFeaturesURL})
results = append(results, checkI2CAvailability()) results = append(results, checkI2CAvailability())
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"} terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 { if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", doctorDocsURL + "#optional-features"}) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL})
} else { } else {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", doctorDocsURL + "#optional-features"}) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", optionalFeaturesURL})
} }
networkResult, err := network.DetectNetworkStack()
networkStatus, networkMessage, networkDetails := statusOK, "Not available", "Network management"
if err == nil && networkResult.Backend != network.BackendNone {
networkMessage = detectNetworkBackend(networkResult)
if doctorVerbose {
networkDetails = networkResult.ChosenReason
}
} else {
networkStatus = statusInfo
}
results = append(results, checkResult{catOptionalFeatures, "Network", networkStatus, networkMessage, networkDetails, optionalFeaturesURL})
deps := []struct { deps := []struct {
name, cmd, altCmd, desc string name, cmd, desc string
important bool important bool
}{ }{
{"matugen", "matugen", "", "Dynamic theming", true}, {"matugen", "matugen", "Dynamic theming", true},
{"dgop", "dgop", "", "System monitoring", true}, {"dgop", "dgop", "System monitoring", true},
{"cava", "cava", "", "Audio visualizer", true}, {"cava", "cava", "Audio visualizer", true},
{"khal", "khal", "", "Calendar events", false}, {"khal", "khal", "Calendar events", false},
{"Network", "nmcli", "iwctl", "Network management", false}, {"danksearch", "dsearch", "File search", false},
{"danksearch", "dsearch", "", "File search", false}, {"fprintd", "fprintd-list", "Fingerprint auth", false},
{"loginctl", "loginctl", "", "Session management", false},
{"fprintd", "fprintd-list", "", "Fingerprint auth", false},
} }
for _, d := range deps { for _, d := range deps {
found, foundCmd := utils.CommandExists(d.cmd), d.cmd found := utils.CommandExists(d.cmd)
if !found && d.altCmd != "" && utils.CommandExists(d.altCmd) {
found, foundCmd = true, d.altCmd
}
switch { switch {
case found: case found:
message := "Installed" results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, "Installed", d.desc, optionalFeaturesURL})
details := d.desc
if d.name == "Network" {
result, err := network.DetectNetworkStack()
if err == nil && result.Backend != network.BackendNone {
message = detectNetworkBackend() + " (active)"
if doctorVerbose {
details = result.ChosenReason
}
} else {
switch foundCmd {
case "nmcli":
message = "NetworkManager (installed)"
case "iwctl":
message = "iwd (installed)"
}
}
}
results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, message, details, doctorDocsURL + "#optional-features"})
case d.important: case d.important:
results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc, doctorDocsURL + "#optional-features"}) results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc, optionalFeaturesURL})
default: default:
results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, doctorDocsURL + "#optional-features"}) results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, optionalFeaturesURL})
} }
} }
@@ -893,6 +890,10 @@ func printResultLine(r checkResult, styles tui.Styles) {
if doctorVerbose && r.details != "" { if doctorVerbose && r.details != "" {
fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details)) fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details))
} }
if (r.status == statusError || r.status == statusWarn) && r.url != "" {
fmt.Printf(" %s\n", styles.Subtle.Render("→ "+r.url))
}
} }
func printSummary(results []checkResult, qsMissingFeatures bool) { func printSummary(results []checkResult, qsMissingFeatures bool) {

View File

@@ -8,6 +8,7 @@ bind = SUPER, N, exec, dms ipc call notifications toggle
bind = SUPER SHIFT, N, exec, dms ipc call notepad toggle bind = SUPER SHIFT, N, exec, dms ipc call notepad toggle
bind = SUPER, Y, exec, dms ipc call dankdash wallpaper bind = SUPER, Y, exec, dms ipc call dankdash wallpaper
bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview
bind = SUPER, X, exec, dms ipc call powermenu toggle
# === Cheat sheet # === Cheat sheet
bind = SUPER SHIFT, Slash, exec, dms ipc call keybinds toggle hyprland bind = SUPER SHIFT, Slash, exec, dms ipc call keybinds toggle hyprland

View File

@@ -15,6 +15,8 @@ binds {
Mod+M hotkey-overlay-title="Task Manager" { Mod+M hotkey-overlay-title="Task Manager" {
spawn "dms" "ipc" "call" "processlist" "focusOrToggle"; spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
} }
Super+X hotkey-overlay-title="Power Menu: Toggle" { spawn "dms" "ipc" "call" "powermenu" "toggle"; }
Mod+Comma hotkey-overlay-title="Settings" { Mod+Comma hotkey-overlay-title="Settings" {
spawn "dms" "ipc" "call" "settings" "focusOrToggle"; spawn "dms" "ipc" "call" "settings" "focusOrToggle";
} }

View File

@@ -108,7 +108,6 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
packages := map[string]PackageMapping{ packages := map[string]PackageMapping{
// Standard zypper packages // Standard zypper packages
"git": {Name: "git", Repository: RepoTypeSystem}, "git": {Name: "git", Repository: RepoTypeSystem},
"ghostty": {Name: "ghostty", Repository: RepoTypeSystem},
"kitty": {Name: "kitty", Repository: RepoTypeSystem}, "kitty": {Name: "kitty", Repository: RepoTypeSystem},
"alacritty": {Name: "alacritty", Repository: RepoTypeSystem}, "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
"xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem}, "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
@@ -117,6 +116,7 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
// DMS packages from OBS // DMS packages from OBS
"dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]), "dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
"quickshell": o.getQuickshellMapping(variants["quickshell"]), "quickshell": o.getQuickshellMapping(variants["quickshell"]),
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
} }

View File

@@ -325,24 +325,30 @@ func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string {
} }
actionNode := bindNode.Children[0] actionNode := bindNode.Children[0]
actionName := actionNode.Name.String()
kdlStr := strings.TrimSpace(actionNode.String()) if actionName == "" {
if kdlStr == "" {
return "" return ""
} }
return n.kdlActionToInternal(kdlStr) parts := []string{actionName}
} for _, arg := range actionNode.Arguments {
val := arg.ValueString()
func (n *NiriProvider) kdlActionToInternal(kdlAction string) string { if val == "" {
parts := n.parseActionParts(kdlAction) parts = append(parts, `""`)
if len(parts) == 0 { } else {
return kdlAction parts = append(parts, val)
}
} }
for i, part := range parts { if actionNode.Properties != nil {
if part == "" { if val, ok := actionNode.Properties.Get("focus"); ok {
parts[i] = `""` parts = append(parts, "focus="+val.String())
}
if val, ok := actionNode.Properties.Get("show-pointer"); ok {
parts = append(parts, "show-pointer="+val.String())
}
if val, ok := actionNode.Properties.Get("write-to-disk"); ok {
parts = append(parts, "write-to-disk="+val.String())
} }
} }

View File

@@ -314,6 +314,7 @@ output_path = '%s'
appendVSCodeConfig(cfgFile, "codeoss", filepath.Join(homeDir, ".config/Code - OSS/extensions"), opts.ShellDir) appendVSCodeConfig(cfgFile, "codeoss", filepath.Join(homeDir, ".config/Code - OSS/extensions"), opts.ShellDir)
appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir) appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir)
appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions"), opts.ShellDir) appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions"), opts.ShellDir)
appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
default: default:
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile) appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
} }

View File

@@ -124,27 +124,23 @@ func (sc *SharedContext) eventDispatcher() {
} }
for { for {
sc.drainCmdQueue()
select { select {
case <-sc.stopChan: case <-sc.stopChan:
return return
default: default:
} }
sc.drainCmdQueue() _, err := unix.Poll(pollFds, -1)
switch {
n, err := unix.Poll(pollFds, 50) case err == unix.EINTR:
if err != nil { continue
if err == unix.EINTR { case err != nil:
continue
}
log.Errorf("Poll error: %v", err) log.Errorf("Poll error: %v", err)
return return
} }
if n == 0 {
continue
}
if pollFds[1].Revents&unix.POLLIN != 0 { if pollFds[1].Revents&unix.POLLIN != 0 {
var buf [64]byte var buf [64]byte
if _, err := unix.Read(sc.wakeR, buf[:]); err != nil && err != unix.EAGAIN { if _, err := unix.Read(sc.wakeR, buf[:]); err != nil && err != unix.EAGAIN {
@@ -152,13 +148,13 @@ func (sc *SharedContext) eventDispatcher() {
} }
} }
if pollFds[0].Revents&unix.POLLIN != 0 { if pollFds[0].Revents&unix.POLLIN == 0 {
if err := ctx.Dispatch(); err != nil { continue
if !os.IsTimeout(err) { }
log.Errorf("Wayland connection error: %v", err)
return if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) {
} log.Errorf("Wayland connection error: %v", err)
} return
} }
} }
} }
@@ -176,12 +172,16 @@ func (sc *SharedContext) drainCmdQueue() {
func (sc *SharedContext) Close() { func (sc *SharedContext) Close() {
close(sc.stopChan) close(sc.stopChan)
if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN {
log.Errorf("wake pipe write error on close: %v", err)
}
sc.wg.Wait() sc.wg.Wait()
unix.Close(sc.wakeR) unix.Close(sc.wakeR)
unix.Close(sc.wakeW) unix.Close(sc.wakeW)
if sc.display != nil { if sc.display == nil {
sc.display.Context().Close() return
} }
sc.display.Context().Close()
} }

View File

@@ -0,0 +1,20 @@
package utils
import (
"github.com/godbus/dbus/v5"
)
func IsDBusServiceAvailable(busName string) bool {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return false
}
defer conn.Close()
obj := conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
var owned bool
if err := obj.Call("org.freedesktop.DBus.NameHasOwner", 0, busName).Store(&owned); err != nil {
return false
}
return owned
}

View File

@@ -2,7 +2,6 @@ package utils
import ( import (
"os/exec" "os/exec"
"strings"
) )
type AppChecker interface { type AppChecker interface {
@@ -43,16 +42,3 @@ func AnyCommandExists(cmds ...string) bool {
} }
return false return false
} }
func IsServiceActive(name string, userService bool) bool {
if !CommandExists("systemctl") {
return false
}
args := []string{"is-active", name}
if userService {
args = []string{"--user", "is-active", name}
}
output, _ := exec.Command("systemctl", args...).Output()
return strings.EqualFold(strings.TrimSpace(string(output)), "active")
}

View File

@@ -19,7 +19,8 @@ in
] ]
++ lib.optional cfg.enableDynamicTheming pkgs.matugen ++ lib.optional cfg.enableDynamicTheming pkgs.matugen
++ lib.optional cfg.enableAudioWavelength pkgs.cava ++ lib.optional cfg.enableAudioWavelength pkgs.cava
++ lib.optional cfg.enableCalendarEvents pkgs.khal; ++ lib.optional cfg.enableCalendarEvents pkgs.khal
++ lib.optional cfg.enableClipboardPaste pkgs.wtype;
plugins = lib.mapAttrs (name: plugin: { plugins = lib.mapAttrs (name: plugin: {
source = plugin.src; source = plugin.src;

View File

@@ -11,12 +11,18 @@ let
inherit (config.services.greetd.settings.default_session) user; inherit (config.services.greetd.settings.default_session) user;
compositorPackage =
let
configured = lib.attrByPath [ "programs" cfg.compositor.name "package" ] null config;
in
if configured != null then configured else builtins.getAttr cfg.compositor.name pkgs;
cacheDir = "/var/lib/dms-greeter"; cacheDir = "/var/lib/dms-greeter";
greeterScript = pkgs.writeShellScriptBin "dms-greeter" '' greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
export PATH=$PATH:${ export PATH=$PATH:${
lib.makeBinPath [ lib.makeBinPath [
cfg.quickshell.package cfg.quickshell.package
config.programs.${cfg.compositor.name}.package compositorPackage
] ]
} }
${ ${
@@ -64,6 +70,7 @@ in
"niri" "niri"
"hyprland" "hyprland"
"sway" "sway"
"labwc"
]; ];
description = "Compositor to run greeter in"; description = "Compositor to run greeter in";
}; };

View File

@@ -73,6 +73,13 @@ in
default = hasPluginSettings; default = hasPluginSettings;
description = ''Whether to manage plugin settings. Automatically enabled if any plugins have settings configured.''; description = ''Whether to manage plugin settings. Automatically enabled if any plugins have settings configured.'';
}; };
systemd.target = lib.mkOption {
type = lib.types.str;
default = config.wayland.systemd.target;
defaultText = lib.literalExpression "config.wayland.systemd.target";
description = "Systemd target to bind to.";
};
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
@@ -84,8 +91,8 @@ in
systemd.user.services.dms = lib.mkIf cfg.systemd.enable { systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
Unit = { Unit = {
Description = "DankMaterialShell"; Description = "DankMaterialShell";
PartOf = [ config.wayland.systemd.target ]; PartOf = [ cfg.systemd.target ];
After = [ config.wayland.systemd.target ]; After = [ cfg.systemd.target ];
}; };
Service = { Service = {
@@ -93,7 +100,7 @@ in
Restart = "on-failure"; Restart = "on-failure";
}; };
Install.WantedBy = [ config.wayland.systemd.target ]; Install.WantedBy = [ cfg.systemd.target ];
}; };
xdg.stateFile."DankMaterialShell/session.json" = lib.mkIf (cfg.session != { }) { xdg.stateFile."DankMaterialShell/session.json" = lib.mkIf (cfg.session != { }) {

View File

@@ -20,15 +20,19 @@ in
imports = [ imports = [
(import ./options.nix args) (import ./options.nix args)
]; ];
options.programs.dank-material-shell.systemd.target = lib.mkOption {
type = lib.types.str;
description = "Systemd target to bind to.";
default = "graphical-session.target";
};
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
systemd.user.services.dms = lib.mkIf cfg.systemd.enable { systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
description = "DankMaterialShell"; description = "DankMaterialShell";
path = lib.mkForce [ ]; path = lib.mkForce [ ];
partOf = [ "graphical-session.target" ]; partOf = [ cfg.systemd.target ];
after = [ "graphical-session.target" ]; after = [ cfg.systemd.target ];
wantedBy = [ "graphical-session.target" ]; wantedBy = [ cfg.systemd.target ];
restartIfChanged = cfg.systemd.restartIfChanged; restartIfChanged = cfg.systemd.restartIfChanged;
serviceConfig = { serviceConfig = {

View File

@@ -70,6 +70,12 @@ in
description = "Add calendar events support via khal"; description = "Add calendar events support via khal";
}; };
enableClipboardPaste = lib.mkOption {
type = types.bool;
default = true;
description = "Adds needed dependencies for directly pasting items from the clipboard history.";
};
quickshell = { quickshell = {
package = lib.mkPackageOption dmsPkgs "quickshell" { package = lib.mkPackageOption dmsPkgs "quickshell" {
extraDescription = "The quickshell package to use (defaults to be built from source, due to unreleased features used by DMS)."; extraDescription = "The quickshell package to use (defaults to be built from source, due to unreleased features used by DMS).";

View File

@@ -61,11 +61,13 @@
(builtins.substring 6 2 longDate) (builtins.substring 6 2 longDate)
]; ];
version = version =
pkgs.lib.removePrefix "v" (pkgs.lib.trim (builtins.readFile ./quickshell/VERSION)) let
+ "+date=" rawVersion = pkgs.lib.removePrefix "v" (pkgs.lib.trim (builtins.readFile ./quickshell/VERSION));
+ mkDate (self.lastModifiedDate or "19700101") cleanVersion = builtins.replaceStrings [ " " ] [ "" ] rawVersion;
+ "_" dateSuffix = "+date=" + mkDate (self.lastModifiedDate or "19700101");
+ (self.shortRev or "dirty"); revSuffix = "_" + (self.shortRev or "dirty");
in
"${cleanVersion}${dateSuffix}${revSuffix}";
in in
{ {
dms-shell = pkgs.buildGoModule ( dms-shell = pkgs.buildGoModule (
@@ -83,7 +85,7 @@
ldflags = [ ldflags = [
"-s" "-s"
"-w" "-w"
"-X main.Version=${version}" "-X 'main.Version=${version}'"
]; ];
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [

View File

@@ -450,10 +450,7 @@ const NIRI_ACTION_ARGS = {
] ]
}, },
"screenshot-window": { "screenshot-window": {
args: [ args: [{ name: "write-to-disk", type: "bool", label: "Save to disk" }]
{ name: "show-pointer", type: "bool", label: "Show pointer" },
{ name: "write-to-disk", type: "bool", label: "Save to disk" }
]
} }
}; };
@@ -841,7 +838,7 @@ function getActionType(action) {
return "compositor"; return "compositor";
if (action.startsWith("spawn dms ipc call ")) if (action.startsWith("spawn dms ipc call "))
return "dms"; return "dms";
if (action.startsWith("spawn sh -c ") || action.startsWith("spawn bash -c ") || action.startsWith("spawn_shell ")) if (/^spawn \w+ -c /.test(action) || action.startsWith("spawn_shell "))
return "shell"; return "shell";
if (action.startsWith("spawn ")) if (action.startsWith("spawn "))
return "spawn"; return "spawn";
@@ -888,12 +885,13 @@ function buildSpawnAction(command, args) {
return "spawn " + parts.join(" "); return "spawn " + parts.join(" ");
} }
function buildShellAction(compositor, shellCmd) { function buildShellAction(compositor, shellCmd, shell) {
if (!shellCmd) if (!shellCmd)
return ""; return "";
if (compositor === "mangowc") if (compositor === "mangowc")
return "spawn_shell " + shellCmd; return "spawn_shell " + shellCmd;
return "spawn sh -c \"" + shellCmd.replace(/"/g, "\\\"") + "\""; var shellBin = shell || "sh";
return "spawn " + shellBin + " -c \"" + shellCmd.replace(/"/g, "\\\"") + "\"";
} }
function parseSpawnCommand(action) { function parseSpawnCommand(action) {
@@ -910,8 +908,9 @@ function parseSpawnCommand(action) {
function parseShellCommand(action) { function parseShellCommand(action) {
if (!action) if (!action)
return ""; return "";
if (action.startsWith("spawn sh -c ")) { var match = action.match(/^spawn (\w+) -c (.+)$/);
var content = action.slice(12); if (match) {
var content = match[2];
if ((content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'"))) if ((content.startsWith('"') && content.endsWith('"')) || (content.startsWith("'") && content.endsWith("'")))
content = content.slice(1, -1); content = content.slice(1, -1);
return content.replace(/\\"/g, "\""); return content.replace(/\\"/g, "\"");
@@ -921,6 +920,13 @@ function parseShellCommand(action) {
return ""; return "";
} }
function getShellFromAction(action) {
if (!action)
return "sh";
var match = action.match(/^spawn (\w+) -c /);
return match ? match[1] : "sh";
}
function getActionArgConfig(compositor, action) { function getActionArgConfig(compositor, action) {
if (!action) if (!action)
return null; return null;
@@ -1107,12 +1113,27 @@ function buildCompositorAction(compositor, base, args) {
parts.push("focus=false"); parts.push("focus=false");
break; break;
default: default:
if (base.startsWith("screenshot")) { switch (base) {
case "screenshot":
if (args["show-pointer"] === true) if (args["show-pointer"] === true)
parts.push("show-pointer=true"); parts.push("show-pointer=true");
else if (args["show-pointer"] === false)
parts.push("show-pointer=false");
break;
case "screenshot-screen":
if (args["show-pointer"] === true)
parts.push("show-pointer=true");
else if (args["show-pointer"] === false)
parts.push("show-pointer=false");
if (args["write-to-disk"] === true) if (args["write-to-disk"] === true)
parts.push("write-to-disk=true"); parts.push("write-to-disk=true");
} else if (args.value) { break;
case "screenshot-window":
if (args["write-to-disk"] === true)
parts.push("write-to-disk=true");
break;
}
if (args.value) {
parts.push(args.value); parts.push(args.value);
} else if (args.index) { } else if (args.index) {
parts.push(args.index); parts.push(args.index);

View File

@@ -13,17 +13,16 @@ Singleton {
property var currentModalsByScreen: ({}) property var currentModalsByScreen: ({})
function openModal(modal) { function openModal(modal) {
if (!modal.allowStacking) {
closeAllModalsExcept(modal);
}
if (!modal.keepPopoutsOpen) {
PopoutManager.closeAllPopouts();
}
TrayMenuManager.closeAllMenus();
const screenName = modal.effectiveScreen?.name ?? "unknown"; const screenName = modal.effectiveScreen?.name ?? "unknown";
currentModalsByScreen[screenName] = modal; currentModalsByScreen[screenName] = modal;
modalChanged(); modalChanged();
Qt.callLater(() => {
if (!modal.allowStacking)
closeAllModalsExcept(modal);
if (!modal.keepPopoutsOpen)
PopoutManager.closeAllPopouts();
TrayMenuManager.closeAllMenus();
});
} }
function closeModal(modal) { function closeModal(modal) {

View File

@@ -45,20 +45,28 @@ Singleton {
Quickshell.execDetached(["cp", strip(from), strip(to)]); Quickshell.execDetached(["cp", strip(from), strip(to)]);
} }
function isSteamApp(appId: string): bool {
return appId && /^steam_app_\d+$/.test(appId);
}
function moddedAppId(appId: string): string { function moddedAppId(appId: string): string {
if (appId === "Spotify") const subs = SettingsData.appIdSubstitutions || [];
return "spotify"; for (let i = 0; i < subs.length; i++) {
if (appId === "beepertexts") const sub = subs[i];
return "beeper"; if (sub.type === "exact" && appId === sub.pattern) {
if (appId === "home assistant desktop") return sub.replacement;
return "homeassistant-desktop"; } else if (sub.type === "contains" && appId.includes(sub.pattern)) {
if (appId.includes("com.transmissionbt.transmission")) { return sub.replacement;
if (DesktopEntries.heuristicLookup("transmission-gtk")) } else if (sub.type === "regex") {
return "transmission-gtk"; const match = appId.match(new RegExp(sub.pattern));
if (DesktopEntries.heuristicLookup("transmission")) if (match) {
return "transmission"; return sub.replacement.replace(/\$(\d+)/g, (_, n) => match[n] || "");
return "transmission-gtk"; }
}
} }
const steamMatch = appId.match(/^steam_app_(\d+)$/);
if (steamMatch)
return `steam_icon_${steamMatch[1]}`;
return appId; return appId;
} }
@@ -68,8 +76,8 @@ Singleton {
} }
const moddedId = moddedAppId(appId); const moddedId = moddedAppId(appId);
if (moddedId.toLowerCase().includes("steam_app")) { if (moddedId !== appId) {
return ""; return Quickshell.iconPath(moddedId, true);
} }
return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : ""; return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : "";

View File

@@ -82,15 +82,19 @@ Singleton {
popoutOpening(); popoutOpening();
} }
let justClosedSamePopout = false; let movedFromOtherScreen = false;
for (const otherScreenName in currentPopoutsByScreen) { for (const otherScreenName in currentPopoutsByScreen) {
if (otherScreenName === screenName) if (otherScreenName === screenName)
continue; continue;
const otherPopout = currentPopoutsByScreen[otherScreenName]; const otherPopout = currentPopoutsByScreen[otherScreenName];
if (!otherPopout) if (!otherPopout)
continue; continue;
if (otherPopout === popout) { if (otherPopout === popout) {
justClosedSamePopout = true; movedFromOtherScreen = true;
currentPopoutsByScreen[otherScreenName] = null;
currentPopoutTriggers[otherScreenName] = null;
continue;
} }
if (otherPopout.dashVisible !== undefined) { if (otherPopout.dashVisible !== undefined) {
@@ -112,7 +116,7 @@ Singleton {
} }
} }
if (currentPopout === popout && popout.shouldBeVisible) { if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) { if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
if (popout.dashVisible !== undefined) { if (popout.dashVisible !== undefined) {
popout.dashVisible = false; popout.dashVisible = false;
@@ -139,6 +143,7 @@ Singleton {
popout.currentTabIndex = tabIndex; popout.currentTabIndex = tabIndex;
} }
currentPopoutTriggers[screenName] = triggerId; currentPopoutTriggers[screenName] = triggerId;
return;
} }
currentPopoutTriggers[screenName] = triggerId; currentPopoutTriggers[screenName] = triggerId;
@@ -153,16 +158,8 @@ Singleton {
ModalManager.closeAllModalsExcept(null); ModalManager.closeAllModalsExcept(null);
} }
if (justClosedSamePopout) { if (movedFromOtherScreen) {
Qt.callLater(() => { popout.open();
if (popout.dashVisible !== undefined) {
popout.dashVisible = true;
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = true;
} else {
popout.open();
}
});
} else { } else {
if (popout.dashVisible !== undefined) { if (popout.dashVisible !== undefined) {
popout.dashVisible = true; popout.dashVisible = true;

View File

@@ -1,5 +1,5 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtCore import QtCore
import QtQuick import QtQuick
@@ -145,6 +145,7 @@ Singleton {
property bool controlCenterShowMicPercent: true property bool controlCenterShowMicPercent: true
property bool controlCenterShowBatteryIcon: false property bool controlCenterShowBatteryIcon: false
property bool controlCenterShowPrinterIcon: false property bool controlCenterShowPrinterIcon: false
property bool controlCenterShowScreenSharingIcon: true
property bool showPrivacyButton: true property bool showPrivacyButton: true
property bool privacyShowMicIcon: false property bool privacyShowMicIcon: false
property bool privacyShowCameraIcon: false property bool privacyShowCameraIcon: false
@@ -200,10 +201,16 @@ Singleton {
property bool showWorkspaceApps: false property bool showWorkspaceApps: false
property bool groupWorkspaceApps: true property bool groupWorkspaceApps: true
property int maxWorkspaceIcons: 3 property int maxWorkspaceIcons: 3
property bool workspacesPerMonitor: true property bool workspaceFollowFocus: false
property bool showOccupiedWorkspacesOnly: false property bool showOccupiedWorkspacesOnly: false
property bool reverseScrolling: false property bool reverseScrolling: false
property bool dwlShowAllTags: false property bool dwlShowAllTags: false
property string workspaceColorMode: "default"
property string workspaceUnfocusedColorMode: "default"
property string workspaceUrgentColorMode: "default"
property bool workspaceFocusedBorderEnabled: false
property string workspaceFocusedBorderColor: "primary"
property int workspaceFocusedBorderThickness: 2
property var workspaceNameIcons: ({}) property var workspaceNameIcons: ({})
property bool waveProgressEnabled: true property bool waveProgressEnabled: true
property bool scrollTitleEnabled: true property bool scrollTitleEnabled: true
@@ -215,6 +222,7 @@ Singleton {
property bool keyboardLayoutNameCompactMode: false property bool keyboardLayoutNameCompactMode: false
property bool runningAppsCurrentWorkspace: false property bool runningAppsCurrentWorkspace: false
property bool runningAppsGroupByApp: false property bool runningAppsGroupByApp: false
property var appIdSubstitutions: []
property string centeringMode: "index" property string centeringMode: "index"
property string clockDateFormat: "" property string clockDateFormat: ""
property string lockDateFormat: "" property string lockDateFormat: ""
@@ -397,6 +405,7 @@ Singleton {
property int notificationTimeoutLow: 5000 property int notificationTimeoutLow: 5000
property int notificationTimeoutNormal: 5000 property int notificationTimeoutNormal: 5000
property int notificationTimeoutCritical: 0 property int notificationTimeoutCritical: 0
property bool notificationCompactMode: false
property int notificationPopupPosition: SettingsData.Position.Top property int notificationPopupPosition: SettingsData.Position.Top
property bool notificationHistoryEnabled: true property bool notificationHistoryEnabled: true
property int notificationHistoryMaxCount: 50 property int notificationHistoryMaxCount: 50
@@ -530,6 +539,7 @@ Singleton {
property var desktopWidgetPositions: ({}) property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({}) property var desktopWidgetGridSettings: ({})
property var desktopWidgetInstances: [] property var desktopWidgetInstances: []
property var desktopWidgetGroups: []
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) { function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
const val = desktopWidgetGridSettings?.[screenKey]?.[property]; const val = desktopWidgetGridSettings?.[screenKey]?.[property];
@@ -681,6 +691,38 @@ Singleton {
saveSettings(); saveSettings();
} }
function syncDesktopWidgetPositionToAllScreens(instanceId) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return;
const positions = instances[idx].positions || {};
const screenKeys = Object.keys(positions).filter(k => k !== "_synced");
if (screenKeys.length === 0)
return;
const sourceKey = screenKeys[0];
const sourcePos = positions[sourceKey];
if (!sourcePos)
return;
const screen = Array.from(Quickshell.screens.values()).find(s => getScreenDisplayName(s) === sourceKey);
if (!screen)
return;
const screenW = screen.width;
const screenH = screen.height;
const synced = {};
if (sourcePos.x !== undefined)
synced.x = sourcePos.x / screenW;
if (sourcePos.y !== undefined)
synced.y = sourcePos.y / screenH;
if (sourcePos.width !== undefined)
synced.width = sourcePos.width;
if (sourcePos.height !== undefined)
synced.height = sourcePos.height;
instances[idx].positions["_synced"] = synced;
desktopWidgetInstances = instances;
saveSettings();
}
function duplicateDesktopWidgetInstance(instanceId) { function duplicateDesktopWidgetInstance(instanceId) {
const source = getDesktopWidgetInstance(instanceId); const source = getDesktopWidgetInstance(instanceId);
if (!source) if (!source)
@@ -713,6 +755,110 @@ Singleton {
return (desktopWidgetInstances || []).filter(inst => inst.enabled); return (desktopWidgetInstances || []).filter(inst => inst.enabled);
} }
function moveDesktopWidgetInstance(instanceId, direction) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1)
return false;
const targetIdx = direction === "up" ? idx - 1 : idx + 1;
if (targetIdx < 0 || targetIdx >= instances.length)
return false;
const temp = instances[idx];
instances[idx] = instances[targetIdx];
instances[targetIdx] = temp;
desktopWidgetInstances = instances;
saveSettings();
return true;
}
function reorderDesktopWidgetInstance(instanceId, newIndex) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const idx = instances.findIndex(inst => inst.id === instanceId);
if (idx === -1 || newIndex < 0 || newIndex >= instances.length)
return false;
const [item] = instances.splice(idx, 1);
instances.splice(newIndex, 0, item);
desktopWidgetInstances = instances;
saveSettings();
return true;
}
function reorderDesktopWidgetInstanceInGroup(instanceId, groupId, newIndexInGroup) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
const groups = desktopWidgetGroups || [];
const groupMatches = inst => {
if (groupId === null)
return !inst.group || !groups.some(g => g.id === inst.group);
return inst.group === groupId;
};
const groupInstances = instances.filter(groupMatches);
const currentGroupIdx = groupInstances.findIndex(inst => inst.id === instanceId);
if (currentGroupIdx === -1 || currentGroupIdx === newIndexInGroup)
return false;
if (newIndexInGroup < 0 || newIndexInGroup >= groupInstances.length)
return false;
const globalIdx = instances.findIndex(inst => inst.id === instanceId);
if (globalIdx === -1)
return false;
const [item] = instances.splice(globalIdx, 1);
const targetInstance = groupInstances[newIndexInGroup];
let targetGlobalIdx = instances.findIndex(inst => inst.id === targetInstance.id);
if (newIndexInGroup > currentGroupIdx)
targetGlobalIdx++;
instances.splice(targetGlobalIdx, 0, item);
desktopWidgetInstances = instances;
saveSettings();
return true;
}
function createDesktopWidgetGroup(name) {
const id = "dwg_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
const group = {
id: id,
name: name,
collapsed: false
};
const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
groups.push(group);
desktopWidgetGroups = groups;
saveSettings();
return group;
}
function updateDesktopWidgetGroup(groupId, updates) {
const groups = JSON.parse(JSON.stringify(desktopWidgetGroups || []));
const idx = groups.findIndex(g => g.id === groupId);
if (idx === -1)
return;
Object.assign(groups[idx], updates);
desktopWidgetGroups = groups;
saveSettings();
}
function removeDesktopWidgetGroup(groupId) {
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
for (let i = 0; i < instances.length; i++) {
if (instances[i].group === groupId)
instances[i].group = null;
}
desktopWidgetInstances = instances;
const groups = (desktopWidgetGroups || []).filter(g => g.id !== groupId);
desktopWidgetGroups = groups;
saveSettings();
}
function getDesktopWidgetGroup(groupId) {
return (desktopWidgetGroups || []).find(g => g.id === groupId) || null;
}
function getDesktopWidgetInstancesByGroup(groupId) {
return (desktopWidgetInstances || []).filter(inst => inst.group === groupId);
}
function getUngroupedDesktopWidgetInstances() {
return (desktopWidgetInstances || []).filter(inst => !inst.group);
}
signal forceDankBarLayoutRefresh signal forceDankBarLayoutRefresh
signal forceDockLayoutRefresh signal forceDockLayoutRefresh
signal widgetDataChanged signal widgetDataChanged
@@ -1836,6 +1982,48 @@ Singleton {
return workspaceNameIcons[workspaceName] || null; return workspaceNameIcons[workspaceName] || null;
} }
function addAppIdSubstitution(pattern, replacement, type) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
subs.push({
pattern: pattern,
replacement: replacement,
type: type
});
appIdSubstitutions = subs;
saveSettings();
}
function updateAppIdSubstitution(index, pattern, replacement, type) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
if (index < 0 || index >= subs.length)
return;
subs[index] = {
pattern: pattern,
replacement: replacement,
type: type
};
appIdSubstitutions = subs;
saveSettings();
}
function removeAppIdSubstitution(index) {
var subs = JSON.parse(JSON.stringify(appIdSubstitutions));
if (index < 0 || index >= subs.length)
return;
subs.splice(index, 1);
appIdSubstitutions = subs;
saveSettings();
}
function getDefaultAppIdSubstitutions() {
return Spec.SPEC.appIdSubstitutions.def;
}
function resetAppIdSubstitutions() {
appIdSubstitutions = JSON.parse(JSON.stringify(Spec.SPEC.appIdSubstitutions.def));
saveSettings();
}
function getRegistryThemeVariant(themeId, defaultVariant) { function getRegistryThemeVariant(themeId, defaultVariant) {
var stored = registryThemeVariants[themeId]; var stored = registryThemeVariants[themeId];
if (typeof stored === "string") if (typeof stored === "string")

View File

@@ -904,7 +904,7 @@ Singleton {
if (typeof SettingsData !== "undefined") { if (typeof SettingsData !== "undefined") {
const skipTemplates = []; const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) { if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode"); skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
} else { } else {
if (!SettingsData.matugenTemplateGtk) if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk"); skipTemplates.push("gtk");

View File

@@ -28,7 +28,8 @@ Singleton {
showMicIcon: false, showMicIcon: false,
showMicPercent: true, showMicPercent: true,
showBatteryIcon: false, showBatteryIcon: false,
showPrinterIcon: false showPrinterIcon: false,
showScreenSharingIcon: true
}; };
leftModel.append(dummy); leftModel.append(dummy);
centerModel.append(dummy); centerModel.append(dummy);
@@ -84,6 +85,8 @@ Singleton {
item.showBatteryIcon = order[i].showBatteryIcon; item.showBatteryIcon = order[i].showBatteryIcon;
if (isObj && order[i].showPrinterIcon !== undefined) if (isObj && order[i].showPrinterIcon !== undefined)
item.showPrinterIcon = order[i].showPrinterIcon; item.showPrinterIcon = order[i].showPrinterIcon;
if (isObj && order[i].showScreenSharingIcon !== undefined)
item.showScreenSharingIcon = order[i].showScreenSharingIcon;
model.append(item); model.append(item);
} }

View File

@@ -70,6 +70,7 @@ var SPEC = {
controlCenterShowMicPercent: { def: false }, controlCenterShowMicPercent: { def: false },
controlCenterShowBatteryIcon: { def: false }, controlCenterShowBatteryIcon: { def: false },
controlCenterShowPrinterIcon: { def: false }, controlCenterShowPrinterIcon: { def: false },
controlCenterShowScreenSharingIcon: { def: true },
showPrivacyButton: { def: true }, showPrivacyButton: { def: true },
privacyShowMicIcon: { def: false }, privacyShowMicIcon: { def: false },
@@ -94,10 +95,16 @@ var SPEC = {
showWorkspaceApps: { def: false }, showWorkspaceApps: { def: false },
maxWorkspaceIcons: { def: 3 }, maxWorkspaceIcons: { def: 3 },
groupWorkspaceApps: { def: true }, groupWorkspaceApps: { def: true },
workspacesPerMonitor: { def: true }, workspaceFollowFocus: { def: false },
showOccupiedWorkspacesOnly: { def: false }, showOccupiedWorkspacesOnly: { def: false },
reverseScrolling: { def: false }, reverseScrolling: { def: false },
dwlShowAllTags: { def: false }, dwlShowAllTags: { def: false },
workspaceColorMode: { def: "default" },
workspaceUnfocusedColorMode: { def: "default" },
workspaceUrgentColorMode: { def: "default" },
workspaceFocusedBorderEnabled: { def: false },
workspaceFocusedBorderColor: { def: "primary" },
workspaceFocusedBorderThickness: { def: 2 },
workspaceNameIcons: { def: {} }, workspaceNameIcons: { def: {} },
waveProgressEnabled: { def: true }, waveProgressEnabled: { def: true },
scrollTitleEnabled: { def: true }, scrollTitleEnabled: { def: true },
@@ -109,6 +116,13 @@ var SPEC = {
keyboardLayoutNameCompactMode: { def: false }, keyboardLayoutNameCompactMode: { def: false },
runningAppsCurrentWorkspace: { def: false }, runningAppsCurrentWorkspace: { def: false },
runningAppsGroupByApp: { def: false }, runningAppsGroupByApp: { def: false },
appIdSubstitutions: { def: [
{ pattern: "Spotify", replacement: "spotify", type: "exact" },
{ pattern: "beepertexts", replacement: "beeper", type: "exact" },
{ pattern: "home assistant desktop", replacement: "homeassistant-desktop", type: "exact" },
{ pattern: "com.transmissionbt.transmission", replacement: "transmission-gtk", type: "contains" },
{ pattern: "^steam_app_(\\d+)$", replacement: "steam_icon_$1", type: "regex" }
]},
centeringMode: { def: "index" }, centeringMode: { def: "index" },
clockDateFormat: { def: "" }, clockDateFormat: { def: "" },
lockDateFormat: { def: "" }, lockDateFormat: { def: "" },
@@ -255,6 +269,7 @@ var SPEC = {
notificationTimeoutLow: { def: 5000 }, notificationTimeoutLow: { def: 5000 },
notificationTimeoutNormal: { def: 5000 }, notificationTimeoutNormal: { def: 5000 },
notificationTimeoutCritical: { def: 0 }, notificationTimeoutCritical: { def: 0 },
notificationCompactMode: { def: false },
notificationPopupPosition: { def: 0 }, notificationPopupPosition: { def: 0 },
notificationHistoryEnabled: { def: true }, notificationHistoryEnabled: { def: true },
notificationHistoryMaxCount: { def: 50 }, notificationHistoryMaxCount: { def: 50 },
@@ -388,6 +403,8 @@ var SPEC = {
desktopWidgetInstances: { def: [] }, desktopWidgetInstances: { def: [] },
desktopWidgetGroups: { def: [] },
builtInPluginSettings: { def: {} } builtInPluginSettings: { def: {} }
}; };

View File

@@ -203,6 +203,8 @@ Item {
Component.onCompleted: { Component.onCompleted: {
dockRecreateDebounce.start(); dockRecreateDebounce.start();
// Force PolkitService singleton to initialize
PolkitService.polkitAvailable;
} }
Connections { Connections {
@@ -315,19 +317,44 @@ Item {
} }
} }
WifiPasswordModal { LazyLoader {
id: wifiPasswordModal id: wifiPasswordModalLoader
active: false
Component.onCompleted: { Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal; PopoutService.wifiPasswordModalLoader = wifiPasswordModalLoader;
}
WifiPasswordModal {
id: wifiPasswordModalItem
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModalItem;
}
} }
} }
PolkitAuthModal { LazyLoader {
id: polkitAuthModal id: polkitAuthModalLoader
active: false
Component.onCompleted: { PolkitAuthModal {
PopoutService.polkitAuthModal = polkitAuthModal; id: polkitAuthModal
Component.onCompleted: {
PopoutService.polkitAuthModal = polkitAuthModal;
}
}
}
Connections {
target: PolkitService.agent
enabled: PolkitService.polkitAvailable
function onAuthenticationRequestStarted() {
polkitAuthModalLoader.active = true;
if (polkitAuthModalLoader.item)
polkitAuthModalLoader.item.show();
} }
} }
@@ -349,17 +376,21 @@ Item {
const now = Date.now(); const now = Date.now();
const timeSinceLastPrompt = now - lastCredentialsTime; const timeSinceLastPrompt = now - lastCredentialsTime;
if (wifiPasswordModal.visible && timeSinceLastPrompt < 1000) { wifiPasswordModalLoader.active = true;
if (!wifiPasswordModalLoader.item)
return;
if (wifiPasswordModalLoader.item.visible && timeSinceLastPrompt < 1000) {
NetworkService.cancelCredentials(lastCredentialsToken); NetworkService.cancelCredentials(lastCredentialsToken);
lastCredentialsToken = token; lastCredentialsToken = token;
lastCredentialsTime = now; lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo); wifiPasswordModalLoader.item.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
return; return;
} }
lastCredentialsToken = token; lastCredentialsToken = token;
lastCredentialsTime = now; lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo); wifiPasswordModalLoader.item.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
} }
} }
@@ -442,17 +473,15 @@ Item {
PopoutService.settingsModalLoader = settingsModalLoader; PopoutService.settingsModalLoader = settingsModalLoader;
} }
onActiveChanged: {
if (active && item) {
PopoutService.settingsModal = item;
PopoutService._onSettingsModalLoaded();
}
}
SettingsModal { SettingsModal {
id: settingsModal id: settingsModal
property bool wasShown: false property bool wasShown: false
Component.onCompleted: {
PopoutService.settingsModal = settingsModal;
PopoutService._onSettingsModalLoaded();
}
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
wasShown = true; wasShown = true;

View File

@@ -132,8 +132,11 @@ Item {
case "media": case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1; root.dankDashPopoutLoader.item.currentTabIndex = 1;
break; break;
case "wallpaper":
root.dankDashPopoutLoader.item.currentTabIndex = 2;
break;
case "weather": case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0; root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0;
break; break;
default: default:
root.dankDashPopoutLoader.item.currentTabIndex = 0; root.dankDashPopoutLoader.item.currentTabIndex = 0;
@@ -599,6 +602,39 @@ Item {
return barConfig.autoHide ? "BAR_MANUAL_HIDE_SUCCESS" : "BAR_AUTO_HIDE_SUCCESS"; return barConfig.autoHide ? "BAR_MANUAL_HIDE_SUCCESS" : "BAR_AUTO_HIDE_SUCCESS";
} }
function getPosition(selector: string, value: string): string {
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
const positions = ["top", "bottom", "left", "right"];
return positions[barConfig.position] || "unknown";
}
function setPosition(selector: string, value: string, position: string): string {
const {
barConfig,
error
} = getBarConfig(selector, value);
if (error)
return error;
const positionMap = {
"top": SettingsData.Position.Top,
"bottom": SettingsData.Position.Bottom,
"left": SettingsData.Position.Left,
"right": SettingsData.Position.Right
};
const posValue = positionMap[position.toLowerCase()];
if (posValue === undefined)
return "BAR_INVALID_POSITION";
SettingsData.updateBarConfig(barConfig.id, {
position: posValue
});
return "BAR_POSITION_SET_SUCCESS";
}
target: "bar" target: "bar"
} }
@@ -764,11 +800,9 @@ Item {
const modal = PopoutService.settingsModal; const modal = PopoutService.settingsModal;
if (modal) { if (modal) {
if (type === "wallpaper") { if (type === "wallpaper") {
modal.wallpaperBrowser.allowStacking = false; modal.openWallpaperBrowser(false);
modal.wallpaperBrowser.open();
} else if (type === "profile") { } else if (type === "profile") {
modal.profileBrowser.allowStacking = false; modal.openProfileBrowser(false);
modal.profileBrowser.open();
} }
} else { } else {
PopoutService.openSettings(); PopoutService.openSettings();
@@ -1035,7 +1069,7 @@ Item {
const instances = SettingsData.desktopWidgetInstances || []; const instances = SettingsData.desktopWidgetInstances || [];
if (instances.length === 0) if (instances.length === 0)
return "No desktop widgets configured"; return "No desktop widgets configured";
return instances.map(i => `${i.id} [${i.widgetType}] ${i.name || i.widgetType}`).join("\n"); return instances.map(i => `${i.id} [${i.widgetType}] ${i.name || i.widgetType} ${i.enabled ? "[enabled]" : "[disabled]"}`).join("\n");
} }
function status(instanceId: string): string { function status(instanceId: string): string {
@@ -1046,9 +1080,115 @@ Item {
if (!instance) if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`; return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const enabled = instance.enabled ?? true;
const overlay = instance.config?.showOnOverlay ?? false; const overlay = instance.config?.showOnOverlay ?? false;
const overview = instance.config?.showOnOverview ?? false; const overview = instance.config?.showOnOverview ?? false;
return `overlay: ${overlay}, overview: ${overview}`; const clickThrough = instance.config?.clickThrough ?? false;
const syncPosition = instance.config?.syncPositionAcrossScreens ?? false;
return `enabled: ${enabled}, overlay: ${overlay}, overview: ${overview}, clickThrough: ${clickThrough}, syncPosition: ${syncPosition}`;
}
function enable(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
SettingsData.updateDesktopWidgetInstance(instanceId, {
enabled: true
});
return `DESKTOP_WIDGET_ENABLED: ${instanceId}`;
}
function disable(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
SettingsData.updateDesktopWidgetInstance(instanceId, {
enabled: false
});
return `DESKTOP_WIDGET_DISABLED: ${instanceId}`;
}
function toggleEnabled(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const currentValue = instance.enabled ?? true;
SettingsData.updateDesktopWidgetInstance(instanceId, {
enabled: !currentValue
});
return !currentValue ? `DESKTOP_WIDGET_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_DISABLED: ${instanceId}`;
}
function toggleClickThrough(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const currentValue = instance.config?.clickThrough ?? false;
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
clickThrough: !currentValue
});
return !currentValue ? `DESKTOP_WIDGET_CLICK_THROUGH_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_CLICK_THROUGH_DISABLED: ${instanceId}`;
}
function setClickThrough(instanceId: string, enabled: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const enabledBool = enabled === "true" || enabled === "1";
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
clickThrough: enabledBool
});
return enabledBool ? `DESKTOP_WIDGET_CLICK_THROUGH_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_CLICK_THROUGH_DISABLED: ${instanceId}`;
}
function toggleSyncPosition(instanceId: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const currentValue = instance.config?.syncPositionAcrossScreens ?? false;
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
syncPositionAcrossScreens: !currentValue
});
return !currentValue ? `DESKTOP_WIDGET_SYNC_POSITION_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_SYNC_POSITION_DISABLED: ${instanceId}`;
}
function setSyncPosition(instanceId: string, enabled: string): string {
if (!instanceId)
return "ERROR: No instance ID specified";
const instance = SettingsData.getDesktopWidgetInstance(instanceId);
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const enabledBool = enabled === "true" || enabled === "1";
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
syncPositionAcrossScreens: enabledBool
});
return enabledBool ? `DESKTOP_WIDGET_SYNC_POSITION_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_SYNC_POSITION_DISABLED: ${instanceId}`;
} }
target: "desktopWidget" target: "desktopWidget"

View File

@@ -128,7 +128,7 @@ FloatingWindow {
iconName: "open_in_new" iconName: "open_in_new"
backgroundColor: Theme.surfaceContainerHighest backgroundColor: Theme.surfaceContainerHighest
textColor: Theme.surfaceText textColor: Theme.surfaceText
onClicked: Qt.openUrlExternally("https://danklinux.com/blog/dms-1-2-spicy-miso") onClicked: Qt.openUrlExternally("https://danklinux.com/blog/v1-2-release")
} }
DankButton { DankButton {

View File

@@ -49,7 +49,7 @@ Item {
readonly property alias clickCatcher: clickCatcher readonly property alias clickCatcher: clickCatcher
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
readonly property bool useSingleWindow: useHyprlandFocusGrab || useBackground readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground
signal opened signal opened
signal dialogClosed signal dialogClosed
@@ -58,7 +58,6 @@ Item {
property bool animationsEnabled: true property bool animationsEnabled: true
function open() { function open() {
ModalManager.openModal(root);
closeTimer.stop(); closeTimer.stop();
const focusedScreen = CompositorService.getFocusedScreen(); const focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen) { if (focusedScreen) {
@@ -66,6 +65,7 @@ Item {
if (!useSingleWindow) if (!useSingleWindow)
clickCatcher.screen = focusedScreen; clickCatcher.screen = focusedScreen;
} }
ModalManager.openModal(root);
shouldBeVisible = true; shouldBeVisible = true;
if (!useSingleWindow) if (!useSingleWindow)
clickCatcher.visible = true; clickCatcher.visible = true;
@@ -302,7 +302,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.useSingleWindow enabled: root.useSingleWindow && root.shouldBeVisible
hoverEnabled: false hoverEnabled: false
acceptedButtons: Qt.AllButtons acceptedButtons: Qt.AllButtons
onPressed: mouse.accepted = true onPressed: mouse.accepted = true

View File

@@ -11,7 +11,6 @@ FloatingWindow {
property var currentFlow: PolkitService.agent?.flow property var currentFlow: PolkitService.agent?.flow
property bool isLoading: false property bool isLoading: false
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2 readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
property int calculatedHeight: Math.max(240, headerRow.implicitHeight + mainColumn.implicitHeight + Theme.spacingM * 3)
function focusPasswordField() { function focusPasswordField() {
passwordField.forceActiveFocus(); passwordField.forceActiveFocus();
@@ -37,15 +36,19 @@ FloatingWindow {
} }
function cancelAuth() { function cancelAuth() {
if (!currentFlow || isLoading) if (isLoading)
return; return;
currentFlow.cancelAuthenticationRequest(); if (currentFlow) {
currentFlow.cancelAuthenticationRequest();
return;
}
hide();
} }
objectName: "polkitAuthModal" objectName: "polkitAuthModal"
title: I18n.tr("Authentication") title: I18n.tr("Authentication")
minimumSize: Qt.size(420, calculatedHeight) minimumSize: Qt.size(460, 220)
maximumSize: Qt.size(420, calculatedHeight) maximumSize: Qt.size(460, 220)
color: Theme.surfaceContainer color: Theme.surfaceContainer
visible: false visible: false
@@ -108,29 +111,24 @@ FloatingWindow {
event.accepted = true; event.accepted = true;
} }
MouseArea {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: headerRow.height + Theme.spacingM
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
}
Item { Item {
id: headerRow id: headerSection
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.leftMargin: Theme.spacingM anchors.margins: Theme.spacingM
anchors.rightMargin: Theme.spacingM height: Math.max(titleColumn.implicitHeight, windowButtonRow.implicitHeight)
anchors.topMargin: Theme.spacingM
height: Math.max(titleColumn.height, buttonRow.height) MouseArea {
anchors.fill: parent
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
}
Column { Column {
id: titleColumn id: titleColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: buttonRow.left anchors.right: windowButtonRow.left
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -141,33 +139,34 @@ FloatingWindow {
font.weight: Font.Medium font.weight: Font.Medium
} }
Column { StyledText {
text: currentFlow?.message ?? ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width width: parent.width
spacing: Theme.spacingXS wrapMode: Text.Wrap
maximumLineCount: 2
elide: Text.ElideRight
visible: text !== ""
}
StyledText { StyledText {
text: currentFlow?.message ?? "" text: currentFlow?.supplementaryMessage ?? ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: (currentFlow?.supplementaryIsError ?? false) ? Theme.error : Theme.surfaceTextMedium
width: parent.width width: parent.width
wrapMode: Text.Wrap wrapMode: Text.Wrap
} maximumLineCount: 2
elide: Text.ElideRight
StyledText { opacity: (currentFlow?.supplementaryIsError ?? false) ? 1 : 0.8
visible: (currentFlow?.supplementaryMessage ?? "") !== "" visible: text !== ""
text: currentFlow?.supplementaryMessage ?? ""
font.pixelSize: Theme.fontSizeSmall
color: (currentFlow?.supplementaryIsError ?? false) ? Theme.error : Theme.surfaceTextMedium
width: parent.width
wrapMode: Text.Wrap
opacity: (currentFlow?.supplementaryIsError ?? false) ? 1 : 0.8
}
} }
} }
Row { Row {
id: buttonRow id: windowButtonRow
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankActionButton { DankActionButton {
@@ -190,21 +189,19 @@ FloatingWindow {
} }
Column { Column {
id: mainColumn id: bottomSection
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.leftMargin: Theme.spacingM anchors.margins: Theme.spacingM
anchors.rightMargin: Theme.spacingM spacing: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
spacing: Theme.spacingM
StyledText { StyledText {
text: currentFlow?.inputPrompt ?? "" text: currentFlow?.inputPrompt ?? ""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
width: parent.width width: parent.width
visible: (currentFlow?.inputPrompt ?? "") !== "" visible: text !== ""
} }
Rectangle { Rectangle {
@@ -229,7 +226,8 @@ FloatingWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: passwordInput text: passwordInput
echoMode: (currentFlow?.responseVisible ?? false) ? TextInput.Normal : TextInput.Password showPasswordToggle: !(currentFlow?.responseVisible ?? false)
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
placeholderText: "" placeholderText: ""
backgroundColor: "transparent" backgroundColor: "transparent"
enabled: !isLoading enabled: !isLoading
@@ -238,38 +236,17 @@ FloatingWindow {
} }
} }
Item { StyledText {
text: I18n.tr("Authentication failed, please try again")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width width: parent.width
height: (currentFlow?.failed ?? false) ? failedText.implicitHeight : 0 visible: currentFlow?.failed ?? false
visible: height > 0
StyledText {
id: failedText
text: I18n.tr("Authentication failed, please try again")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
opacity: (currentFlow?.failed ?? false) ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
Item { Item {
width: parent.width width: parent.width
height: 40 height: 36
Row { Row {
anchors.right: parent.right anchors.right: parent.right

View File

@@ -74,9 +74,7 @@ Rectangle {
if (root.parentModal) { if (root.parentModal) {
root.parentModal.allowFocusOverride = true; root.parentModal.allowFocusOverride = true;
root.parentModal.shouldHaveFocus = false; root.parentModal.shouldHaveFocus = false;
if (root.parentModal.profileBrowser) { root.parentModal.openProfileBrowser();
root.parentModal.profileBrowser.open();
}
} }
} }
} }

View File

@@ -8,8 +8,26 @@ import qs.Widgets
FloatingWindow { FloatingWindow {
id: settingsModal id: settingsModal
property alias profileBrowser: profileBrowser property var profileBrowser: profileBrowserLoader.item
property alias wallpaperBrowser: wallpaperBrowser property var wallpaperBrowser: wallpaperBrowserLoader.item
function openProfileBrowser(allowStacking) {
profileBrowserLoader.active = true;
if (!profileBrowserLoader.item)
return;
if (allowStacking !== undefined)
profileBrowserLoader.item.allowStacking = allowStacking;
profileBrowserLoader.item.open();
}
function openWallpaperBrowser(allowStacking) {
wallpaperBrowserLoader.active = true;
if (!wallpaperBrowserLoader.item)
return;
if (allowStacking !== undefined)
wallpaperBrowserLoader.item.allowStacking = allowStacking;
wallpaperBrowserLoader.item.open();
}
property alias sidebar: sidebar property alias sidebar: sidebar
property int currentTabIndex: 0 property int currentTabIndex: 0
property bool shouldHaveFocus: visible property bool shouldHaveFocus: visible
@@ -96,41 +114,51 @@ FloatingWindow {
} }
} }
FileBrowserModal { LazyLoader {
id: profileBrowser id: profileBrowserLoader
active: false
allowStacking: true FileBrowserModal {
parentModal: settingsModal id: profileBrowserItem
browserTitle: I18n.tr("Select Profile Image", "profile image file browser title")
browserIcon: "person" allowStacking: true
browserType: "profile" parentModal: settingsModal
showHiddenFiles: true browserTitle: I18n.tr("Select Profile Image", "profile image file browser title")
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] browserIcon: "person"
onFileSelected: path => { browserType: "profile"
PortalService.setProfileImage(path); showHiddenFiles: true
close(); fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
} onFileSelected: path => {
onDialogClosed: () => { PortalService.setProfileImage(path);
allowStacking = true; close();
}
onDialogClosed: () => {
allowStacking = true;
}
} }
} }
FileBrowserModal { LazyLoader {
id: wallpaperBrowser id: wallpaperBrowserLoader
active: false
allowStacking: true FileBrowserModal {
parentModal: settingsModal id: wallpaperBrowserItem
browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title")
browserIcon: "wallpaper" allowStacking: true
browserType: "wallpaper" parentModal: settingsModal
showHiddenFiles: true browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title")
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] browserIcon: "wallpaper"
onFileSelected: path => { browserType: "wallpaper"
SessionData.setWallpaper(path); showHiddenFiles: true
close(); fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
} onFileSelected: path => {
onDialogClosed: () => { SessionData.setWallpaper(path);
allowStacking = true; close();
}
onDialogClosed: () => {
allowStacking = true;
}
} }
} }
@@ -319,8 +347,8 @@ FloatingWindow {
visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true visible: settingsModal.isCompactMode ? settingsModal.menuVisible : true
parentModal: settingsModal parentModal: settingsModal
currentIndex: settingsModal.currentTabIndex currentIndex: settingsModal.currentTabIndex
onCurrentIndexChanged: { onTabChangeRequested: tabIndex => {
settingsModal.currentTabIndex = currentIndex; settingsModal.currentTabIndex = tabIndex;
if (settingsModal.isCompactMode) { if (settingsModal.isCompactMode) {
settingsModal.enableAnimations = true; settingsModal.enableAnimations = true;
settingsModal.menuVisible = false; settingsModal.menuVisible = false;

View File

@@ -15,6 +15,8 @@ Rectangle {
property int currentIndex: 0 property int currentIndex: 0
property var parentModal: null property var parentModal: null
signal tabChangeRequested(int tabIndex)
property var expandedCategories: ({}) property var expandedCategories: ({})
property var autoExpandedCategories: ({}) property var autoExpandedCategories: ({})
property bool searchActive: searchField.text.length > 0 property bool searchActive: searchField.text.length > 0
@@ -55,8 +57,9 @@ Rectangle {
if (keyboardHighlightIndex < 0) if (keyboardHighlightIndex < 0)
return; return;
var oldIndex = currentIndex; var oldIndex = currentIndex;
currentIndex = keyboardHighlightIndex; var newIndex = keyboardHighlightIndex;
autoCollapseIfNeeded(oldIndex, currentIndex); tabChangeRequested(newIndex);
autoCollapseIfNeeded(oldIndex, newIndex);
keyboardHighlightIndex = -1; keyboardHighlightIndex = -1;
Qt.callLater(searchField.forceActiveFocus); Qt.callLater(searchField.forceActiveFocus);
} }
@@ -398,28 +401,32 @@ Rectangle {
var flatItems = getFlatNavigableItems(); var flatItems = getFlatNavigableItems();
var currentPos = flatItems.findIndex(item => item.tabIndex === currentIndex); var currentPos = flatItems.findIndex(item => item.tabIndex === currentIndex);
var oldIndex = currentIndex; var oldIndex = currentIndex;
var newIndex;
if (currentPos === -1) { if (currentPos === -1) {
currentIndex = flatItems[0]?.tabIndex ?? 0; newIndex = flatItems[0]?.tabIndex ?? 0;
} else { } else {
var nextPos = (currentPos + 1) % flatItems.length; var nextPos = (currentPos + 1) % flatItems.length;
currentIndex = flatItems[nextPos].tabIndex; newIndex = flatItems[nextPos].tabIndex;
} }
autoCollapseIfNeeded(oldIndex, currentIndex); tabChangeRequested(newIndex);
autoExpandForTab(currentIndex); autoCollapseIfNeeded(oldIndex, newIndex);
autoExpandForTab(newIndex);
} }
function navigatePrevious() { function navigatePrevious() {
var flatItems = getFlatNavigableItems(); var flatItems = getFlatNavigableItems();
var currentPos = flatItems.findIndex(item => item.tabIndex === currentIndex); var currentPos = flatItems.findIndex(item => item.tabIndex === currentIndex);
var oldIndex = currentIndex; var oldIndex = currentIndex;
var newIndex;
if (currentPos === -1) { if (currentPos === -1) {
currentIndex = flatItems[0]?.tabIndex ?? 0; newIndex = flatItems[0]?.tabIndex ?? 0;
} else { } else {
var prevPos = (currentPos - 1 + flatItems.length) % flatItems.length; var prevPos = (currentPos - 1 + flatItems.length) % flatItems.length;
currentIndex = flatItems[prevPos].tabIndex; newIndex = flatItems[prevPos].tabIndex;
} }
autoCollapseIfNeeded(oldIndex, currentIndex); tabChangeRequested(newIndex);
autoExpandForTab(currentIndex); autoCollapseIfNeeded(oldIndex, newIndex);
autoExpandForTab(newIndex);
} }
function getFlatNavigableItems() { function getFlatNavigableItems() {
@@ -488,7 +495,7 @@ Rectangle {
SettingsSearchService.navigateToSection(result.section); SettingsSearchService.navigateToSection(result.section);
} }
var oldIndex = root.currentIndex; var oldIndex = root.currentIndex;
root.currentIndex = result.tabIndex; tabChangeRequested(result.tabIndex);
autoCollapseIfNeeded(oldIndex, result.tabIndex); autoCollapseIfNeeded(oldIndex, result.tabIndex);
autoExpandForTab(result.tabIndex); autoExpandForTab(result.tabIndex);
searchField.text = ""; searchField.text = "";
@@ -807,7 +814,7 @@ Rectangle {
if (categoryDelegate.modelData.children) { if (categoryDelegate.modelData.children) {
root.toggleCategory(categoryDelegate.modelData.id); root.toggleCategory(categoryDelegate.modelData.id);
} else if (categoryDelegate.modelData.tabIndex !== undefined) { } else if (categoryDelegate.modelData.tabIndex !== undefined) {
root.currentIndex = categoryDelegate.modelData.tabIndex; root.tabChangeRequested(categoryDelegate.modelData.tabIndex);
} }
Qt.callLater(searchField.forceActiveFocus); Qt.callLater(searchField.forceActiveFocus);
} }
@@ -882,7 +889,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
root.keyboardHighlightIndex = -1; root.keyboardHighlightIndex = -1;
root.currentIndex = childDelegate.modelData.tabIndex; root.tabChangeRequested(childDelegate.modelData.tabIndex);
Qt.callLater(searchField.forceActiveFocus); Qt.callLater(searchField.forceActiveFocus);
} }
} }

View File

@@ -38,11 +38,10 @@ DankModal {
isClosing = false; isClosing = false;
resetContent(); resetContent();
spotlightOpen = true; spotlightOpen = true;
if (spotlightContent?.appLauncher)
spotlightContent.appLauncher.ensureInitialized();
open(); open();
Qt.callLater(() => { Qt.callLater(() => {
if (spotlightContent?.appLauncher)
spotlightContent.appLauncher.ensureInitialized();
if (spotlightContent?.searchField) if (spotlightContent?.searchField)
spotlightContent.searchField.forceActiveFocus(); spotlightContent.searchField.forceActiveFocus();
}); });
@@ -53,15 +52,14 @@ DankModal {
isClosing = false; isClosing = false;
resetContent(); resetContent();
spotlightOpen = true; spotlightOpen = true;
if (spotlightContent?.appLauncher) {
spotlightContent.appLauncher.ensureInitialized();
spotlightContent.appLauncher.searchQuery = query;
}
if (spotlightContent?.searchField) if (spotlightContent?.searchField)
spotlightContent.searchField.text = query; spotlightContent.searchField.text = query;
open(); open();
Qt.callLater(() => { Qt.callLater(() => {
if (spotlightContent?.appLauncher) {
spotlightContent.appLauncher.ensureInitialized();
spotlightContent.appLauncher.searchQuery = query;
}
if (spotlightContent?.searchField) if (spotlightContent?.searchField)
spotlightContent.searchField.forceActiveFocus(); spotlightContent.searchField.forceActiveFocus();
}); });

View File

@@ -33,7 +33,6 @@ FloatingWindow {
readonly property bool showPasswordField: fieldsInfo.length === 0 readonly property bool showPasswordField: fieldsInfo.length === 0
readonly property bool showAnonField: requiresEnterprise && !isVpnPrompt readonly property bool showAnonField: requiresEnterprise && !isVpnPrompt
readonly property bool showDomainField: requiresEnterprise && !isVpnPrompt readonly property bool showDomainField: requiresEnterprise && !isVpnPrompt
readonly property bool showShowPasswordCheckbox: fieldsInfo.length === 0
readonly property bool showSavePasswordCheckbox: (isVpnPrompt || fieldsInfo.length > 0) && promptReason !== "pkcs11" readonly property bool showSavePasswordCheckbox: (isVpnPrompt || fieldsInfo.length > 0) && promptReason !== "pkcs11"
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2 readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
@@ -55,8 +54,6 @@ FloatingWindow {
h += inputFieldWithSpacing; h += inputFieldWithSpacing;
if (showDomainField) if (showDomainField)
h += inputFieldWithSpacing; h += inputFieldWithSpacing;
if (showShowPasswordCheckbox)
h += checkboxRowHeight;
if (showSavePasswordCheckbox) if (showSavePasswordCheckbox)
h += checkboxRowHeight; h += checkboxRowHeight;
return h; return h;
@@ -447,7 +444,8 @@ FloatingWindow {
anchors.fill: parent anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText textColor: Theme.surfaceText
echoMode: modelData.isSecret ? TextInput.Password : TextInput.Normal showPasswordToggle: modelData.isSecret
echoMode: modelData.isSecret && !passwordVisible ? TextInput.Password : TextInput.Normal
placeholderText: getFieldLabel(modelData.name) placeholderText: getFieldLabel(modelData.name)
backgroundColor: "transparent" backgroundColor: "transparent"
enabled: root.visible enabled: root.visible
@@ -549,7 +547,8 @@ FloatingWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText textColor: Theme.surfaceText
text: wifiPasswordInput text: wifiPasswordInput
echoMode: showPasswordCheckbox.checked ? TextInput.Normal : TextInput.Password showPasswordToggle: true
echoMode: passwordVisible ? TextInput.Normal : TextInput.Password
placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : "" placeholderText: (requiresEnterprise && !isVpnPrompt) ? I18n.tr("Password") : ""
backgroundColor: "transparent" backgroundColor: "transparent"
enabled: root.visible enabled: root.visible
@@ -628,88 +627,43 @@ FloatingWindow {
} }
} }
Column { Row {
spacing: Theme.spacingS spacing: Theme.spacingS
width: parent.width visible: showSavePasswordCheckbox
Row { Rectangle {
spacing: Theme.spacingS id: savePasswordCheckbox
visible: showShowPasswordCheckbox
Rectangle { property bool checked: true
id: showPasswordCheckbox
property bool checked: false width: 20
height: 20
radius: 4
color: checked ? Theme.primary : "transparent"
border.color: checked ? Theme.primary : Theme.outlineButton
border.width: 2
width: 20 DankIcon {
height: 20 anchors.centerIn: parent
radius: 4 name: "check"
color: checked ? Theme.primary : "transparent" size: 12
border.color: checked ? Theme.primary : Theme.outlineButton color: Theme.background
border.width: 2 visible: parent.checked
DankIcon {
anchors.centerIn: parent
name: "check"
size: 12
color: Theme.background
visible: parent.checked
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: showPasswordCheckbox.checked = !showPasswordCheckbox.checked
}
} }
StyledText { MouseArea {
text: I18n.tr("Show password") anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium hoverEnabled: true
color: Theme.surfaceText cursorShape: Qt.PointingHandCursor
anchors.verticalCenter: parent.verticalCenter onClicked: savePasswordCheckbox.checked = !savePasswordCheckbox.checked
} }
} }
Row { StyledText {
spacing: Theme.spacingS text: I18n.tr("Save password")
visible: showSavePasswordCheckbox font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
Rectangle { anchors.verticalCenter: parent.verticalCenter
id: savePasswordCheckbox
property bool checked: false
width: 20
height: 20
radius: 4
color: checked ? Theme.primary : "transparent"
border.color: checked ? Theme.primary : Theme.outlineButton
border.width: 2
DankIcon {
anchors.centerIn: parent
name: "check"
size: 12
color: Theme.background
visible: parent.checked
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: savePasswordCheckbox.checked = !savePasswordCheckbox.checked
}
}
StyledText {
text: I18n.tr("Save password")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
} }
} }

View File

@@ -122,6 +122,21 @@ Rectangle {
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
property int maxPinnedInputs: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedInputs() {
const pins = SettingsData.audioInputDevicePins || {}
return normalizePinList(pins["preferredInput"])
}
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
@@ -133,16 +148,20 @@ Rectangle {
const nodes = Pipewire.nodes.values.filter(node => { const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && !node.isSink && !node.isStream; return node.audio && !node.isSink && !node.isStream;
}); });
const pins = SettingsData.audioInputDevicePins || {}; const pinnedList = audioContent.getPinnedInputs();
const pinnedName = pins["preferredInput"];
let sorted = [...nodes]; let sorted = [...nodes];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// Pinned device first // Pinned device first
if (a.name === pinnedName && b.name !== pinnedName) const aPinnedIndex = pinnedList.indexOf(a.name)
return -1; const bPinnedIndex = pinnedList.indexOf(b.name)
if (b.name === pinnedName && a.name !== pinnedName) if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
return 1; if (aPinnedIndex === -1)
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
// Then active device // Then active device
if (a === AudioService.source && b !== AudioService.source) if (a === AudioService.source && b !== AudioService.source)
return -1; return -1;
@@ -224,7 +243,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -237,7 +256,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -245,12 +264,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name);
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -261,16 +280,24 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {})); const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}))
const isCurrentlyPinned = pins["preferredInput"] === modelData.name; let pinnedList = audioContent.normalizePinList(pins["preferredInput"])
const pinIndex = pinnedList.indexOf(modelData.name)
if (isCurrentlyPinned) { if (pinIndex !== -1) {
delete pins["preferredInput"]; pinnedList.splice(pinIndex, 1)
} else { } else {
pins["preferredInput"] = modelData.name; pinnedList.unshift(modelData.name)
if (pinnedList.length > audioContent.maxPinnedInputs)
pinnedList = pinnedList.slice(0, audioContent.maxPinnedInputs)
} }
SettingsData.set("audioInputDevicePins", pins); if (pinnedList.length > 0)
pins["preferredInput"] = pinnedList
else
delete pins["preferredInput"]
SettingsData.set("audioInputDevicePins", pins)
} }
} }
} }

View File

@@ -132,6 +132,21 @@ Rectangle {
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
property int maxPinnedOutputs: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedOutputs() {
const pins = SettingsData.audioOutputDevicePins || {}
return normalizePinList(pins["preferredOutput"])
}
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
@@ -143,16 +158,20 @@ Rectangle {
const nodes = Pipewire.nodes.values.filter(node => { const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && !node.isStream; return node.audio && node.isSink && !node.isStream;
}); });
const pins = SettingsData.audioOutputDevicePins || {}; const pinnedList = audioContent.getPinnedOutputs();
const pinnedName = pins["preferredOutput"];
let sorted = [...nodes]; let sorted = [...nodes];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// Pinned device first // Pinned device first
if (a.name === pinnedName && b.name !== pinnedName) const aPinnedIndex = pinnedList.indexOf(a.name)
return -1; const bPinnedIndex = pinnedList.indexOf(b.name)
if (b.name === pinnedName && a.name !== pinnedName) if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
return 1; if (aPinnedIndex === -1)
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
// Then active device // Then active device
if (a === AudioService.sink && b !== AudioService.sink) if (a === AudioService.sink && b !== AudioService.sink)
return -1; return -1;
@@ -236,7 +255,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name);
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -249,7 +268,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name);
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -257,12 +276,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name);
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name; const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name);
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -273,16 +292,24 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {})); const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}))
const isCurrentlyPinned = pins["preferredOutput"] === modelData.name; let pinnedList = audioContent.normalizePinList(pins["preferredOutput"])
const pinIndex = pinnedList.indexOf(modelData.name)
if (isCurrentlyPinned) { if (pinIndex !== -1) {
delete pins["preferredOutput"]; pinnedList.splice(pinIndex, 1)
} else { } else {
pins["preferredOutput"] = modelData.name; pinnedList.unshift(modelData.name)
if (pinnedList.length > audioContent.maxPinnedOutputs)
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs)
} }
SettingsData.set("audioOutputDevicePins", pins); if (pinnedList.length > 0)
pins["preferredOutput"] = pinnedList
else
delete pins["preferredOutput"]
SettingsData.set("audioOutputDevicePins", pins)
} }
} }
} }

View File

@@ -150,6 +150,21 @@ Rectangle {
contentHeight: bluetoothColumn.height contentHeight: bluetoothColumn.height
clip: true clip: true
property int maxPinnedDevices: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedDevices() {
const pins = SettingsData.bluetoothDevicePins || {}
return normalizePinList(pins["preferredDevice"])
}
Column { Column {
id: bluetoothColumn id: bluetoothColumn
width: parent.width width: parent.width
@@ -162,14 +177,18 @@ Rectangle {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return [] return []
const pins = SettingsData.bluetoothDevicePins || {} const pinnedList = bluetoothContent.getPinnedDevices()
const pinnedAddr = pins["preferredDevice"]
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))] let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
devices.sort((a, b) => { devices.sort((a, b) => {
// Pinned device first // Pinned device first
if (a.address === pinnedAddr && b.address !== pinnedAddr) return -1 const aPinnedIndex = pinnedList.indexOf(a.address)
if (b.address === pinnedAddr && a.address !== pinnedAddr) return 1 const bPinnedIndex = pinnedList.indexOf(b.address)
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
if (aPinnedIndex === -1) return 1
if (bPinnedIndex === -1) return -1
return aPinnedIndex - bPinnedIndex
}
// Then connected devices // Then connected devices
if (a.connected && !b.connected) return -1 if (a.connected && !b.connected) return -1
if (!a.connected && b.connected) return 1 if (!a.connected && b.connected) return 1
@@ -302,7 +321,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address)
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05) return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
} }
@@ -315,7 +334,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address)
return isThisDevicePinned ? Theme.primary : Theme.surfaceText return isThisDevicePinned ? Theme.primary : Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -323,12 +342,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address)
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin") return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address)
return isThisDevicePinned ? Theme.primary : Theme.surfaceText return isThisDevicePinned ? Theme.primary : Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -340,14 +359,22 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {}))
const isCurrentlyPinned = pins["preferredDevice"] === modelData.address let pinnedList = bluetoothContent.normalizePinList(pins["preferredDevice"])
const pinIndex = pinnedList.indexOf(modelData.address)
if (isCurrentlyPinned) { if (pinIndex !== -1) {
delete pins["preferredDevice"] pinnedList.splice(pinIndex, 1)
} else { } else {
pins["preferredDevice"] = modelData.address pinnedList.unshift(modelData.address)
if (pinnedList.length > bluetoothContent.maxPinnedDevices)
pinnedList = pinnedList.slice(0, bluetoothContent.maxPinnedDevices)
} }
if (pinnedList.length > 0)
pins["preferredDevice"] = pinnedList
else
delete pins["preferredDevice"]
SettingsData.set("bluetoothDevicePins", pins) SettingsData.set("bluetoothDevicePins", pins)
} }
} }

View File

@@ -37,6 +37,10 @@ Rectangle {
NetworkService.removeRef(); NetworkService.removeRef();
} }
property bool hasEthernetAvailable: (NetworkService.ethernetDevices?.length ?? 0) > 0
property bool hasWifiAvailable: (NetworkService.wifiDevices?.length ?? 0) > 0
property bool hasBothConnectionTypes: hasEthernetAvailable && hasWifiAvailable
property int currentPreferenceIndex: { property int currentPreferenceIndex: {
if (DMSService.apiVersion < 5) { if (DMSService.apiVersion < 5) {
return 1; return 1;
@@ -46,19 +50,24 @@ Rectangle {
return 1; return 1;
} }
const pref = NetworkService.userPreference; if (!hasEthernetAvailable) {
const status = NetworkService.networkStatus; return 1;
let index = 1;
if (pref === "ethernet") {
index = 0;
} else if (pref === "wifi") {
index = 1;
} else {
index = status === "ethernet" ? 0 : 1;
} }
return index; if (!hasWifiAvailable) {
return 0;
}
const pref = NetworkService.userPreference;
const status = NetworkService.networkStatus;
if (pref === "ethernet") {
return 0;
}
if (pref === "wifi") {
return 1;
}
return status === "ethernet" ? 0 : 1;
} }
Row { Row {
@@ -117,7 +126,7 @@ Rectangle {
DankButtonGroup { DankButtonGroup {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10 visible: hasBothConnectionTypes && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
buttonHeight: 28 buttonHeight: 28
textSize: Theme.fontSizeSmall textSize: Theme.fontSizeSmall
@@ -454,20 +463,39 @@ Rectangle {
contentHeight: wifiColumn.height contentHeight: wifiColumn.height
clip: true clip: true
property int maxPinnedNetworks: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedNetworks() {
const pins = SettingsData.wifiNetworkPins || {}
return normalizePinList(pins["preferredWifi"])
}
property var frozenNetworks: [] property var frozenNetworks: []
property bool menuOpen: false property bool menuOpen: false
property var sortedNetworks: { property var sortedNetworks: {
const ssid = NetworkService.currentWifiSSID; const ssid = NetworkService.currentWifiSSID;
const networks = NetworkService.wifiNetworks; const networks = NetworkService.wifiNetworks;
const pins = SettingsData.wifiNetworkPins || {}; const pinnedList = getPinnedNetworks()
const pinnedSSID = pins["preferredWifi"];
let sorted = [...networks]; let sorted = [...networks];
sorted.sort((a, b) => { sorted.sort((a, b) => {
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID) const aPinnedIndex = pinnedList.indexOf(a.ssid)
return -1; const bPinnedIndex = pinnedList.indexOf(b.ssid)
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID) if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
return 1; if (aPinnedIndex === -1)
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
if (a.ssid === ssid) if (a.ssid === ssid)
return -1; return -1;
if (b.ssid === ssid) if (b.ssid === ssid)
@@ -616,7 +644,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid; const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid);
return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -629,7 +657,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid; const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid);
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText; return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -637,12 +665,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid; const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid);
return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid; const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid);
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText; return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -653,16 +681,24 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})); const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid; let pinnedList = wifiContent.normalizePinList(pins["preferredWifi"])
const pinIndex = pinnedList.indexOf(modelData.ssid)
if (isCurrentlyPinned) { if (pinIndex !== -1) {
delete pins["preferredWifi"]; pinnedList.splice(pinIndex, 1)
} else { } else {
pins["preferredWifi"] = modelData.ssid; pinnedList.unshift(modelData.ssid)
if (pinnedList.length > wifiContent.maxPinnedNetworks)
pinnedList = pinnedList.slice(0, wifiContent.maxPinnedNetworks)
} }
SettingsData.set("wifiNetworkPins", pins); if (pinnedList.length > 0)
pins["preferredWifi"] = pinnedList
else
delete pins["preferredWifi"]
SettingsData.set("wifiNetworkPins", pins)
} }
} }
} }
@@ -678,8 +714,8 @@ Rectangle {
if (modelData.secured && !modelData.saved) { if (modelData.secured && !modelData.saved) {
if (DMSService.apiVersion >= 7) { if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(modelData.ssid); NetworkService.connectToWifi(modelData.ssid);
} else if (PopoutService.wifiPasswordModal) { } else {
PopoutService.wifiPasswordModal.show(modelData.ssid); PopoutService.showWifiPasswordModal(modelData.ssid);
} }
} else { } else {
NetworkService.connectToWifi(modelData.ssid); NetworkService.connectToWifi(modelData.ssid);
@@ -740,8 +776,8 @@ Rectangle {
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) { if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
if (DMSService.apiVersion >= 7) { if (DMSService.apiVersion >= 7) {
NetworkService.connectToWifi(networkContextMenu.currentSSID); NetworkService.connectToWifi(networkContextMenu.currentSSID);
} else if (PopoutService.wifiPasswordModal) { } else {
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID); PopoutService.showWifiPasswordModal(networkContextMenu.currentSSID);
} }
} else { } else {
NetworkService.connectToWifi(networkContextMenu.currentSSID); NetworkService.connectToWifi(networkContextMenu.currentSSID);

View File

@@ -39,7 +39,7 @@ Item {
function getRealWorkspaces() { function getRealWorkspaces() {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentOutputWorkspaceNumbers(); return NiriService.getCurrentOutputWorkspaceNumbers();
} }
const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName).map(ws => ws.idx + 1); const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName).map(ws => ws.idx + 1);
@@ -47,7 +47,7 @@ Item {
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
const workspaces = Hyprland.workspaces?.values || []; const workspaces = Hyprland.workspaces?.values || [];
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
const sorted = workspaces.slice().sort((a, b) => a.id - b.id); const sorted = workspaces.slice().sort((a, b) => a.id - b.id);
const filtered = sorted.filter(ws => ws.id > -1); const filtered = sorted.filter(ws => ws.id > -1);
return filtered.length > 0 ? filtered : [ return filtered.length > 0 ? filtered : [
@@ -91,7 +91,7 @@ Item {
} }
]; ];
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return workspaces.slice().sort((a, b) => a.num - b.num); return workspaces.slice().sort((a, b) => a.num - b.num);
} }
@@ -107,7 +107,7 @@ Item {
function getCurrentWorkspace() { function getCurrentWorkspace() {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentWorkspaceNumber(); return NiriService.getCurrentWorkspaceNumber();
} }
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active); const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active);
@@ -125,7 +125,7 @@ Item {
const activeTags = DwlService.getActiveTags(barWindow.screenName); const activeTags = DwlService.getActiveTags(barWindow.screenName);
return activeTags.length > 0 ? activeTags[0] : 0; return activeTags.length > 0 ? activeTags[0] : 0;
} else if (CompositorService.isSway || CompositorService.isScroll) { } else if (CompositorService.isSway || CompositorService.isScroll) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) { if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs ? focusedWs.num : 1; return focusedWs ? focusedWs.num : 1;
} }

View File

@@ -24,10 +24,12 @@ BasePill {
property bool showMicPercent: widgetData?.showMicPercent !== undefined ? widgetData.showMicPercent : SettingsData.controlCenterShowMicPercent property bool showMicPercent: widgetData?.showMicPercent !== undefined ? widgetData.showMicPercent : SettingsData.controlCenterShowMicPercent
property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
property bool showScreenSharingIcon: widgetData?.showScreenSharingIcon !== undefined ? widgetData.showScreenSharingIcon : SettingsData.controlCenterShowScreenSharingIcon
property real touchpadThreshold: 100 property real touchpadThreshold: 100
property real micAccumulator: 0 property real micAccumulator: 0
property real volumeAccumulator: 0 property real volumeAccumulator: 0
property real brightnessAccumulator: 0 property real brightnessAccumulator: 0
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4)
Loader { Loader {
active: root.showPrinterIcon active: root.showPrinterIcon
@@ -213,7 +215,25 @@ BasePill {
} }
function hasNoVisibleIcons() { function hasNoVisibleIcons() {
return !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon && !root.showVpnIcon && !root.showBrightnessIcon && !root.showMicIcon && !root.showBatteryIcon && !root.showPrinterIcon; if (root.showScreenSharingIcon && NiriService.hasCasts)
return false;
if (root.showNetworkIcon && NetworkService.networkAvailable)
return false;
if (root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected)
return false;
if (root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled)
return false;
if (root.showAudioIcon)
return false;
if (root.showMicIcon)
return false;
if (root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice())
return false;
if (root.showBatteryIcon && BatteryService.batteryAvailable)
return false;
if (root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs())
return false;
return true;
} }
content: Component { content: Component {
@@ -224,48 +244,74 @@ BasePill {
Column { Column {
id: controlColumn id: controlColumn
visible: root.isVerticalOrientation visible: root.isVerticalOrientation
anchors.centerIn: parent width: root.vIconSize
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankIcon { Item {
name: root.getNetworkIconName() width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: root.getNetworkIconColor() visible: root.showScreenSharingIcon && NiriService.hasCasts
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_record"
size: root.vIconSize
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
}
Item {
width: root.vIconSize
height: root.vIconSize
visible: root.showNetworkIcon && NetworkService.networkAvailable visible: root.showNetworkIcon && NetworkService.networkAvailable
DankIcon {
name: root.getNetworkIconName()
size: root.vIconSize
color: root.getNetworkIconColor()
anchors.centerIn: parent
}
} }
DankIcon { Item {
name: "vpn_lock" width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
DankIcon {
name: "vpn_lock"
size: root.vIconSize
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
} }
DankIcon { Item {
name: "bluetooth" width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
DankIcon {
name: "bluetooth"
size: root.vIconSize
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
} }
Rectangle { Item {
width: audioIconV.implicitWidth + 4 width: root.vIconSize
height: audioIconV.implicitHeight + (root.showAudioPercent ? audioPercentV.implicitHeight : 0) + 4 height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0)
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showAudioIcon visible: root.showAudioIcon
DankIcon { DankIcon {
id: audioIconV id: audioIconV
name: root.getVolumeIconName() name: root.getVolumeIconName()
size: Theme.barIconSize(root.barThickness, -4) size: root.vIconSize
color: Theme.widgetIconColor color: Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -292,21 +338,18 @@ BasePill {
} }
} }
Rectangle { Item {
width: micIconV.implicitWidth + 4 width: root.vIconSize
height: micIconV.implicitHeight + (root.showAudioPercent ? micPercentV.implicitHeight : 0) + 4 height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0)
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showMicIcon visible: root.showMicIcon
DankIcon { DankIcon {
id: micIconV id: micIconV
name: root.getMicIconName() name: root.getMicIconName()
size: Theme.barIconSize(root.barThickness, -4) size: root.vIconSize
color: root.getMicIconColor() color: root.getMicIconColor()
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -333,21 +376,18 @@ BasePill {
} }
} }
Rectangle { Item {
width: brightnessIconV.implicitWidth + 4 width: root.vIconSize
height: brightnessIconV.implicitHeight + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight : 0) + 4 height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0)
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice() visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
DankIcon { DankIcon {
id: brightnessIconV id: brightnessIconV
name: root.getBrightnessIconName() name: root.getBrightnessIconName()
size: Theme.barIconSize(root.barThickness, -4) size: root.vIconSize
color: Theme.widgetIconColor color: Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -371,28 +411,43 @@ BasePill {
} }
} }
DankIcon { Item {
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable) width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: root.getBatteryIconColor()
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBatteryIcon && BatteryService.batteryAvailable visible: root.showBatteryIcon && BatteryService.batteryAvailable
DankIcon {
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
size: root.vIconSize
color: root.getBatteryIconColor()
anchors.centerIn: parent
}
} }
DankIcon { Item {
name: "print" width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs() visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
DankIcon {
name: "print"
size: root.vIconSize
color: Theme.primary
anchors.centerIn: parent
}
} }
DankIcon { Item {
name: "settings" width: root.vIconSize
size: Theme.barIconSize(root.barThickness, -4) height: root.vIconSize
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter
visible: root.hasNoVisibleIcons() visible: root.hasNoVisibleIcons()
DankIcon {
name: "settings"
size: root.vIconSize
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.centerIn: parent
}
} }
} }
@@ -402,6 +457,14 @@ BasePill {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankIcon {
name: "screen_record"
size: Theme.barIconSize(root.barThickness, -4)
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: root.showScreenSharingIcon && NiriService.hasCasts
}
DankIcon { DankIcon {
id: networkIcon id: networkIcon
name: root.getNetworkIconName() name: root.getNetworkIconName()

View File

@@ -56,6 +56,13 @@ BasePill {
} }
} }
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
root.updateDesktopEntry();
}
}
function updateDesktopEntry() { function updateDesktopEntry() {
if (activeWindow && activeWindow.appId) { if (activeWindow && activeWindow.appId) {
const moddedId = Paths.moddedAppId(activeWindow.appId); const moddedId = Paths.moddedAppId(activeWindow.appId);
@@ -153,24 +160,12 @@ BasePill {
size: 18 size: 18
name: "sports_esports" name: "sports_esports"
color: Theme.widgetTextColor color: Theme.widgetTextColor
visible: { visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && Paths.isSteamApp(activeWindow.appId)
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId)
return false;
const moddedId = Paths.moddedAppId(activeWindow.appId);
return moddedId.toLowerCase().includes("steam_app");
}
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: { visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && !Paths.isSteamApp(activeWindow.appId)
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId)
return false;
if (appIcon.status === Image.Ready)
return false;
const moddedId = Paths.moddedAppId(activeWindow.appId);
return !moddedId.toLowerCase().includes("steam_app");
}
text: { text: {
if (!activeWindow || !activeWindow.appId) if (!activeWindow || !activeWindow.appId)
return "?"; return "?";

View File

@@ -19,7 +19,7 @@ BasePill {
anchors.centerIn: parent anchors.centerIn: parent
name: SessionData.doNotDisturb ? "notifications_off" : "notifications" name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
size: Theme.barIconSize(root.barThickness, -4) size: Theme.barIconSize(root.barThickness, -4)
color: SessionData.doNotDisturb ? Theme.error : (root.isActive ? Theme.primary : Theme.widgetIconColor) color: SessionData.doNotDisturb ? Theme.primary : (root.isActive ? Theme.primary : Theme.widgetIconColor)
} }
Rectangle { Rectangle {
@@ -35,6 +35,6 @@ BasePill {
} }
onRightClicked: { onRightClicked: {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb) SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
} }
} }

View File

@@ -69,6 +69,7 @@ Item {
property int _desktopEntriesUpdateTrigger: 0 property int _desktopEntriesUpdateTrigger: 0
property int _toplevelsUpdateTrigger: 0 property int _toplevelsUpdateTrigger: 0
property int _appIdSubstitutionsTrigger: 0
readonly property var sortedToplevels: { readonly property var sortedToplevels: {
_toplevelsUpdateTrigger; _toplevelsUpdateTrigger;
@@ -95,6 +96,13 @@ Item {
_desktopEntriesUpdateTrigger++; _desktopEntriesUpdateTrigger++;
} }
} }
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
_appIdSubstitutionsTrigger++;
}
}
readonly property var groupedWindows: { readonly property var groupedWindows: {
if (!SettingsData.runningAppsGroupByApp) { if (!SettingsData.runningAppsGroupByApp) {
return []; return [];
@@ -364,6 +372,7 @@ Item {
height: Theme.barIconSize(root.barThickness) height: Theme.barIconSize(root.barThickness)
source: { source: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appId) if (!appId)
return ""; return "";
const moddedId = Paths.moddedAppId(appId); const moddedId = Paths.moddedAppId(appId);
@@ -391,20 +400,12 @@ Item {
size: Theme.barIconSize(root.barThickness) size: Theme.barIconSize(root.barThickness)
name: "sports_esports" name: "sports_esports"
color: Theme.widgetTextColor color: Theme.widgetTextColor
visible: { visible: !iconImg.visible && Paths.isSteamApp(appId)
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
} }
// Fallback text if no icon found
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: { visible: !iconImg.visible && !Paths.isSteamApp(appId)
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
text: { text: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
if (!appId) if (!appId)
@@ -502,8 +503,10 @@ Item {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX; const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7; const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top"); const isBottom = root.axis?.edge === "bottom";
const yPos = isBottom ? (screenHeight - root.barThickness - root.barSpacing - 32 - Theme.spacingXS) : (root.barThickness + root.barSpacing + Theme.spacingXS);
windowContextMenuLoader.item.showAt(relativeX, yPos, false, root.axis?.edge);
} }
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
@@ -614,6 +617,7 @@ Item {
height: Theme.barIconSize(root.barThickness) height: Theme.barIconSize(root.barThickness)
source: { source: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appId) if (!appId)
return ""; return "";
const moddedId = Paths.moddedAppId(appId); const moddedId = Paths.moddedAppId(appId);
@@ -641,19 +645,12 @@ Item {
size: Theme.barIconSize(root.barThickness) size: Theme.barIconSize(root.barThickness)
name: "sports_esports" name: "sports_esports"
color: Theme.widgetTextColor color: Theme.widgetTextColor
visible: { visible: !iconImg.visible && Paths.isSteamApp(appId)
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: { visible: !iconImg.visible && !Paths.isSteamApp(appId)
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
text: { text: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
if (!appId) if (!appId)
@@ -751,8 +748,10 @@ Item {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0); const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0; const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX; const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7; const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top"); const isBottom = root.axis?.edge === "bottom";
const yPos = isBottom ? (screenHeight - root.barThickness - root.barSpacing - 32 - Theme.spacingXS) : (root.barThickness + root.barSpacing + Theme.spacingXS);
windowContextMenuLoader.item.showAt(relativeX, yPos, false, root.axis?.edge);
} }
} }
} }

View File

@@ -1208,7 +1208,7 @@ Item {
visible: entryStack.count === 0 visible: entryStack.count === 0
width: parent.width width: parent.width
height: 28 height: 28
radius: 0 radius: Theme.cornerRadius
color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0) color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
StyledText { StyledText {
@@ -1261,7 +1261,7 @@ Item {
visible: entryStack.count > 0 visible: entryStack.count > 0
width: parent.width width: parent.width
height: 28 height: 28
radius: 0 radius: Theme.cornerRadius
color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0) color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
Row { Row {
@@ -1309,11 +1309,10 @@ Item {
width: menuColumn.width width: menuColumn.width
height: menuEntry?.isSeparator ? 1 : 28 height: menuEntry?.isSeparator ? 1 : 28
radius: 0 radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
color: { color: {
if (menuEntry?.isSeparator) { if (menuEntry?.isSeparator)
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2); return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
}
return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0); return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0);
} }

View File

@@ -24,6 +24,26 @@ Item {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName); return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
} }
readonly property string effectiveScreenName: {
if (!SettingsData.workspaceFollowFocus)
return root.screenName;
switch (CompositorService.compositor) {
case "niri":
return NiriService.currentOutput || root.screenName;
case "hyprland":
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
case "dwl":
return DwlService.activeOutput || root.screenName;
case "sway":
case "scroll":
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs?.monitor?.name || root.screenName;
default:
return root.screenName;
}
}
readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && ExtWorkspaceService.extWorkspaceAvailable) readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && ExtWorkspaceService.extWorkspaceAvailable)
Connections { Connections {
@@ -94,7 +114,7 @@ Item {
} }
]; ];
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
return workspaces.slice().sort((a, b) => a.num - b.num); return workspaces.slice().sort((a, b) => a.num - b.num);
} }
@@ -107,7 +127,7 @@ Item {
} }
function getSwayActiveWorkspace() { function getSwayActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs ? focusedWs.num : 1; return focusedWs ? focusedWs.num : 1;
} }
@@ -137,7 +157,7 @@ Item {
]; ];
} }
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
filtered = filtered.slice().sort((a, b) => a.id - b.id); filtered = filtered.slice().sort((a, b) => a.id - b.id);
} else { } else {
const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName); const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName);
@@ -163,7 +183,7 @@ Item {
} }
function getHyprlandActiveWorkspace() { function getHyprlandActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
return Hyprland.focusedWorkspace?.id || 1; return Hyprland.focusedWorkspace?.id || 1;
} }
@@ -183,7 +203,7 @@ Item {
if (wsNumber <= 0) { if (wsNumber <= 0) {
return []; return [];
} }
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.screenName); const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
if (!workspace) { if (!workspace) {
return []; return [];
} }
@@ -211,7 +231,7 @@ Item {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false; isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
const output = DwlService.getOutputState(root.screenName); const output = DwlService.getOutputState(root.effectiveScreenName);
if (output && output.tags) { if (output && output.tags) {
const tag = output.tags.find(t => t.tag === targetWorkspaceId); const tag = output.tags.find(t => t.tag === targetWorkspaceId);
isActiveWs = tag ? (tag.state === 1) : false; isActiveWs = tag ? (tag.state === 1) : false;
@@ -244,16 +264,16 @@ Item {
const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${keyBase}_${i}` : keyBase; const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${keyBase}_${i}` : keyBase;
if (!byApp[key]) { if (!byApp[key]) {
const moddedId = Paths.moddedAppId(keyBase);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
const isQuickshell = keyBase === "org.quickshell"; const isQuickshell = keyBase === "org.quickshell";
const desktopEntry = DesktopEntries.heuristicLookup(keyBase); const isSteamApp = Paths.isSteamApp(keyBase);
const icon = isSteamApp ? "" : Paths.getAppIcon(keyBase, desktopEntry); const moddedId = Paths.moddedAppId(keyBase);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
const icon = Paths.getAppIcon(keyBase, desktopEntry);
byApp[key] = { byApp[key] = {
"type": "icon", "type": "icon",
"icon": icon, "icon": icon,
"isSteamApp": isSteamApp,
"isQuickshell": isQuickshell, "isQuickshell": isQuickshell,
"isSteamApp": isSteamApp,
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)), "active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
"count": 1, "count": 1,
"windowId": w.address || w.id, "windowId": w.address || w.id,
@@ -308,7 +328,7 @@ Item {
} }
let workspaces; let workspaces;
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
workspaces = NiriService.getCurrentOutputWorkspaceNumbers(); workspaces = NiriService.getCurrentOutputWorkspaceNumbers();
} else { } else {
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName).map(ws => ws.idx + 1); const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName).map(ws => ws.idx + 1);
@@ -320,7 +340,7 @@ Item {
} }
return workspaces.filter(wsNum => { return workspaces.filter(wsNum => {
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNum && w.output === root.screenName); const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNum && w.output === root.effectiveScreenName);
if (!workspace) if (!workspace)
return false; return false;
if (workspace.is_active) if (workspace.is_active)
@@ -334,7 +354,7 @@ Item {
return 1; return 1;
} }
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentWorkspaceNumber(); return NiriService.getCurrentWorkspaceNumber();
} }
@@ -343,14 +363,13 @@ Item {
} }
function getDwlTags() { function getDwlTags() {
if (!DwlService.dwlAvailable) { if (!DwlService.dwlAvailable)
return []; return [];
}
const output = DwlService.getOutputState(root.screenName); const targetScreen = root.effectiveScreenName;
if (!output || !output.tags || output.tags.length === 0) { const output = DwlService.getOutputState(targetScreen);
if (!output || !output.tags || output.tags.length === 0)
return []; return [];
}
if (SettingsData.dwlShowAllTags) { if (SettingsData.dwlShowAllTags) {
return output.tags.map(tag => ({ return output.tags.map(tag => ({
@@ -361,7 +380,7 @@ Item {
})); }));
} }
const visibleTagIndices = DwlService.getVisibleTags(root.screenName); const visibleTagIndices = DwlService.getVisibleTags(targetScreen);
return visibleTagIndices.map(tagIndex => { return visibleTagIndices.map(tagIndex => {
const tagData = output.tags.find(t => t.tag === tagIndex); const tagData = output.tags.find(t => t.tag === tagIndex);
return { return {
@@ -374,12 +393,10 @@ Item {
} }
function getDwlActiveTags() { function getDwlActiveTags() {
if (!DwlService.dwlAvailable) { if (!DwlService.dwlAvailable)
return []; return [];
}
const activeTags = DwlService.getActiveTags(root.screenName); return DwlService.getActiveTags(root.effectiveScreenName);
return activeTags;
} }
function getExtWorkspaceWorkspaces() { function getExtWorkspaceWorkspaces() {
@@ -790,6 +807,68 @@ Item {
readonly property real visualWidth: baseWidth + iconsExtraWidth readonly property real visualWidth: baseWidth + iconsExtraWidth
readonly property real visualHeight: baseHeight + iconsExtraHeight readonly property real visualHeight: baseHeight + iconsExtraHeight
readonly property color unfocusedColor: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
case "sch":
return Theme.surfaceContainerHigh;
default:
return Theme.surfaceTextAlpha;
}
}
readonly property color activeColor: {
switch (SettingsData.workspaceColorMode) {
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
case "sch":
return Theme.surfaceContainerHigh;
case "none":
return unfocusedColor;
default:
return Theme.primary;
}
}
readonly property color urgentColor: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return Theme.primary;
case "secondary":
return Theme.secondary;
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
default:
return Theme.error;
}
}
readonly property color focusedBorderColor: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return Theme.surfaceText;
case "secondary":
return Theme.secondary;
default:
return Theme.primary;
}
}
function getContrastingIconColor(bgColor) {
const luminance = 0.299 * bgColor.r + 0.587 * bgColor.g + 0.114 * bgColor.b;
return luminance > 0.4 ? Qt.rgba(0.15, 0.15, 0.15, 1) : Qt.rgba(0.8, 0.8, 0.8, 1);
}
readonly property color quickshellIconActiveColor: getContrastingIconColor(activeColor)
readonly property color quickshellIconInactiveColor: getContrastingIconColor(unfocusedColor)
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
@@ -850,7 +929,7 @@ Item {
if (root.useExtWorkspace) { if (root.useExtWorkspace) {
wsData = modelData; wsData = modelData;
} else if (CompositorService.isNiri) { } else if (CompositorService.isNiri) {
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null; wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName) || null;
} else if (CompositorService.isHyprland) { } else if (CompositorService.isHyprland) {
wsData = modelData; wsData = modelData;
} else if (CompositorService.isDwl) { } else if (CompositorService.isDwl) {
@@ -892,16 +971,61 @@ Item {
width: root.isVertical ? root.barThickness : visualWidth width: root.isVertical ? root.barThickness : visualWidth
height: root.isVertical ? visualHeight : root.barThickness height: root.isVertical ? visualHeight : root.barThickness
Rectangle {
id: focusedBorderRing
anchors.centerIn: parent
width: {
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
return delegateRoot.visualWidth + borderWidth * 2;
}
height: {
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
return delegateRoot.visualHeight + borderWidth * 2;
}
radius: Theme.cornerRadius
color: "transparent"
border.width: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0
border.color: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? focusedBorderColor : "transparent"
Behavior on width {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.width {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
Rectangle { Rectangle {
id: visualContent id: visualContent
width: delegateRoot.visualWidth width: delegateRoot.visualWidth
height: delegateRoot.visualHeight height: delegateRoot.visualHeight
anchors.centerIn: parent anchors.centerIn: parent
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(Theme.surfaceText, 0.45) : Theme.surfaceTextAlpha color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : unfocusedColor
border.width: isUrgent ? 2 : 0 border.width: isUrgent ? 2 : 0
border.color: isUrgent ? Theme.error : Theme.withAlpha(Theme.error, 0) border.color: isUrgent ? urgentColor : "transparent"
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {
@@ -1013,7 +1137,7 @@ Item {
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isSteamApp && !modelData.isQuickshell visible: !modelData.isQuickshell && !modelData.isSteamApp
} }
IconImage { IconImage {
@@ -1025,17 +1149,24 @@ Item {
layer.effect: MultiEffect { layer.effect: MultiEffect {
saturation: 0 saturation: 0
colorization: 1 colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
} }
} }
IconImage {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && modelData.icon
}
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
size: root.appIconSize size: root.appIconSize
name: "sports_esports" name: "sports_esports"
color: Theme.widgetTextColor color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp visible: modelData.isSteamApp && !modelData.icon
} }
MouseArea { MouseArea {
@@ -1116,7 +1247,7 @@ Item {
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isSteamApp && !modelData.isQuickshell visible: !modelData.isQuickshell && !modelData.isSteamApp
} }
IconImage { IconImage {
@@ -1128,17 +1259,24 @@ Item {
layer.effect: MultiEffect { layer.effect: MultiEffect {
saturation: 0 saturation: 0
colorization: 1 colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
} }
} }
IconImage {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && modelData.icon
}
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
size: root.appIconSize size: root.appIconSize
name: "sports_esports" name: "sports_esports"
color: Theme.widgetTextColor color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp visible: modelData.isSteamApp && !modelData.icon
} }
MouseArea { MouseArea {
@@ -1264,6 +1402,9 @@ Item {
function onWorkspaceNameIconsChanged() { function onWorkspaceNameIconsChanged() {
delegateRoot.updateAllData(); delegateRoot.updateAllData();
} }
function onAppIdSubstitutionsChanged() {
delegateRoot.updateAllData();
}
} }
Connections { Connections {
target: DwlService target: DwlService

View File

@@ -327,11 +327,12 @@ Item {
clip: false clip: false
visible: !_noneAvailable && (!showNoPlayerNow) visible: !_noneAvailable && (!showNoPlayerNow)
ColumnLayout { ColumnLayout {
x: 72
y: 20
width: 484 width: 484
height: 370 height: 370
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
Item { Item {
width: parent.width width: parent.width

View File

@@ -20,7 +20,19 @@ Rectangle {
} }
} }
readonly property string dateText: (daily ? root.forecastData?.day : root.forecastData?.time) ?? "--" readonly property string dateText: {
if (daily)
return root.forecastData?.day ?? "--";
if (!root.forecastData?.rawTime)
return root.forecastData?.time ?? "--";
try {
const date = new Date(root.forecastData.rawTime);
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
return date.toLocaleTimeString(Qt.locale(), format);
} catch (e) {
return root.forecastData?.time ?? "--";
}
}
readonly property var minTemp: WeatherService.formatTemp(root.forecastData?.tempMin) readonly property var minTemp: WeatherService.formatTemp(root.forecastData?.tempMin)
readonly property var maxTemp: WeatherService.formatTemp(root.forecastData?.tempMax) readonly property var maxTemp: WeatherService.formatTemp(root.forecastData?.tempMax)

View File

@@ -4,7 +4,6 @@ import QtQuick.Shapes
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.DankBar.Widgets
Item { Item {
id: root id: root
@@ -261,7 +260,17 @@ Item {
StyledText { StyledText {
id: sunriseText id: sunriseText
text: WeatherService.weather.sunrise || "" text: {
if (!WeatherService.weather.rawSunrise)
return WeatherService.weather.sunrise || "";
try {
const date = new Date(WeatherService.weather.rawSunrise);
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
return date.toLocaleTimeString(Qt.locale(), format);
} catch (e) {
return WeatherService.weather.sunrise || "";
}
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.left: sunriseIcon.right anchors.left: sunriseIcon.right
@@ -285,7 +294,17 @@ Item {
StyledText { StyledText {
id: sunsetText id: sunsetText
text: WeatherService.weather.sunset || "" text: {
if (!WeatherService.weather.rawSunset)
return WeatherService.weather.sunset || "";
try {
const date = new Date(WeatherService.weather.rawSunset);
const format = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
return date.toLocaleTimeString(Qt.locale(), format);
} catch (e) {
return WeatherService.weather.sunset || "";
}
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.left: sunsetIcon.right anchors.left: sunsetIcon.right
@@ -324,14 +343,14 @@ Item {
break; break;
} }
} }
readonly property var splitDate: Qt.formatDateTime(dateStepper.currentDate, "yyyy.MM.dd.hh.mm.AP").split('.') readonly property var splitDate: Qt.formatDateTime(dateStepper.currentDate, SettingsData.use24HourClock ? "yyyy.MM.dd.HH.mm" : "yyyy.MM.dd.hh.mm.AP").split('.')
Item { Item {
id: dateStepperInner id: dateStepperInner
anchors.fill: parent anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
readonly property var space: Theme.spacingXS readonly property var space: Theme.spacingXS
width: yearStepper.width + monthStepper.width + dayStepper.width + hourStepper.width + minuteStepper.width + suffix.width + 10.5 * space + 2 * dateStepperInnerPadding.width width: yearStepper.width + monthStepper.width + dayStepper.width + hourStepper.width + minuteStepper.width + (suffix.visible ? suffix.width : 0) + 10.5 * space + 2 * dateStepperInnerPadding.width
Item { Item {
id: dateStepperInnerPadding id: dateStepperInnerPadding
@@ -420,13 +439,14 @@ Item {
} }
Rectangle { Rectangle {
id: suffix id: suffix
visible: !SettingsData.use24HourClock
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: minuteStepper.right anchors.left: minuteStepper.right
anchors.leftMargin: 2 * parent.space anchors.leftMargin: 2 * parent.space
StyledText { StyledText {
isMonospace: true isMonospace: true
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: dateStepper.splitDate[5] text: dateStepper.splitDate[5] ?? ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
x: -Theme.fontSizeSmall / 2 x: -Theme.fontSizeSmall / 2
y: -Theme.fontSizeSmall / 2 y: -Theme.fontSizeSmall / 2

View File

@@ -49,6 +49,13 @@ Item {
updateDesktopEntry(); updateDesktopEntry();
} }
} }
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
updateDesktopEntry();
}
}
property bool isWindowFocused: { property bool isWindowFocused: {
if (!appData) { if (!appData) {
return false; return false;
@@ -392,25 +399,11 @@ Item {
} }
} }
DankIcon {
anchors.centerIn: parent
size: actualIconSize
name: "sports_esports"
color: Theme.surfaceText
visible: {
if (!appData || !appData.appId || appData.appId === "__SEPARATOR__") {
return false;
}
const moddedId = Paths.moddedAppId(appData.appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
Rectangle { Rectangle {
width: actualIconSize width: actualIconSize
height: actualIconSize height: actualIconSize
anchors.centerIn: parent anchors.centerIn: parent
visible: iconImg.status !== Image.Ready visible: iconImg.status !== Image.Ready && appData && appData.appId && !Paths.isSteamApp(appData.appId)
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 1
@@ -432,6 +425,14 @@ Item {
} }
} }
DankIcon {
anchors.centerIn: parent
size: actualIconSize
name: "sports_esports"
color: Theme.surfaceText
visible: iconImg.status !== Image.Ready && appData && appData.appId && Paths.isSteamApp(appData.appId)
}
Loader { Loader {
anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter
anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined

View File

@@ -2,8 +2,6 @@
set -e set -e
export WLR_DRM_DEVICES=/dev/dri/card1
COMPOSITOR="" COMPOSITOR=""
COMPOSITOR_CONFIG="" COMPOSITOR_CONFIG=""
DMS_PATH="dms-greeter" DMS_PATH="dms-greeter"
@@ -16,7 +14,7 @@ dms-greeter - DankMaterialShell greeter launcher
Usage: dms-greeter --command COMPOSITOR [OPTIONS] Usage: dms-greeter --command COMPOSITOR [OPTIONS]
Required: Required:
--command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll or mangowc) --command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll, mangowc, or labwc)
Options: Options:
-C, --config PATH Custom compositor config file -C, --config PATH Custom compositor config file
@@ -33,6 +31,7 @@ Examples:
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
dms-greeter --command niri --cache-dir /tmp/dmsgreeter dms-greeter --command niri --cache-dir /tmp/dmsgreeter
dms-greeter --command mangowc dms-greeter --command mangowc
dms-greeter --command labwc
EOF EOF
} }
@@ -231,6 +230,15 @@ SCROLL_EOF
exec scroll -c "$COMPOSITOR_CONFIG" exec scroll -c "$COMPOSITOR_CONFIG"
;; ;;
labwc)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
else
exec labwc --session "$QS_CMD"
fi
;;
mangowc) mangowc)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit" exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"
@@ -241,7 +249,7 @@ SCROLL_EOF
*) *)
echo "Error: Unsupported compositor: $COMPOSITOR" >&2 echo "Error: Unsupported compositor: $COMPOSITOR" >&2
echo "Supported compositors: niri, hyprland, sway, mangowc" >&2 echo "Supported compositors: niri, hyprland, sway, scroll, mangowc, labwc" >&2
exit 1 exit 1
;; ;;
esac esac

View File

@@ -12,37 +12,54 @@ Scope {
property string sharedPasswordBuffer: "" property string sharedPasswordBuffer: ""
property bool shouldLock: false property bool shouldLock: false
property bool processingExternalEvent: false property bool lockInitiatedLocally: false
property bool pendingLock: false
Component.onCompleted: { Component.onCompleted: {
IdleService.lockComponent = this; IdleService.lockComponent = this;
} }
function notifyLoginctl(lockAction: bool) {
if (!SettingsData.loginctlLockIntegration || !DMSService.isConnected)
return;
if (lockAction)
DMSService.lockSession(() => {});
else
DMSService.unlockSession(() => {});
}
function lock() { function lock() {
if (SettingsData.customPowerActionLock && SettingsData.customPowerActionLock.length > 0) { if (SettingsData.customPowerActionLock?.length > 0) {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock]); Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock]);
return; return;
} }
shouldLock = true; if (shouldLock || pendingLock)
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) { return;
DMSService.lockSession(response => {
if (response.error) lockInitiatedLocally = true;
console.warn("Lock: loginctl.lock failed:", response.error);
}); if (!SessionService.active && SessionService.loginctlAvailable) {
pendingLock = true;
notifyLoginctl(true);
return;
} }
shouldLock = true;
notifyLoginctl(true);
} }
function unlock() { function unlock() {
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) { if (!shouldLock)
DMSService.unlockSession(response => { return;
if (response.error) { lockInitiatedLocally = false;
console.warn("Lock: Failed to call loginctl.unlock:", response.error); notifyLoginctl(false);
shouldLock = false; shouldLock = false;
} }
});
} else { function forceReset() {
shouldLock = false; lockInitiatedLocally = false;
} pendingLock = false;
shouldLock = false;
} }
function activate() { function activate() {
@@ -53,15 +70,39 @@ Scope {
target: SessionService target: SessionService
function onSessionLocked() { function onSessionLocked() {
processingExternalEvent = true; if (shouldLock || pendingLock)
return;
if (!SessionService.active && SessionService.loginctlAvailable) {
pendingLock = true;
lockInitiatedLocally = false;
return;
}
lockInitiatedLocally = false;
shouldLock = true; shouldLock = true;
processingExternalEvent = false;
} }
function onSessionUnlocked() { function onSessionUnlocked() {
processingExternalEvent = true; if (pendingLock) {
pendingLock = false;
lockInitiatedLocally = false;
return;
}
if (!shouldLock || lockInitiatedLocally)
return;
shouldLock = false; shouldLock = false;
processingExternalEvent = false; }
function onLoginctlStateChanged() {
if (SessionService.active && pendingLock) {
pendingLock = false;
lockInitiatedLocally = true;
shouldLock = true;
return;
}
if (SessionService.locked && !shouldLock && !pendingLock) {
lockInitiatedLocally = false;
shouldLock = true;
}
} }
} }
@@ -79,8 +120,10 @@ Scope {
locked: shouldLock locked: shouldLock
onLockedChanged: { onLockedChanged: {
if (locked) if (locked) {
pendingLock = false;
dpmsReapplyTimer.start(); dpmsReapplyTimer.start();
}
} }
WlSessionLockSurface { WlSessionLockSurface {
@@ -104,9 +147,7 @@ Scope {
sharedPasswordBuffer: root.sharedPasswordBuffer sharedPasswordBuffer: root.sharedPasswordBuffer
screenName: lockSurface.currentScreenName screenName: lockSurface.currentScreenName
isLocked: shouldLock isLocked: shouldLock
onUnlockRequested: { onUnlockRequested: root.unlock()
root.unlock();
}
onPasswordChanged: newPassword => { onPasswordChanged: newPassword => {
root.sharedPasswordBuffer = newPassword; root.sharedPasswordBuffer = newPassword;
} }
@@ -122,13 +163,15 @@ Scope {
target: "lock" target: "lock"
function lock() { function lock() {
root.shouldLock = true; root.lock();
if (SettingsData.loginctlLockIntegration && DMSService.isConnected) { }
DMSService.lockSession(response => {
if (response.error) function unlock() {
console.warn("Lock: loginctl.lock failed:", response.error); root.unlock();
}); }
}
function forceReset() {
root.forceReset();
} }
function demo() { function demo() {
@@ -138,6 +181,17 @@ Scope {
function isLocked(): bool { function isLocked(): bool {
return sessionLock.locked; return sessionLock.locked;
} }
function status(): string {
return JSON.stringify({
shouldLock: root.shouldLock,
sessionLockLocked: sessionLock.locked,
lockInitiatedLocally: root.lockInitiatedLocally,
pendingLock: root.pendingLock,
loginctlLocked: SessionService.locked,
loginctlActive: SessionService.active
});
}
} }
Timer { Timer {

View File

@@ -34,6 +34,13 @@ Item {
signal unlockRequested signal unlockRequested
function resetLockState() {
lockerReadySent = false;
lockerReadyArmed = true;
unlocking = false;
pamState = "";
}
function pickRandomFact() { function pickRandomFact() {
randomFact = Facts.getRandomFact(); randomFact = Facts.getRandomFact();
} }
@@ -1399,6 +1406,14 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio) visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
DankIcon {
name: "screen_record"
size: Theme.iconSize - 2
color: NiriService.hasActiveCast ? "white" : Qt.rgba(255, 255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: NiriService.hasCasts
}
DankIcon { DankIcon {
name: { name: {
if (NetworkService.wifiToggling) if (NetworkService.wifiToggling)

View File

@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common
PanelWindow { PanelWindow {
id: root id: root
@@ -26,13 +25,13 @@ PanelWindow {
color: "transparent" color: "transparent"
function showDemo(): void { function showDemo(): void {
console.log("Showing lock screen demo") console.log("Showing lock screen demo");
demoActive = true demoActive = true;
} }
function hideDemo(): void { function hideDemo(): void {
console.log("Hiding lock screen demo") console.log("Hiding lock screen demo");
demoActive = false demoActive = false;
} }
Loader { Loader {

View File

@@ -32,8 +32,10 @@ Rectangle {
} }
onIsLockedChanged: { onIsLockedChanged: {
if (!isLocked) { if (isLocked) {
lockContent.unlocking = false; lockContent.resetLockState();
return;
} }
lockContent.unlocking = false;
} }
} }

View File

@@ -10,9 +10,17 @@ Rectangle {
required property var historyItem required property var historyItem
property bool isSelected: false property bool isSelected: false
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property bool descriptionExpanded: NotificationService.expandedMessages[historyItem?.id ? (historyItem.id + "_hist") : ""] || false
readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real iconSize: compactMode ? 48 : 63
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real collapsedContentHeight: iconSize + cardPadding
readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight
width: parent ? parent.width : 400 width: parent ? parent.width : 400
height: 116 height: baseCardHeight + contentItem.extraHeight
radius: Theme.cornerRadius radius: Theme.cornerRadius
clip: true clip: true
@@ -65,23 +73,28 @@ Rectangle {
} }
Item { Item {
id: contentItem
readonly property real expandedTextHeight: descriptionText.contentHeight
readonly property real twoLineHeight: descriptionText.font.pixelSize * 1.2 * 2
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > twoLineHeight + 2) ? (expandedTextHeight - twoLineHeight) : 0
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.leftMargin: 16 anchors.leftMargin: Theme.spacingL
anchors.rightMargin: 56 anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
height: 92 height: collapsedContentHeight + extraHeight
DankCircularImage { DankCircularImage {
id: iconContainer id: iconContainer
readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== "" readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== ""
width: 63 width: iconSize
height: 63 height: iconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 14
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -116,60 +129,79 @@ Rectangle {
Rectangle { Rectangle {
anchors.left: iconContainer.right anchors.left: iconContainer.right
anchors.leftMargin: 12 anchors.leftMargin: Theme.spacingM
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
color: "transparent" color: "transparent"
Item { Column {
width: parent.width width: parent.width
height: parent.height
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: -2 spacing: compactMode ? 1 : 2
Column { StyledText {
width: parent.width width: parent.width
spacing: 2 text: {
const timeStr = NotificationService.formatHistoryTime(historyItem.timestamp);
const appName = historyItem.appName || "";
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName;
}
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText { StyledText {
width: parent.width text: historyItem.summary || ""
text: { color: Theme.surfaceText
const timeStr = NotificationService.formatHistoryTime(historyItem.timestamp); font.pixelSize: Theme.fontSizeMedium
const appName = historyItem.appName || ""; font.weight: Font.Medium
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName; width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText {
id: descriptionText
property bool hasMoreText: truncated
text: historyItem.htmlBody || historyItem.body || ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
const messageId = historyItem?.id ? (historyItem.id + "_hist") : "";
NotificationService.toggleMessageExpansion(messageId);
}
} }
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
StyledText { propagateComposedEvents: true
text: historyItem.summary || "" onPressed: mouse => {
color: Theme.surfaceText if (parent.hoveredLink)
font.pixelSize: Theme.fontSizeMedium mouse.accepted = false;
font.weight: Font.Medium }
width: parent.width onReleased: mouse => {
elide: Text.ElideRight if (parent.hoveredLink)
maximumLineCount: 1 mouse.accepted = false;
visible: text.length > 0 }
}
StyledText {
id: descriptionText
text: historyItem.htmlBody || historyItem.body || ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: Text.ElideRight
maximumLineCount: 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
} }
} }
} }
@@ -179,11 +211,11 @@ Rectangle {
DankActionButton { DankActionButton {
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.rightMargin: 16 anchors.rightMargin: Theme.spacingL
iconName: "close" iconName: "close"
iconSize: 18 iconSize: compactMode ? 16 : 18
buttonSize: 28 buttonSize: compactMode ? 24 : 28
onClicked: NotificationService.removeFromHistory(historyItem.id) onClicked: NotificationService.removeFromHistory(historyItem.id)
} }
} }

View File

@@ -83,23 +83,54 @@ Item {
} }
readonly property var allFilters: [ readonly property var allFilters: [
{ label: I18n.tr("All", "notification history filter"), key: "all", maxDays: 0 }, {
{ label: I18n.tr("Last hour", "notification history filter"), key: "1h", maxDays: 1 }, label: I18n.tr("All", "notification history filter"),
{ label: I18n.tr("Today", "notification history filter"), key: "today", maxDays: 1 }, key: "all",
{ label: I18n.tr("Yesterday", "notification history filter"), key: "yesterday", maxDays: 2 }, maxDays: 0
{ label: I18n.tr("7 days", "notification history filter"), key: "7d", maxDays: 7 }, },
{ label: I18n.tr("30 days", "notification history filter"), key: "30d", maxDays: 30 }, {
{ label: I18n.tr("Older", "notification history filter for content older than other filters"), key: "older", maxDays: 0 } label: I18n.tr("Last hour", "notification history filter"),
key: "1h",
maxDays: 1
},
{
label: I18n.tr("Today", "notification history filter"),
key: "today",
maxDays: 1
},
{
label: I18n.tr("Yesterday", "notification history filter"),
key: "yesterday",
maxDays: 2
},
{
label: I18n.tr("7 days", "notification history filter"),
key: "7d",
maxDays: 7
},
{
label: I18n.tr("30 days", "notification history filter"),
key: "30d",
maxDays: 30
},
{
label: I18n.tr("Older", "notification history filter for content older than other filters"),
key: "older",
maxDays: 0
}
] ]
function filterRelevantForRetention(filter) { function filterRelevantForRetention(filter) {
const retention = SettingsData.notificationHistoryMaxAgeDays; const retention = SettingsData.notificationHistoryMaxAgeDays;
if (filter.key === "older") { if (filter.key === "older") {
if (retention === 0) return true; if (retention === 0)
return true;
return retention > 2 && retention < 7 || retention > 30; return retention > 2 && retention < 7 || retention > 30;
} }
if (retention === 0) return true; if (retention === 0)
if (filter.maxDays === 0) return true; return true;
if (filter.maxDays === 0)
return true;
return filter.maxDays <= retention; return filter.maxDays <= retention;
} }
@@ -119,10 +150,15 @@ Item {
const retention = SettingsData.notificationHistoryMaxAgeDays; const retention = SettingsData.notificationHistoryMaxAgeDays;
for (let i = 0; i < allFilters.length; i++) { for (let i = 0; i < allFilters.length; i++) {
const f = allFilters[i]; const f = allFilters[i];
if (!filterRelevantForRetention(f)) continue; if (!filterRelevantForRetention(f))
continue;
const count = countForFilter(f.key); const count = countForFilter(f.key);
if (f.key === "all" || count > 0) { if (f.key === "all" || count > 0) {
result.push({ label: f.label, key: f.key, count: count }); result.push({
label: f.label,
key: f.key,
count: count
});
} }
} }
return result; return result;
@@ -165,6 +201,14 @@ Item {
function enableAutoScroll() { function enableAutoScroll() {
} }
function removeWithScrollPreserve(itemId) {
historyListView.savedY = historyListView.contentY;
NotificationService.removeFromHistory(itemId);
Qt.callLater(() => {
historyListView.forceLayout();
});
}
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -201,14 +245,66 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
delegate: HistoryNotificationCard { delegate: Item {
id: delegateRoot
required property var modelData required property var modelData
required property int index required property int index
property real swipeOffset: 0
property bool isDismissing: false
readonly property real dismissThreshold: width * 0.35
width: ListView.view.width width: ListView.view.width
historyItem: modelData height: historyCard.height
isSelected: root.keyboardActive && root.selectedIndex === index clip: true
keyboardNavigationActive: root.keyboardActive
HistoryNotificationCard {
id: historyCard
width: parent.width
x: delegateRoot.swipeOffset
historyItem: modelData
isSelected: root.keyboardActive && root.selectedIndex === index
keyboardNavigationActive: root.keyboardActive
opacity: 1 - Math.abs(delegateRoot.swipeOffset) / (delegateRoot.width * 0.5)
Behavior on x {
enabled: !swipeDragHandler.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
}
}
}
DragHandler {
id: swipeDragHandler
target: null
yAxis.enabled: false
xAxis.enabled: true
onActiveChanged: {
if (active || delegateRoot.isDismissing)
return;
if (Math.abs(delegateRoot.swipeOffset) > delegateRoot.dismissThreshold) {
delegateRoot.isDismissing = true;
root.removeWithScrollPreserve(delegateRoot.modelData?.id || "");
} else {
delegateRoot.swipeOffset = 0;
}
}
onTranslationChanged: {
if (delegateRoot.isDismissing)
return;
delegateRoot.swipeOffset = translation.x;
}
}
} }
} }
} }

View File

@@ -18,17 +18,17 @@ Rectangle {
property int selectedNotificationIndex: -1 property int selectedNotificationIndex: -1
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real iconSize: compactMode ? 48 : 63
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real badgeSize: compactMode ? 16 : 18
readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real collapsedContentHeight: iconSize
readonly property real baseCardHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing
width: parent ? parent.width : 400 width: parent ? parent.width : 400
height: { height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
if (expanded) {
return expandedContent.height + 28;
}
const baseHeight = 116;
if (descriptionExpanded) {
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2);
}
return baseHeight;
}
radius: Theme.cornerRadius radius: Theme.cornerRadius
Behavior on border.color { Behavior on border.color {
@@ -97,24 +97,28 @@ Rectangle {
Item { Item {
id: collapsedContent id: collapsedContent
readonly property real expandedTextHeight: descriptionText.contentHeight
readonly property real twoLineHeight: descriptionText.font.pixelSize * 1.2 * 2
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > twoLineHeight + 2) ? (expandedTextHeight - twoLineHeight) : 0
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.leftMargin: 16 anchors.leftMargin: Theme.spacingL
anchors.rightMargin: 56 anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
height: 92 height: collapsedContentHeight + extraHeight
visible: !expanded visible: !expanded
DankCircularImage { DankCircularImage {
id: iconContainer id: iconContainer
readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== "" readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== ""
width: 63 width: iconSize
height: 63 height: iconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 14
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -147,9 +151,9 @@ Rectangle {
} }
Rectangle { Rectangle {
width: 18 width: badgeSize
height: 18 height: badgeSize
radius: 9 radius: badgeSize / 2
color: Theme.primary color: Theme.primary
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
@@ -161,7 +165,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString() text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText color: Theme.primaryText
font.pixelSize: 9 font.pixelSize: compactMode ? 8 : 9
font.weight: Font.Bold font.weight: Font.Bold
} }
} }
@@ -171,87 +175,79 @@ Rectangle {
id: textContainer id: textContainer
anchors.left: iconContainer.right anchors.left: iconContainer.right
anchors.leftMargin: 12 anchors.leftMargin: Theme.spacingM
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
color: "transparent" color: "transparent"
Item { Column {
width: parent.width width: parent.width
height: parent.height
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: -2 spacing: compactMode ? 1 : 2
Column { StyledText {
width: parent.width width: parent.width
spacing: 2 text: {
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "";
const appName = (notificationGroup && notificationGroup.appName) || "";
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName;
}
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText { StyledText {
width: parent.width text: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.summary) || ""
text: { color: Theme.surfaceText
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || ""; font.pixelSize: Theme.fontSizeMedium
const appName = (notificationGroup && notificationGroup.appName) || ""; font.weight: Font.Medium
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName; width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
StyledText {
id: descriptionText
property string fullText: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.htmlBody) || ""
property bool hasMoreText: truncated
text: fullText
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: Text.ElideRight
maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
NotificationService.toggleMessageExpansion(messageId);
}
} }
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
StyledText { propagateComposedEvents: true
text: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.summary) || "" onPressed: mouse => {
color: Theme.surfaceText if (parent.hoveredLink)
font.pixelSize: Theme.fontSizeMedium mouse.accepted = false;
font.weight: Font.Medium }
width: parent.width onReleased: mouse => {
elide: Text.ElideRight if (parent.hoveredLink)
maximumLineCount: 1 mouse.accepted = false;
visible: text.length > 0
}
StyledText {
id: descriptionText
property string fullText: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.htmlBody) || ""
property bool hasMoreText: truncated
text: fullText
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: Text.ElideRight
maximumLineCount: descriptionExpanded ? -1 : 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
const messageId = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.notification && notificationGroup.latestNotification.notification.id) ? (notificationGroup.latestNotification.notification.id + "_desc") : "";
NotificationService.toggleMessageExpansion(messageId);
}
}
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
onReleased: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
} }
} }
} }
@@ -265,21 +261,20 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 14 anchors.topMargin: cardPadding
anchors.bottomMargin: 14
anchors.leftMargin: Theme.spacingL anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL anchors.rightMargin: Theme.spacingL
spacing: -1 spacing: compactMode ? Theme.spacingXS : Theme.spacingS
visible: expanded visible: expanded
Item { Item {
width: parent.width width: parent.width
height: 40 height: compactMode ? 32 : 40
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 56 anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -294,9 +289,9 @@ Rectangle {
} }
Rectangle { Rectangle {
width: 18 width: badgeSize
height: 18 height: badgeSize
radius: 9 radius: badgeSize / 2
color: Theme.primary color: Theme.primary
visible: (notificationGroup?.count || 0) > 1 visible: (notificationGroup?.count || 0) > 1
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -305,7 +300,7 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString() text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText color: Theme.primaryText
font.pixelSize: 9 font.pixelSize: compactMode ? 8 : 9
font.weight: Font.Bold font.weight: Font.Bold
} }
} }
@@ -314,7 +309,7 @@ Rectangle {
Column { Column {
width: parent.width width: parent.width
spacing: 16 spacing: compactMode ? Theme.spacingS : Theme.spacingL
Repeater { Repeater {
id: notificationRepeater id: notificationRepeater
@@ -328,23 +323,23 @@ Rectangle {
required property int index required property int index
readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false
readonly property bool isSelected: root.selectedNotificationIndex === index readonly property bool isSelected: root.selectedNotificationIndex === index
readonly property real expandedIconSize: compactMode ? 40 : 48
readonly property real expandedItemPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real expandedBaseHeight: expandedItemPadding * 2 + expandedIconSize + actionButtonHeight + contentSpacing * 2
width: parent.width width: parent.width
height: { height: {
const baseHeight = 120; if (!messageExpanded)
if (messageExpanded) { return expandedBaseHeight;
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2; const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
if (bodyText.implicitHeight > twoLineHeight + 2) { if (bodyText.implicitHeight > twoLineHeight + 2)
const extraHeight = bodyText.implicitHeight - twoLineHeight; return expandedBaseHeight + bodyText.implicitHeight - twoLineHeight;
return baseHeight + extraHeight; return expandedBaseHeight;
}
}
return baseHeight;
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05) border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
border.width: isSelected ? 1 : 1 border.width: 1
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
@@ -359,19 +354,19 @@ Rectangle {
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: 12 anchors.margins: compactMode ? Theme.spacingS : Theme.spacingM
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
DankCircularImage { DankCircularImage {
id: messageIcon id: messageIcon
readonly property bool hasNotificationImage: modelData?.image && modelData.image !== "" readonly property bool hasNotificationImage: modelData?.image && modelData.image !== ""
width: 48 width: expandedIconSize
height: 48 height: expandedIconSize
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 32 anchors.topMargin: compactMode ? Theme.spacingM : Theme.spacingXL
imageSource: { imageSource: {
if (hasNotificationImage) if (hasNotificationImage)
@@ -397,9 +392,9 @@ Rectangle {
Item { Item {
anchors.left: messageIcon.right anchors.left: messageIcon.right
anchors.leftMargin: 12 anchors.leftMargin: Theme.spacingM
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 12 anchors.rightMargin: Theme.spacingM
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -408,8 +403,8 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: buttonArea.top anchors.bottom: buttonArea.top
anchors.bottomMargin: 4 anchors.bottomMargin: contentSpacing
spacing: 2 spacing: compactMode ? 1 : 2
StyledText { StyledText {
width: parent.width width: parent.width
@@ -477,12 +472,12 @@ Rectangle {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
height: 30 height: actionButtonHeight + contentSpacing
Row { Row {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
spacing: 8 spacing: contentSpacing
Repeater { Repeater {
model: modelData?.actions || [] model: modelData?.actions || []
@@ -490,18 +485,17 @@ Rectangle {
Rectangle { Rectangle {
property bool isHovered: false property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50) width: Math.max(actionText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
id: actionText id: actionText
text: { text: {
const baseText = modelData.text || "View"; const baseText = modelData.text || "View";
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) { if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
return `${baseText} (${index + 1})`; return `${baseText} (${index + 1})`;
}
return baseText; return baseText;
} }
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
@@ -518,9 +512,8 @@ Rectangle {
onEntered: parent.isHovered = true onEntered: parent.isHovered = true
onExited: parent.isHovered = false onExited: parent.isHovered = false
onClicked: { onClicked: {
if (modelData && modelData.invoke) { if (modelData && modelData.invoke)
modelData.invoke(); modelData.invoke();
}
} }
} }
} }
@@ -529,9 +522,9 @@ Rectangle {
Rectangle { Rectangle {
property bool isHovered: false property bool isHovered: false
width: Math.max(clearText.implicitWidth + 12, 50) width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
@@ -563,11 +556,11 @@ Rectangle {
Row { Row {
visible: !expanded visible: !expanded
anchors.right: clearButton.left anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: 8 anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
spacing: 8 spacing: contentSpacing
Repeater { Repeater {
model: notificationGroup?.latestNotification?.actions || [] model: notificationGroup?.latestNotification?.actions || []
@@ -575,9 +568,9 @@ Rectangle {
Rectangle { Rectangle {
property bool isHovered: false property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50) width: Math.max(actionText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
@@ -616,15 +609,16 @@ Rectangle {
id: clearButton id: clearButton
property bool isHovered: false property bool isHovered: false
readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length
visible: !expanded visible: !expanded && actionCount < 3
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 16 anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
width: Math.max(clearText.implicitWidth + 12, 50) width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
@@ -660,18 +654,18 @@ Rectangle {
id: fixedControls id: fixedControls
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.rightMargin: 16 anchors.rightMargin: Theme.spacingL
width: 60 width: compactMode ? 52 : 60
height: 28 height: compactMode ? 24 : 28
DankActionButton { DankActionButton {
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
visible: (notificationGroup?.count || 0) > 1 visible: (notificationGroup?.count || 0) > 1
iconName: expanded ? "expand_less" : "expand_more" iconName: expanded ? "expand_less" : "expand_more"
iconSize: 18 iconSize: compactMode ? 16 : 18
buttonSize: 28 buttonSize: compactMode ? 24 : 28
onClicked: { onClicked: {
root.userInitiatedExpansion = true; root.userInitiatedExpansion = true;
NotificationService.toggleGroupExpansion(notificationGroup?.key || ""); NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
@@ -682,8 +676,8 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
iconName: "close" iconName: "close"
iconSize: 18 iconSize: compactMode ? 16 : 18
buttonSize: 28 buttonSize: compactMode ? 24 : 28
onClicked: NotificationService.dismissGroup(notificationGroup?.key || "") onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
} }
} }

View File

@@ -22,6 +22,15 @@ PanelWindow {
property bool _isDestroying: false property bool _isDestroying: false
property bool _finalized: false property bool _finalized: false
readonly property string clearText: I18n.tr("Dismiss") readonly property string clearText: I18n.tr("Dismiss")
property bool descriptionExpanded: false
readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real popupIconSize: compactMode ? 48 : 63
readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real collapsedContentHeight: popupIconSize
readonly property real basePopupHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + Theme.spacingS
signal entered signal entered
signal exitStarted signal exitStarted
@@ -92,7 +101,15 @@ PanelWindow {
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent" color: "transparent"
implicitWidth: 400 implicitWidth: 400
implicitHeight: 122 implicitHeight: {
if (!descriptionExpanded)
return basePopupHeight;
const bodyTextHeight = bodyText.contentHeight || 0;
const twoLineHeight = Theme.fontSizeSmall * 1.2 * 2;
if (bodyTextHeight > twoLineHeight + 2)
return basePopupHeight + bodyTextHeight - twoLineHeight;
return basePopupHeight;
}
onHasValidDataChanged: { onHasValidDataChanged: {
if (!hasValidData && !exiting && !_isDestroying) { if (!hasValidData && !exiting && !_isDestroying) {
forceExit(); forceExit();
@@ -352,13 +369,17 @@ PanelWindow {
Item { Item {
id: notificationContent id: notificationContent
readonly property real expandedTextHeight: bodyText.contentHeight || 0
readonly property real twoLineHeight: Theme.fontSizeSmall * 1.2 * 2
readonly property real extraHeight: (descriptionExpanded && expandedTextHeight > twoLineHeight + 2) ? (expandedTextHeight - twoLineHeight) : 0
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.leftMargin: 16 anchors.leftMargin: Theme.spacingL
anchors.rightMargin: 56 anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
height: 98 height: collapsedContentHeight + extraHeight
DankCircularImage { DankCircularImage {
id: iconContainer id: iconContainer
@@ -366,10 +387,10 @@ PanelWindow {
readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== "" readonly property bool hasNotificationImage: notificationData && notificationData.image && notificationData.image !== ""
readonly property bool needsImagePersist: hasNotificationImage && notificationData.image.startsWith("image://qsimage/") && !notificationData.persistedImagePath readonly property bool needsImagePersist: hasNotificationImage && notificationData.image.startsWith("image://qsimage/") && !notificationData.persistedImagePath
width: 63 width: popupIconSize
height: 63 height: popupIconSize
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.top: parent.top
imageSource: { imageSource: {
if (!notificationData) if (!notificationData)
@@ -412,81 +433,78 @@ PanelWindow {
} }
} }
Rectangle { Column {
id: textContainer id: textContainer
anchors.left: iconContainer.right anchors.left: iconContainer.right
anchors.leftMargin: 12 anchors.leftMargin: Theme.spacingM
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom spacing: compactMode ? 1 : 2
anchors.bottomMargin: 8
color: "transparent"
Item { StyledText {
width: parent.width width: parent.width
height: parent.height text: {
anchors.top: parent.top if (!notificationData)
anchors.topMargin: -2 return "";
const appName = notificationData.appName || "";
const timeStr = notificationData.timeStr || "";
return timeStr.length > 0 ? appName + " • " + timeStr : appName;
}
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
maximumLineCount: 1
visible: text.length > 0
}
Column { StyledText {
width: parent.width text: notificationData ? (notificationData.summary || "") : ""
spacing: 2 color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
maximumLineCount: 1
visible: text.length > 0
}
StyledText { StyledText {
width: parent.width id: bodyText
text: { property bool hasMoreText: truncated
if (!notificationData)
return "";
const appName = notificationData.appName || ""; text: notificationData ? (notificationData.htmlBody || "") : ""
const timeStr = notificationData.timeStr || ""; color: Theme.surfaceVariantText
if (timeStr.length > 0) font.pixelSize: Theme.fontSizeSmall
return appName + " • " + timeStr; width: parent.width
else elide: descriptionExpanded ? Text.ElideNone : Text.ElideRight
return appName; horizontalAlignment: Text.AlignLeft
} maximumLineCount: descriptionExpanded ? -1 : (compactMode ? 1 : 2)
color: Theme.surfaceVariantText wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall visible: text.length > 0
font.weight: Font.Medium linkColor: Theme.primary
elide: Text.ElideRight onLinkActivated: link => Qt.openUrlExternally(link)
horizontalAlignment: Text.AlignLeft
maximumLineCount: 1 MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : (bodyText.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || descriptionExpanded))
win.descriptionExpanded = !win.descriptionExpanded;
} }
StyledText { propagateComposedEvents: true
text: notificationData ? (notificationData.summary || "") : "" onPressed: mouse => {
color: Theme.surfaceText if (parent.hoveredLink)
font.pixelSize: Theme.fontSizeMedium mouse.accepted = false;
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
maximumLineCount: 1
visible: text.length > 0
} }
onReleased: mouse => {
StyledText { if (parent.hoveredLink)
text: notificationData ? (notificationData.htmlBody || "") : "" mouse.accepted = false;
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
maximumLineCount: 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: link => {
return Qt.openUrlExternally(link);
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
} }
} }
} }
@@ -498,11 +516,11 @@ PanelWindow {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 12 anchors.topMargin: cardPadding
anchors.rightMargin: 16 anchors.rightMargin: Theme.spacingL
iconName: "close" iconName: "close"
iconSize: 18 iconSize: compactMode ? 16 : 18
buttonSize: 28 buttonSize: compactMode ? 24 : 28
z: 15 z: 15
onClicked: { onClicked: {
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
@@ -511,11 +529,11 @@ PanelWindow {
} }
Row { Row {
anchors.right: clearButton.left anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: 8 anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
spacing: 8 spacing: contentSpacing
z: 20 z: 20
Repeater { Repeater {
@@ -524,9 +542,9 @@ PanelWindow {
Rectangle { Rectangle {
property bool isHovered: false property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50) width: Math.max(actionText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText { StyledText {
@@ -550,7 +568,6 @@ PanelWindow {
onClicked: { onClicked: {
if (modelData && modelData.invoke) if (modelData && modelData.invoke)
modelData.invoke(); modelData.invoke();
if (notificationData && !win.exiting) if (notificationData && !win.exiting)
notificationData.popup = false; notificationData.popup = false;
} }
@@ -563,14 +580,16 @@ PanelWindow {
id: clearButton id: clearButton
property bool isHovered: false property bool isHovered: false
readonly property int actionCount: notificationData ? (notificationData.actions || []).length : 0
visible: actionCount < 3
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 16 anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 8 anchors.bottomMargin: contentSpacing
width: Math.max(clearTextLabel.implicitWidth + 12, 50) width: Math.max(clearTextLabel.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: 24 height: actionButtonHeight
radius: 4 radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent" color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
z: 20 z: 20

View File

@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import qs.Common
import qs.Services import qs.Services
QtObject { QtObject {
@@ -6,7 +7,12 @@ QtObject {
property var modelData property var modelData
property int topMargin: 0 property int topMargin: 0
property int baseNotificationHeight: 120 readonly property bool compactMode: SettingsData.notificationCompactMode
readonly property real cardPadding: compactMode ? Theme.spacingS : Theme.spacingM
readonly property real popupIconSize: compactMode ? 48 : 63
readonly property real actionButtonHeight: compactMode ? 20 : 24
readonly property real popupSpacing: 4
readonly property int baseNotificationHeight: cardPadding * 2 + popupIconSize + actionButtonHeight + Theme.spacingS + popupSpacing
property int maxTargetNotifications: 4 property int maxTargetNotifications: 4
property var popupWindows: [] // strong refs to windows (live until exitFinished) property var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set() property var destroyingWindows: new Set()

View File

@@ -262,9 +262,7 @@ DankOSD {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!vertSlider.dragging) { vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
}
} }
} }
} }

View File

@@ -26,6 +26,8 @@ Item {
readonly property bool showOnOverview: instanceData?.config?.showOnOverview ?? false readonly property bool showOnOverview: instanceData?.config?.showOnOverview ?? false
readonly property bool showOnOverviewOnly: instanceData?.config?.showOnOverviewOnly ?? false readonly property bool showOnOverviewOnly: instanceData?.config?.showOnOverviewOnly ?? false
readonly property bool overviewActive: CompositorService.isNiri && NiriService.inOverview readonly property bool overviewActive: CompositorService.isNiri && NiriService.inOverview
readonly property bool clickThrough: instanceData?.config?.clickThrough ?? false
readonly property bool syncPositionAcrossScreens: instanceData?.config?.syncPositionAcrossScreens ?? false
Connections { Connections {
target: PluginService target: PluginService
@@ -83,6 +85,7 @@ Item {
} }
} }
readonly property string screenKey: SettingsData.getScreenDisplayName(screen) readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
readonly property string positionKey: syncPositionAcrossScreens ? "_synced" : screenKey
readonly property int screenWidth: screen?.width ?? 1920 readonly property int screenWidth: screen?.width ?? 1920
readonly property int screenHeight: screen?.height ?? 1080 readonly property int screenHeight: screen?.height ?? 1080
@@ -96,53 +99,114 @@ Item {
readonly property bool hasSavedPosition: { readonly property bool hasSavedPosition: {
if (isInstance) if (isInstance)
return instanceData?.positions?.[screenKey]?.x !== undefined; return instanceData?.positions?.[positionKey]?.x !== undefined;
if (usePluginService) if (usePluginService)
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, null) !== null; return pluginService.loadPluginData(pluginId, "desktopX_" + positionKey, null) !== null;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", null) !== null; return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "x", null) !== null;
} }
readonly property bool hasSavedSize: { readonly property bool hasSavedSize: {
if (isInstance) if (isInstance)
return instanceData?.positions?.[screenKey]?.width !== undefined; return instanceData?.positions?.[positionKey]?.width !== undefined;
if (usePluginService) if (usePluginService)
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, null) !== null; return pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null) !== null;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", null) !== null; return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null) !== null;
} }
property real savedX: { property real savedX: {
if (isInstance) if (isInstance) {
return instanceData?.positions?.[screenKey]?.x ?? (screenWidth / 2 - savedWidth / 2); const val = instanceData?.positions?.[positionKey]?.x;
if (usePluginService) if (val === undefined)
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, screenWidth / 2 - savedWidth / 2); return screenWidth / 2 - savedWidth / 2;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", screenWidth / 2 - savedWidth / 2); return syncPositionAcrossScreens ? val * screenWidth : val;
}
if (usePluginService) {
const val = pluginService.loadPluginData(pluginId, "desktopX_" + positionKey, null);
if (val === null)
return screenWidth / 2 - savedWidth / 2;
return syncPositionAcrossScreens ? val * screenWidth : val;
}
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "x", null);
if (val === null)
return screenWidth / 2 - savedWidth / 2;
return syncPositionAcrossScreens ? val * screenWidth : val;
} }
property real savedY: { property real savedY: {
if (isInstance) if (isInstance) {
return instanceData?.positions?.[screenKey]?.y ?? (screenHeight / 2 - savedHeight / 2); const val = instanceData?.positions?.[positionKey]?.y;
if (usePluginService) if (val === undefined)
return pluginService.loadPluginData(pluginId, "desktopY_" + screenKey, screenHeight / 2 - savedHeight / 2); return screenHeight / 2 - savedHeight / 2;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "y", screenHeight / 2 - savedHeight / 2); return syncPositionAcrossScreens ? val * screenHeight : val;
}
if (usePluginService) {
const val = pluginService.loadPluginData(pluginId, "desktopY_" + positionKey, null);
if (val === null)
return screenHeight / 2 - savedHeight / 2;
return syncPositionAcrossScreens ? val * screenHeight : val;
}
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "y", null);
if (val === null)
return screenHeight / 2 - savedHeight / 2;
return syncPositionAcrossScreens ? val * screenHeight : val;
} }
property real savedWidth: { property real savedWidth: {
if (isInstance) if (isInstance) {
return instanceData?.positions?.[screenKey]?.width ?? 280; const val = instanceData?.positions?.[positionKey]?.width;
if (usePluginService) if (val === undefined)
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, 200); return 280;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", 280); return val;
}
if (usePluginService) {
const val = pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null);
if (val === null)
return 200;
return val;
}
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null);
if (val === null)
return 280;
return val;
} }
property real savedHeight: { property real savedHeight: {
if (isInstance) if (isInstance) {
return instanceData?.positions?.[screenKey]?.height ?? 180; const val = instanceData?.positions?.[positionKey]?.height;
if (usePluginService) if (val === undefined)
return pluginService.loadPluginData(pluginId, "desktopHeight_" + screenKey, 200); return forceSquare ? savedWidth : 180;
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "height", 180); return forceSquare ? savedWidth : val;
}
if (usePluginService) {
const val = pluginService.loadPluginData(pluginId, "desktopHeight_" + positionKey, null);
if (val === null)
return forceSquare ? savedWidth : 200;
return forceSquare ? savedWidth : val;
}
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "height", null);
if (val === null)
return forceSquare ? savedWidth : 180;
return forceSquare ? savedWidth : val;
} }
property real widgetX: Math.max(0, Math.min(savedX, screenWidth - widgetWidth)) property real dragOverrideX: -1
property real widgetY: Math.max(0, Math.min(savedY, screenHeight - widgetHeight)) property real dragOverrideY: -1
property real widgetWidth: Math.max(minWidth, Math.min(savedWidth, screenWidth)) property real dragOverrideW: -1
property real widgetHeight: Math.max(minHeight, Math.min(savedHeight, screenHeight)) property real dragOverrideH: -1
readonly property real effectiveX: dragOverrideX >= 0 ? dragOverrideX : savedX
readonly property real effectiveY: dragOverrideY >= 0 ? dragOverrideY : savedY
readonly property real effectiveW: dragOverrideW >= 0 ? dragOverrideW : savedWidth
readonly property real effectiveH: dragOverrideH >= 0 ? dragOverrideH : savedHeight
readonly property real widgetX: Math.max(0, Math.min(effectiveX, screenWidth - widgetWidth))
readonly property real widgetY: Math.max(0, Math.min(effectiveY, screenHeight - widgetHeight))
readonly property real widgetWidth: Math.max(minWidth, Math.min(effectiveW, screenWidth))
readonly property real widgetHeight: Math.max(minHeight, Math.min(effectiveH, screenHeight))
function clearDragOverrides() {
dragOverrideX = -1;
dragOverrideY = -1;
dragOverrideW = -1;
dragOverrideH = -1;
}
property real minWidth: contentLoader.item?.minWidth ?? 100 property real minWidth: contentLoader.item?.minWidth ?? 100
property real minHeight: contentLoader.item?.minHeight ?? 100 property real minHeight: contentLoader.item?.minHeight ?? 100
@@ -163,41 +227,45 @@ Item {
return Math.round(value / gridSize) * gridSize; return Math.round(value / gridSize) * gridSize;
} }
function savePosition() { function savePosition(finalX, finalY) {
const xVal = syncPositionAcrossScreens ? finalX / screenWidth : finalX;
const yVal = syncPositionAcrossScreens ? finalY / screenHeight : finalY;
if (isInstance && instanceData) { if (isInstance && instanceData) {
SettingsData.updateDesktopWidgetInstancePosition(instanceId, screenKey, { SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
x: root.widgetX, x: xVal,
y: root.widgetY y: yVal
}); });
return; return;
} }
if (usePluginService) { if (usePluginService) {
pluginService.savePluginData(pluginId, "desktopX_" + screenKey, root.widgetX); pluginService.savePluginData(pluginId, "desktopX_" + positionKey, xVal);
pluginService.savePluginData(pluginId, "desktopY_" + screenKey, root.widgetY); pluginService.savePluginData(pluginId, "desktopY_" + positionKey, yVal);
return; return;
} }
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, { SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, {
x: root.widgetX, x: xVal,
y: root.widgetY y: yVal
}); });
} }
function saveSize() { function saveSize(finalW, finalH) {
const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW;
const heightVal = forceSquare ? sizeVal : finalH;
if (isInstance && instanceData) { if (isInstance && instanceData) {
SettingsData.updateDesktopWidgetInstancePosition(instanceId, screenKey, { SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
width: root.widgetWidth, width: sizeVal,
height: root.widgetHeight height: heightVal
}); });
return; return;
} }
if (usePluginService) { if (usePluginService) {
pluginService.savePluginData(pluginId, "desktopWidth_" + screenKey, root.widgetWidth); pluginService.savePluginData(pluginId, "desktopWidth_" + positionKey, sizeVal);
pluginService.savePluginData(pluginId, "desktopHeight_" + screenKey, root.widgetHeight); pluginService.savePluginData(pluginId, "desktopHeight_" + positionKey, heightVal);
return; return;
} }
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, { SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, {
width: root.widgetWidth, width: sizeVal,
height: root.widgetHeight height: heightVal
}); });
} }
@@ -213,6 +281,12 @@ Item {
} }
color: "transparent" color: "transparent"
Region {
id: emptyMask
}
mask: root.clickThrough ? emptyMask : null
WlrLayershell.namespace: "quickshell:desktop-widget:" + root.pluginId + (root.instanceId ? ":" + root.instanceId : "") WlrLayershell.namespace: "quickshell:desktop-widget:" + root.pluginId + (root.instanceId ? ":" + root.instanceId : "")
WlrLayershell.layer: { WlrLayershell.layer: {
if (root.isInteracting && !CompositorService.useHyprlandFocusGrab) if (root.isInteracting && !CompositorService.useHyprlandFocusGrab)
@@ -315,12 +389,14 @@ Item {
if (!root.hasSavedSize) { if (!root.hasSavedSize) {
const defW = item.defaultWidth ?? item.widgetWidth ?? 280; const defW = item.defaultWidth ?? item.widgetWidth ?? 280;
const defH = item.defaultHeight ?? item.widgetHeight ?? 180; const defH = item.defaultHeight ?? item.widgetHeight ?? 180;
root.widgetWidth = Math.max(root.minWidth, Math.min(defW, root.screenWidth)); const finalW = Math.max(root.minWidth, Math.min(defW, root.screenWidth));
root.widgetHeight = Math.max(root.minHeight, Math.min(defH, root.screenHeight)); const finalH = Math.max(root.minHeight, Math.min(defH, root.screenHeight));
root.saveSize(finalW, finalH);
} }
if (!root.hasSavedPosition) { if (!root.hasSavedPosition) {
root.widgetX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth)); const finalX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth));
root.widgetY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight)); const finalY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight));
root.savePosition(finalX, finalY);
} }
if (item.widgetWidth !== undefined) if (item.widgetWidth !== undefined)
item.widgetWidth = Qt.binding(() => contentLoader.width); item.widgetWidth = Qt.binding(() => contentLoader.width);
@@ -355,6 +431,7 @@ Item {
id: dragArea id: dragArea
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
enabled: !root.clickThrough
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.ArrowCursor cursorShape: pressed ? Qt.ClosedHandCursor : Qt.ArrowCursor
property point startPos property point startPos
@@ -367,6 +444,8 @@ Item {
startY = root.widgetY; startY = root.widgetY;
root.previewX = root.widgetX; root.previewX = root.widgetX;
root.previewY = root.widgetY; root.previewY = root.widgetY;
root.dragOverrideX = root.widgetX;
root.dragOverrideY = root.widgetY;
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
@@ -384,16 +463,15 @@ Item {
root.previewY = newY; root.previewY = newY;
return; return;
} }
root.widgetX = newX; root.dragOverrideX = newX;
root.widgetY = newY; root.dragOverrideY = newY;
} }
onReleased: { onReleased: {
if (root.useGhostPreview) { const finalX = root.useGhostPreview ? root.previewX : root.dragOverrideX;
root.widgetX = root.previewX; const finalY = root.useGhostPreview ? root.previewY : root.dragOverrideY;
root.widgetY = root.previewY; root.savePosition(finalX, finalY);
} root.clearDragOverrides();
root.savePosition();
} }
} }
@@ -404,6 +482,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
enabled: !root.clickThrough
cursorShape: pressed ? Qt.SizeFDiagCursor : Qt.ArrowCursor cursorShape: pressed ? Qt.SizeFDiagCursor : Qt.ArrowCursor
property point startPos property point startPos
@@ -416,6 +495,8 @@ Item {
startHeight = root.widgetHeight; startHeight = root.widgetHeight;
root.previewWidth = root.widgetWidth; root.previewWidth = root.widgetWidth;
root.previewHeight = root.widgetHeight; root.previewHeight = root.widgetHeight;
root.dragOverrideW = root.widgetWidth;
root.dragOverrideH = root.widgetHeight;
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
@@ -438,16 +519,15 @@ Item {
root.previewHeight = newH; root.previewHeight = newH;
return; return;
} }
root.widgetWidth = newW; root.dragOverrideW = newW;
root.widgetHeight = newH; root.dragOverrideH = newH;
} }
onReleased: { onReleased: {
if (root.useGhostPreview) { const finalW = root.useGhostPreview ? root.previewWidth : root.dragOverrideW;
root.widgetWidth = root.previewWidth; const finalH = root.useGhostPreview ? root.previewHeight : root.dragOverrideH;
root.widgetHeight = root.previewHeight; root.saveSize(finalW, finalH);
} root.clearDragOverrides();
root.saveSize();
} }
} }
} }

View File

@@ -177,10 +177,11 @@ Item {
StyledText { StyledText {
text: { text: {
if (!SystemUpdateService.shellVersion) if (!SystemUpdateService.shellVersion && !DMSService.cliVersion)
return "dms"; return "dms";
let version = SystemUpdateService.shellVersion; let version = SystemUpdateService.shellVersion || "";
let cliVersion = DMSService.cliVersion || "";
// Debian/Ubuntu/OpenSUSE git format: 1.0.3+git2264.c5c5ce84 // Debian/Ubuntu/OpenSUSE git format: 1.0.3+git2264.c5c5ce84
let match = version.match(/^([\d.]+)\+git(\d+)\./); let match = version.match(/^([\d.]+)\+git(\d+)\./);
@@ -191,7 +192,25 @@ Item {
// Fedora COPR git format: 0.0.git.2267.d430cae9 // Fedora COPR git format: 0.0.git.2267.d430cae9
match = version.match(/^[\d.]+\.git\.(\d+)\./); match = version.match(/^[\d.]+\.git\.(\d+)\./);
if (match) { if (match) {
return `dms (git) v1.0.3-${match[1]}`; function extractBaseVersion(value) {
if (!value)
return "";
let baseMatch = value.match(/(\d+\.\d+\.\d+)/);
if (baseMatch)
return baseMatch[1];
baseMatch = value.match(/(\d+\.\d+)/);
if (baseMatch)
return baseMatch[1];
return "";
}
let baseVersion = extractBaseVersion(cliVersion);
if (!baseVersion)
baseVersion = extractBaseVersion(SystemUpdateService.semverVersion);
if (baseVersion) {
return `dms (git) v${baseVersion}-${match[1]}`;
}
return `dms (git) v${match[1]}`;
} }
// Stable release format: 1.0.3 // Stable release format: 1.0.3
@@ -200,6 +219,18 @@ Item {
return `dms v${match[1]}`; return `dms v${match[1]}`;
} }
if (!version && cliVersion) {
match = cliVersion.match(/^([\d.]+)\+git(\d+)\./);
if (match) {
return `dms (git) v${match[1]}-${match[2]}`;
}
match = cliVersion.match(/^([\d.]+)$/);
if (match) {
return `dms v${match[1]}`;
}
return `dms ${cliVersion}`;
}
return `dms ${version}`; return `dms ${version}`;
} }
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge

View File

@@ -1,4 +1,4 @@
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell

View File

@@ -63,7 +63,13 @@ SettingsCard {
DankActionButton { DankActionButton {
id: menuButton id: menuButton
iconName: "more_vert" iconName: "more_vert"
onClicked: actionsMenu.open() onClicked: {
if (actionsMenu.opened) {
actionsMenu.close();
return;
}
actionsMenu.open();
}
Popup { Popup {
id: actionsMenu id: actionsMenu
@@ -71,7 +77,7 @@ SettingsCard {
y: parent.height + Theme.spacingXS y: parent.height + Theme.spacingXS
width: 160 width: 160
padding: Theme.spacingXS padding: Theme.spacingXS
modal: true modal: false
focus: true focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
@@ -218,6 +224,73 @@ SettingsCard {
SettingsDivider {} SettingsDivider {}
Item {
width: parent.width
height: groupRow.height + Theme.spacingM * 2
visible: (SettingsData.desktopWidgetGroups || []).length > 0
Row {
id: groupRow
x: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
width: parent.width - Theme.spacingM * 2
StyledText {
text: I18n.tr("Group")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: 80
horizontalAlignment: Text.AlignLeft
}
DankDropdown {
id: groupDropdown
width: parent.width - 80 - Theme.spacingM
compactMode: true
property var groupsData: {
const groups = SettingsData.desktopWidgetGroups || [];
const items = [
{
value: "",
label: I18n.tr("None")
}
];
for (const g of groups) {
items.push({
value: g.id,
label: g.name
});
}
return items;
}
options: groupsData.map(g => g.label)
currentValue: {
const currentGroup = root.instanceData?.group ?? "";
const item = groupsData.find(g => g.value === currentGroup);
return item?.label ?? I18n.tr("None");
}
onValueChanged: value => {
if (!root.instanceId)
return;
const item = groupsData.find(g => g.label === value);
const groupId = item?.value ?? "";
SettingsData.updateDesktopWidgetInstance(root.instanceId, {
group: groupId || null
});
}
}
}
}
SettingsDivider {
visible: (SettingsData.desktopWidgetGroups || []).length > 0
}
SettingsToggleRow { SettingsToggleRow {
text: I18n.tr("Show on Overlay") text: I18n.tr("Show on Overlay")
checked: instanceData?.config?.showOnOverlay ?? false checked: instanceData?.config?.showOnOverlay ?? false
@@ -266,6 +339,38 @@ SettingsCard {
SettingsDivider {} SettingsDivider {}
SettingsToggleRow {
text: I18n.tr("Click Through")
description: I18n.tr("Allow clicks to pass through the widget")
checked: instanceData?.config?.clickThrough ?? false
onToggled: isChecked => {
if (!root.instanceId)
return;
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, {
clickThrough: isChecked
});
}
}
SettingsDivider {}
SettingsToggleRow {
text: I18n.tr("Sync Position Across Screens")
description: I18n.tr("Use the same position and size on all displays")
checked: instanceData?.config?.syncPositionAcrossScreens ?? false
onToggled: isChecked => {
if (!root.instanceId)
return;
if (isChecked)
SettingsData.syncDesktopWidgetPositionToAllScreens(root.instanceId);
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, {
syncPositionAcrossScreens: isChecked
});
}
}
SettingsDivider {}
Item { Item {
width: parent.width width: parent.width
height: ipcColumn.height + Theme.spacingM * 2 height: ipcColumn.height + Theme.spacingM * 2

View File

@@ -14,20 +14,46 @@ Item {
LayoutMirroring.childrenInherit: true LayoutMirroring.childrenInherit: true
property var expandedStates: ({}) property var expandedStates: ({})
property var groupCollapsedStates: ({})
property var parentModal: null property var parentModal: null
property string editingGroupId: ""
property string newGroupName: ""
DesktopWidgetBrowser { readonly property var allInstances: SettingsData.desktopWidgetInstances || []
id: widgetBrowser readonly property var allGroups: SettingsData.desktopWidgetGroups || []
parentModal: root.parentModal
onWidgetAdded: widgetType => { function showWidgetBrowser() {
ToastService.showInfo(I18n.tr("Widget added")); widgetBrowserLoader.active = true;
if (widgetBrowserLoader.item)
widgetBrowserLoader.item.show();
}
function showDesktopPluginBrowser() {
desktopPluginBrowserLoader.active = true;
if (desktopPluginBrowserLoader.item)
desktopPluginBrowserLoader.item.show();
}
LazyLoader {
id: widgetBrowserLoader
active: false
DesktopWidgetBrowser {
parentModal: root.parentModal
onWidgetAdded: widgetType => {
ToastService.showInfo(I18n.tr("Widget added"));
}
} }
} }
PluginBrowser { LazyLoader {
id: desktopPluginBrowser id: desktopPluginBrowserLoader
parentModal: root.parentModal active: false
typeFilter: "desktop-widget"
PluginBrowser {
parentModal: root.parentModal
typeFilter: "desktop-widget"
}
} }
DankFlickable { DankFlickable {
@@ -68,66 +94,512 @@ Item {
DankButton { DankButton {
text: I18n.tr("Add Widget") text: I18n.tr("Add Widget")
iconName: "add" iconName: "add"
onClicked: widgetBrowser.show() onClicked: root.showWidgetBrowser()
} }
DankButton { DankButton {
text: I18n.tr("Browse Plugins") text: I18n.tr("Browse Plugins")
iconName: "store" iconName: "store"
onClicked: desktopPluginBrowser.show() onClicked: root.showDesktopPluginBrowser()
}
}
}
}
SettingsCard {
width: parent.width
iconName: "folder"
title: I18n.tr("Groups")
collapsible: true
expanded: root.allGroups.length > 0
Column {
width: parent.width - Theme.spacingM * 2
x: Theme.spacingM
spacing: Theme.spacingM
StyledText {
width: parent.width
text: I18n.tr("Organize widgets into collapsible groups")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}
Row {
spacing: Theme.spacingS
width: parent.width
DankTextField {
id: newGroupField
width: parent.width - addGroupBtn.width - Theme.spacingS
placeholderText: I18n.tr("New group name...")
text: root.newGroupName
onTextChanged: root.newGroupName = text
onAccepted: {
if (!text.trim())
return;
SettingsData.createDesktopWidgetGroup(text.trim());
root.newGroupName = "";
text = "";
}
}
DankButton {
id: addGroupBtn
iconName: "add"
text: I18n.tr("Add")
enabled: root.newGroupName.trim().length > 0
onClicked: {
SettingsData.createDesktopWidgetGroup(root.newGroupName.trim());
root.newGroupName = "";
newGroupField.text = "";
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
visible: root.allGroups.length > 0
Repeater {
model: root.allGroups
Rectangle {
id: groupItem
required property var modelData
required property int index
width: parent.width
height: 40
radius: Theme.cornerRadius
color: groupMouseArea.containsMouse ? Theme.surfaceHover : Theme.surfaceContainer
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: "folder"
size: Theme.iconSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Loader {
active: root.editingGroupId === groupItem.modelData.id
width: active ? parent.width - Theme.iconSizeSmall - deleteGroupBtn.width - Theme.spacingS * 3 : 0
height: active ? 32 : 0
anchors.verticalCenter: parent.verticalCenter
sourceComponent: DankTextField {
text: groupItem.modelData.name
onAccepted: {
if (!text.trim())
return;
SettingsData.updateDesktopWidgetGroup(groupItem.modelData.id, {
name: text.trim()
});
root.editingGroupId = "";
}
onEditingFinished: {
if (!text.trim())
return;
SettingsData.updateDesktopWidgetGroup(groupItem.modelData.id, {
name: text.trim()
});
root.editingGroupId = "";
}
Component.onCompleted: forceActiveFocus()
}
}
StyledText {
visible: root.editingGroupId !== groupItem.modelData.id
text: groupItem.modelData.name
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.width - Theme.iconSizeSmall - deleteGroupBtn.width - Theme.spacingS * 3
}
DankActionButton {
id: deleteGroupBtn
iconName: "delete"
anchors.verticalCenter: parent.verticalCenter
onClicked: {
SettingsData.removeDesktopWidgetGroup(groupItem.modelData.id);
ToastService.showInfo(I18n.tr("Group removed"));
}
}
}
MouseArea {
id: groupMouseArea
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: root.editingGroupId = groupItem.modelData.id
}
}
}
}
}
}
Repeater {
model: root.allGroups
Column {
id: groupSection
required property var modelData
required property int index
readonly property string groupId: modelData.id
readonly property var groupInstances: root.allInstances.filter(inst => inst.group === groupId)
width: mainColumn.width
spacing: Theme.spacingM
visible: groupInstances.length > 0
Rectangle {
width: parent.width
height: 44
radius: Theme.cornerRadius
color: Theme.surfaceContainer
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: (root.groupCollapsedStates[groupSection.groupId] ?? false) ? "expand_more" : "expand_less"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankIcon {
name: "folder"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: groupSection.modelData.name
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "(" + groupSection.groupInstances.length + ")"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var states = Object.assign({}, root.groupCollapsedStates);
states[groupSection.groupId] = !(states[groupSection.groupId] ?? false);
root.groupCollapsedStates = states;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: !(root.groupCollapsedStates[groupSection.groupId] ?? false)
leftPadding: Theme.spacingM
Repeater {
model: ScriptModel {
objectProp: "id"
values: groupSection.groupInstances
}
Item {
id: groupDelegateItem
required property var modelData
required property int index
property bool held: groupDragArea.pressed
property real originalY: y
readonly property string instanceIdRef: modelData.id
readonly property var liveInstanceData: {
const instances = root.allInstances;
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
}
width: groupSection.width - Theme.spacingM
height: groupCard.height
z: held ? 2 : 1
DesktopWidgetInstanceCard {
id: groupCard
width: parent.width
headerLeftPadding: 20
instanceData: groupDelegateItem.liveInstanceData
isExpanded: root.expandedStates[groupDelegateItem.instanceIdRef] ?? false
onExpandedChanged: {
if (expanded === (root.expandedStates[groupDelegateItem.instanceIdRef] ?? false))
return;
var states = Object.assign({}, root.expandedStates);
states[groupDelegateItem.instanceIdRef] = expanded;
root.expandedStates = states;
}
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(groupDelegateItem.instanceIdRef)
onDeleteRequested: {
SettingsData.removeDesktopWidgetInstance(groupDelegateItem.instanceIdRef);
ToastService.showInfo(I18n.tr("Widget removed"));
}
}
MouseArea {
id: groupDragArea
anchors.left: parent.left
anchors.top: parent.top
width: 40
height: 50
hoverEnabled: true
cursorShape: Qt.SizeVerCursor
drag.target: groupDelegateItem.held ? groupDelegateItem : undefined
drag.axis: Drag.YAxis
preventStealing: true
onPressed: {
groupDelegateItem.z = 2;
groupDelegateItem.originalY = groupDelegateItem.y;
}
onReleased: {
groupDelegateItem.z = 1;
if (!drag.active) {
groupDelegateItem.y = groupDelegateItem.originalY;
return;
}
const spacing = Theme.spacingM;
const itemH = groupDelegateItem.height + spacing;
var newIndex = Math.round(groupDelegateItem.y / itemH);
newIndex = Math.max(0, Math.min(newIndex, groupSection.groupInstances.length - 1));
if (newIndex !== groupDelegateItem.index)
SettingsData.reorderDesktopWidgetInstanceInGroup(groupDelegateItem.instanceIdRef, groupSection.groupId, newIndex);
groupDelegateItem.y = groupDelegateItem.originalY;
}
}
DankIcon {
x: Theme.spacingL - 2
y: Theme.spacingL + (Theme.iconSize / 2) - (size / 2)
name: "drag_indicator"
size: 18
color: Theme.outline
opacity: groupDragArea.containsMouse || groupDragArea.pressed ? 1 : 0.5
}
Behavior on y {
enabled: !groupDragArea.pressed && !groupDragArea.drag.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
} }
} }
} }
} }
Column { Column {
id: instancesColumn id: ungroupedSection
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: SettingsData.desktopWidgetInstances.length > 0 visible: ungroupedInstances.length > 0
Repeater { readonly property var ungroupedInstances: root.allInstances.filter(inst => {
id: instanceRepeater if (!inst.group)
model: ScriptModel { return true;
id: instancesModel return !root.allGroups.some(g => g.id === inst.group);
objectProp: "id" })
values: SettingsData.desktopWidgetInstances
Rectangle {
width: parent.width
height: 44
radius: Theme.cornerRadius
color: Theme.surfaceContainer
visible: root.allGroups.length > 0
Row {
anchors.fill: parent
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: (root.groupCollapsedStates["_ungrouped"] ?? false) ? "expand_more" : "expand_less"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
DankIcon {
name: "widgets"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Ungrouped")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "(" + ungroupedSection.ungroupedInstances.length + ")"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
} }
DesktopWidgetInstanceCard { MouseArea {
required property var modelData anchors.fill: parent
required property int index cursorShape: Qt.PointingHandCursor
onClicked: {
var states = Object.assign({}, root.groupCollapsedStates);
states["_ungrouped"] = !(states["_ungrouped"] ?? false);
root.groupCollapsedStates = states;
}
}
}
readonly property string instanceIdRef: modelData.id Column {
readonly property var liveInstanceData: { width: parent.width
const instances = SettingsData.desktopWidgetInstances || []; spacing: Theme.spacingM
return instances.find(inst => inst.id === instanceIdRef) ?? modelData; visible: !(root.groupCollapsedStates["_ungrouped"] ?? false)
leftPadding: root.allGroups.length > 0 ? Theme.spacingM : 0
Repeater {
model: ScriptModel {
objectProp: "id"
values: ungroupedSection.ungroupedInstances
} }
width: instancesColumn.width Item {
instanceData: liveInstanceData id: ungroupedDelegateItem
isExpanded: root.expandedStates[instanceIdRef] ?? false required property var modelData
required property int index
onExpandedChanged: { property bool held: ungroupedDragArea.pressed
if (expanded === (root.expandedStates[instanceIdRef] ?? false)) property real originalY: y
return;
var states = Object.assign({}, root.expandedStates);
states[instanceIdRef] = expanded;
root.expandedStates = states;
}
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(instanceIdRef) readonly property string instanceIdRef: modelData.id
readonly property var liveInstanceData: {
const instances = root.allInstances;
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
}
onDeleteRequested: { width: ungroupedSection.width - (root.allGroups.length > 0 ? Theme.spacingM : 0)
SettingsData.removeDesktopWidgetInstance(instanceIdRef); height: ungroupedCard.height
ToastService.showInfo(I18n.tr("Widget removed")); z: held ? 2 : 1
DesktopWidgetInstanceCard {
id: ungroupedCard
width: parent.width
headerLeftPadding: 20
instanceData: ungroupedDelegateItem.liveInstanceData
isExpanded: root.expandedStates[ungroupedDelegateItem.instanceIdRef] ?? false
onExpandedChanged: {
if (expanded === (root.expandedStates[ungroupedDelegateItem.instanceIdRef] ?? false))
return;
var states = Object.assign({}, root.expandedStates);
states[ungroupedDelegateItem.instanceIdRef] = expanded;
root.expandedStates = states;
}
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(ungroupedDelegateItem.instanceIdRef)
onDeleteRequested: {
SettingsData.removeDesktopWidgetInstance(ungroupedDelegateItem.instanceIdRef);
ToastService.showInfo(I18n.tr("Widget removed"));
}
}
MouseArea {
id: ungroupedDragArea
anchors.left: parent.left
anchors.top: parent.top
width: 40
height: 50
hoverEnabled: true
cursorShape: Qt.SizeVerCursor
drag.target: ungroupedDelegateItem.held ? ungroupedDelegateItem : undefined
drag.axis: Drag.YAxis
preventStealing: true
onPressed: {
ungroupedDelegateItem.z = 2;
ungroupedDelegateItem.originalY = ungroupedDelegateItem.y;
}
onReleased: {
ungroupedDelegateItem.z = 1;
if (!drag.active) {
ungroupedDelegateItem.y = ungroupedDelegateItem.originalY;
return;
}
const spacing = Theme.spacingM;
const itemH = ungroupedDelegateItem.height + spacing;
var newIndex = Math.round(ungroupedDelegateItem.y / itemH);
newIndex = Math.max(0, Math.min(newIndex, ungroupedSection.ungroupedInstances.length - 1));
if (newIndex !== ungroupedDelegateItem.index)
SettingsData.reorderDesktopWidgetInstanceInGroup(ungroupedDelegateItem.instanceIdRef, null, newIndex);
ungroupedDelegateItem.y = ungroupedDelegateItem.originalY;
}
}
DankIcon {
x: Theme.spacingL - 2
y: Theme.spacingL + (Theme.iconSize / 2) - (size / 2)
name: "drag_indicator"
size: 18
color: Theme.outline
opacity: ungroupedDragArea.containsMouse || ungroupedDragArea.pressed ? 1 : 0.5
}
Behavior on y {
enabled: !ungroupedDragArea.pressed && !ungroupedDragArea.drag.active
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
} }
} }
} }
StyledText { StyledText {
visible: SettingsData.desktopWidgetInstances.length === 0 visible: root.allInstances.length === 0
text: I18n.tr("No widgets added. Click \"Add Widget\" to get started.") text: I18n.tr("No widgets added. Click \"Add Widget\" to get started.")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -147,6 +619,7 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
Row { Row {
width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Rectangle { Rectangle {
@@ -188,6 +661,7 @@ Item {
} }
Row { Row {
width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Rectangle { Rectangle {

View File

@@ -29,6 +29,9 @@ Item {
SettingsButtonGroupRow { SettingsButtonGroupRow {
text: I18n.tr("Position") text: I18n.tr("Position")
model: ["Top", "Bottom", "Left", "Right"] model: ["Top", "Bottom", "Left", "Right"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: { currentIndex: {
switch (SettingsData.dockPosition) { switch (SettingsData.dockPosition) {
case SettingsData.Position.Top: case SettingsData.Position.Top:
@@ -129,6 +132,9 @@ Item {
tags: ["dock", "indicator", "style", "circle", "line"] tags: ["dock", "indicator", "style", "circle", "line"]
text: I18n.tr("Indicator Style") text: I18n.tr("Indicator Style")
model: ["Circle", "Line"] model: ["Circle", "Line"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: SettingsData.dockIndicatorStyle === "circle" ? 0 : 1 currentIndex: SettingsData.dockIndicatorStyle === "circle" ? 0 : 1
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (selected) { if (selected) {
@@ -225,6 +231,9 @@ Item {
description: I18n.tr("Choose the border accent color") description: I18n.tr("Choose the border accent color")
visible: SettingsData.dockBorderEnabled visible: SettingsData.dockBorderEnabled
model: ["Surface", "Secondary", "Primary"] model: ["Surface", "Secondary", "Primary"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: { currentIndex: {
switch (SettingsData.dockBorderColor) { switch (SettingsData.dockBorderColor) {
case "surfaceText": case "surfaceText":

View File

@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Common import qs.Common
import qs.Modals.Common import qs.Modals.Common
import qs.Modals.FileBrowser import qs.Modals.FileBrowser
@@ -14,6 +15,7 @@ Item {
property string expandedVpnUuid: "" property string expandedVpnUuid: ""
property string expandedWifiSsid: "" property string expandedWifiSsid: ""
property string expandedEthDevice: "" property string expandedEthDevice: ""
property int maxPinnedWifiNetworks: 3
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef(); NetworkService.addRef();
@@ -23,15 +25,59 @@ Item {
NetworkService.removeRef(); NetworkService.removeRef();
} }
FileBrowserModal { function openVpnFileBrowser() {
id: vpnFileBrowser vpnFileBrowserLoader.active = true;
browserTitle: I18n.tr("Import VPN") if (vpnFileBrowserLoader.item)
browserIcon: "vpn_key" vpnFileBrowserLoader.item.open();
browserType: "vpn" }
fileExtensions: VPNService.getFileFilter()
onFileSelected: path => { function normalizePinList(value) {
VPNService.importVpn(path.replace("file://", "")); if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedWifiNetworks() {
const pins = SettingsData.wifiNetworkPins || {}
return normalizePinList(pins["preferredWifi"])
}
function toggleWifiPin(ssid) {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
let pinnedList = normalizePinList(pins["preferredWifi"])
const pinIndex = pinnedList.indexOf(ssid)
if (pinIndex !== -1) {
pinnedList.splice(pinIndex, 1)
} else {
pinnedList.unshift(ssid)
if (pinnedList.length > maxPinnedWifiNetworks)
pinnedList = pinnedList.slice(0, maxPinnedWifiNetworks)
}
if (pinnedList.length > 0)
pins["preferredWifi"] = pinnedList
else
delete pins["preferredWifi"]
SettingsData.set("wifiNetworkPins", pins)
}
LazyLoader {
id: vpnFileBrowserLoader
active: false
FileBrowserModal {
browserTitle: I18n.tr("Import VPN")
browserIcon: "vpn_key"
browserType: "vpn"
fileExtensions: VPNService.getFileFilter()
onFileSelected: path => {
VPNService.importVpn(path.replace("file://", ""));
}
} }
} }
@@ -1014,15 +1060,19 @@ Item {
model: { model: {
const ssid = NetworkService.currentWifiSSID; const ssid = NetworkService.currentWifiSSID;
const networks = NetworkService.wifiNetworks || []; const networks = NetworkService.wifiNetworks || [];
const pins = SettingsData.wifiNetworkPins || {}; const pinnedList = networkTab.getPinnedWifiNetworks();
const pinnedSSID = pins["preferredWifi"];
let sorted = [...networks]; let sorted = [...networks];
sorted.sort((a, b) => { sorted.sort((a, b) => {
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID) const aPinnedIndex = pinnedList.indexOf(a.ssid)
return -1; const bPinnedIndex = pinnedList.indexOf(b.ssid)
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID) if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
return 1; if (aPinnedIndex === -1)
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
if (a.ssid === ssid) if (a.ssid === ssid)
return -1; return -1;
if (b.ssid === ssid) if (b.ssid === ssid)
@@ -1038,7 +1088,7 @@ Item {
required property int index required property int index
readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID
readonly property bool isPinned: (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid readonly property bool isPinned: networkTab.getPinnedWifiNetworks().includes(modelData.ssid)
readonly property bool isExpanded: networkTab.expandedWifiSsid === modelData.ssid readonly property bool isExpanded: networkTab.expandedWifiSsid === modelData.ssid
width: parent.width width: parent.width
@@ -1213,13 +1263,7 @@ Item {
buttonSize: 28 buttonSize: 28
iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})); networkTab.toggleWifiPin(modelData.ssid)
if (isPinned) {
delete pins["preferredWifi"];
} else {
pins["preferredWifi"] = modelData.ssid;
}
SettingsData.set("wifiNetworkPins", pins);
} }
} }
@@ -1520,7 +1564,7 @@ Item {
hoverEnabled: true hoverEnabled: true
cursorShape: VPNService.importing ? Qt.BusyCursor : Qt.PointingHandCursor cursorShape: VPNService.importing ? Qt.BusyCursor : Qt.PointingHandCursor
enabled: !VPNService.importing enabled: !VPNService.importing
onClicked: vpnFileBrowser.open() onClicked: networkTab.openVpnFileBrowser()
} }
} }

View File

@@ -144,6 +144,15 @@ Item {
checked: SettingsData.notificationOverlayEnabled checked: SettingsData.notificationOverlayEnabled
onToggled: checked => SettingsData.set("notificationOverlayEnabled", checked) onToggled: checked => SettingsData.set("notificationOverlayEnabled", checked)
} }
SettingsToggleRow {
settingKey: "notificationCompactMode"
tags: ["notification", "compact", "size", "display", "mode"]
text: I18n.tr("Compact")
description: I18n.tr("Use smaller notification cards")
checked: SettingsData.notificationCompactMode
onToggled: checked => SettingsData.set("notificationCompactMode", checked)
}
} }
SettingsCard { SettingsCard {

View File

@@ -270,7 +270,9 @@ FloatingWindow {
root.updateFilteredPlugins(); root.updateFilteredPlugins();
return; return;
} }
thirdPartyConfirmModal.visible = true; thirdPartyConfirmLoader.active = true;
if (thirdPartyConfirmLoader.item)
thirdPartyConfirmLoader.item.show();
} }
} }
@@ -668,119 +670,132 @@ FloatingWindow {
} }
} }
FloatingWindow { LazyLoader {
id: thirdPartyConfirmModal id: thirdPartyConfirmLoader
active: false
objectName: "thirdPartyConfirm" FloatingWindow {
title: I18n.tr("Third-Party Plugin Warning") id: thirdPartyConfirmModal
implicitWidth: 500
implicitHeight: 350
color: Theme.surfaceContainer
visible: false
FocusScope { function show() {
anchors.fill: parent visible = true;
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
thirdPartyConfirmModal.visible = false;
event.accepted = true;
}
} }
Column { function hide() {
visible = false;
}
objectName: "thirdPartyConfirm"
title: I18n.tr("Third-Party Plugin Warning")
implicitWidth: 500
implicitHeight: 350
color: Theme.surfaceContainer
visible: false
FocusScope {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL focus: true
spacing: Theme.spacingL
Row { Keys.onPressed: event => {
width: parent.width if (event.key === Qt.Key_Escape) {
spacing: Theme.spacingM thirdPartyConfirmModal.hide();
event.accepted = true;
DankIcon {
name: "warning"
size: Theme.iconSize
color: Theme.warning
anchors.verticalCenter: parent.verticalCenter
} }
StyledText {
text: I18n.tr("Third-Party Plugin Warning")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - parent.spacing * 2 - Theme.iconSize - parent.children[1].implicitWidth - closeConfirmBtn.width
height: 1
}
DankActionButton {
id: closeConfirmBtn
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.verticalCenter: parent.verticalCenter
onClicked: thirdPartyConfirmModal.visible = false
}
}
StyledText {
width: parent.width
text: I18n.tr("Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\n\nThese plugins may pose security and privacy risks - install at your own risk.")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
wrapMode: Text.WordWrap
} }
Column { Column {
width: parent.width anchors.fill: parent
spacing: Theme.spacingS anchors.margins: Theme.spacingL
spacing: Theme.spacingL
StyledText { Row {
text: I18n.tr("• Plugins may contain bugs or security issues") width: parent.width
font.pixelSize: Theme.fontSizeSmall spacing: Theme.spacingM
color: Theme.surfaceVariantText
DankIcon {
name: "warning"
size: Theme.iconSize
color: Theme.warning
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Third-Party Plugin Warning")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - parent.spacing * 2 - Theme.iconSize - parent.children[1].implicitWidth - closeConfirmBtn.width
height: 1
}
DankActionButton {
id: closeConfirmBtn
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.verticalCenter: parent.verticalCenter
onClicked: thirdPartyConfirmModal.hide()
}
} }
StyledText { StyledText {
text: I18n.tr("• Review code before installation when possible") width: parent.width
font.pixelSize: Theme.fontSizeSmall text: I18n.tr("Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\n\nThese plugins may pose security and privacy risks - install at your own risk.")
color: Theme.surfaceVariantText font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
wrapMode: Text.WordWrap
} }
StyledText { Column {
text: I18n.tr("• Install only from trusted sources") width: parent.width
font.pixelSize: Theme.fontSizeSmall spacing: Theme.spacingS
color: Theme.surfaceVariantText
}
}
Item { StyledText {
width: parent.width text: I18n.tr("• Plugins may contain bugs or security issues")
height: parent.height - parent.spacing * 3 - y font.pixelSize: Theme.fontSizeSmall
} color: Theme.surfaceVariantText
}
Row { StyledText {
anchors.right: parent.right text: I18n.tr("• Review code before installation when possible")
spacing: Theme.spacingM font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankButton { StyledText {
text: I18n.tr("Cancel") text: I18n.tr("• Install only from trusted sources")
iconName: "close" font.pixelSize: Theme.fontSizeSmall
onClicked: thirdPartyConfirmModal.visible = false color: Theme.surfaceVariantText
}
} }
DankButton { Item {
text: I18n.tr("I Understand") width: parent.width
iconName: "check" height: parent.height - parent.spacing * 3 - y
onClicked: { }
SessionData.setShowThirdPartyPlugins(true);
root.updateFilteredPlugins(); Row {
thirdPartyConfirmModal.visible = false; anchors.right: parent.right
spacing: Theme.spacingM
DankButton {
text: I18n.tr("Cancel")
iconName: "close"
onClicked: thirdPartyConfirmModal.hide()
}
DankButton {
text: I18n.tr("I Understand")
iconName: "check"
onClicked: {
SessionData.setShowThirdPartyPlugins(true);
root.updateFilteredPlugins();
thirdPartyConfirmModal.hide();
}
} }
} }
} }

View File

@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -209,7 +210,7 @@ FocusScope {
iconName: "store" iconName: "store"
enabled: DMSService.dmsAvailable enabled: DMSService.dmsAvailable
onClicked: { onClicked: {
pluginBrowser.show(); showPluginBrowser();
} }
} }
@@ -382,9 +383,11 @@ FocusScope {
Connections { Connections {
target: DMSService target: DMSService
function onPluginsListReceived(plugins) { function onPluginsListReceived(plugins) {
pluginBrowser.isLoading = false; if (!pluginBrowserLoader.item)
pluginBrowser.allPlugins = plugins; return;
pluginBrowser.updateFilteredPlugins(); pluginBrowserLoader.item.isLoading = false;
pluginBrowserLoader.item.allPlugins = plugins;
pluginBrowserLoader.item.updateFilteredPlugins();
} }
function onInstalledPluginsReceived(plugins) { function onInstalledPluginsReceived(plugins) {
var pluginMap = {}; var pluginMap = {};
@@ -410,22 +413,36 @@ FocusScope {
} }
Component.onCompleted: { Component.onCompleted: {
pluginBrowser.parentModal = pluginsTab.parentModal;
if (DMSService.dmsAvailable && DMSService.apiVersion >= 8) if (DMSService.dmsAvailable && DMSService.apiVersion >= 8)
DMSService.listInstalled(); DMSService.listInstalled();
if (PopoutService.pendingPluginInstall) if (PopoutService.pendingPluginInstall)
Qt.callLater(() => pluginBrowser.show()); Qt.callLater(showPluginBrowser);
} }
Connections { Connections {
target: PopoutService target: PopoutService
function onPendingPluginInstallChanged() { function onPendingPluginInstallChanged() {
if (PopoutService.pendingPluginInstall) if (PopoutService.pendingPluginInstall)
pluginBrowser.show(); showPluginBrowser();
} }
} }
PluginBrowser { LazyLoader {
id: pluginBrowser id: pluginBrowserLoader
active: false
PluginBrowser {
id: pluginBrowserItem
Component.onCompleted: {
pluginBrowserItem.parentModal = pluginsTab.parentModal;
}
}
}
function showPluginBrowser() {
pluginBrowserLoader.active = true;
if (pluginBrowserLoader.item)
pluginBrowserLoader.item.show();
} }
} }

View File

@@ -32,6 +32,159 @@ Item {
onToggled: checked => SettingsData.set("runningAppsCurrentWorkspace", checked) onToggled: checked => SettingsData.set("runningAppsCurrentWorkspace", checked)
} }
} }
SettingsCard {
width: parent.width
iconName: "find_replace"
title: I18n.tr("App ID Substitutions")
settingKey: "appIdSubstitutions"
tags: ["app", "icon", "substitution", "replacement", "pattern", "window", "class", "regex"]
headerActions: [
DankActionButton {
buttonSize: 36
iconName: "restart_alt"
iconSize: 20
visible: JSON.stringify(SettingsData.appIdSubstitutions) !== JSON.stringify(SettingsData.getDefaultAppIdSubstitutions())
backgroundColor: Theme.surfaceContainer
iconColor: Theme.surfaceVariantText
onClicked: SettingsData.resetAppIdSubstitutions()
},
DankActionButton {
buttonSize: 36
iconName: "add"
iconSize: 20
backgroundColor: Theme.surfaceContainer
iconColor: Theme.primary
onClicked: SettingsData.addAppIdSubstitution("", "", "exact")
}
]
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Map window class names to icon names for proper icon display")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
bottomPadding: Theme.spacingS
}
Repeater {
model: SettingsData.appIdSubstitutions
delegate: Rectangle {
id: subItem
width: parent.width
height: subColumn.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
Column {
id: subColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: (parent.width - deleteBtn.width - Theme.spacingS) / 2
spacing: 2
StyledText {
text: I18n.tr("Pattern")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankTextField {
id: patternField
width: parent.width
text: modelData.pattern
font.pixelSize: Theme.fontSizeSmall
onEditingFinished: SettingsData.updateAppIdSubstitution(index, text, replacementField.text, modelData.type)
}
}
Column {
width: (parent.width - deleteBtn.width - Theme.spacingS) / 2
spacing: 2
StyledText {
text: I18n.tr("Replacement")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankTextField {
id: replacementField
width: parent.width
text: modelData.replacement
font.pixelSize: Theme.fontSizeSmall
onEditingFinished: SettingsData.updateAppIdSubstitution(index, patternField.text, text, modelData.type)
}
}
Item {
id: deleteBtn
width: 32
height: 40
anchors.verticalCenter: parent.verticalCenter
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: deleteArea.containsMouse ? Theme.withAlpha(Theme.error, 0.2) : "transparent"
}
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SettingsData.removeAppIdSubstitution(index)
}
}
}
Column {
width: 120
spacing: 2
StyledText {
text: I18n.tr("Type")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
compactMode: true
dropdownWidth: 120
currentValue: modelData.type
options: ["exact", "contains", "regex"]
onValueChanged: value => SettingsData.updateAppIdSubstitution(index, modelData.pattern, modelData.replacement, value)
}
}
}
}
}
}
}
} }
} }
} }

View File

@@ -131,7 +131,7 @@ Item {
if (DMSService.dmsAvailable) if (DMSService.dmsAvailable)
DMSService.listInstalledThemes(); DMSService.listInstalledThemes();
if (PopoutService.pendingThemeInstall) if (PopoutService.pendingThemeInstall)
Qt.callLater(() => themeBrowser.show()); Qt.callLater(() => showThemeBrowser());
templateCheckProcess.running = true; templateCheckProcess.running = true;
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl) if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
checkCursorIncludeStatus(); checkCursorIncludeStatus();
@@ -169,7 +169,7 @@ Item {
target: PopoutService target: PopoutService
function onPendingThemeInstallChanged() { function onPendingThemeInstallChanged() {
if (PopoutService.pendingThemeInstall) if (PopoutService.pendingThemeInstall)
themeBrowser.show(); showThemeBrowser();
} }
} }
@@ -307,7 +307,7 @@ Item {
Item { Item {
width: parent.width width: parent.width
height: genericColorGrid.implicitHeight height: genericColorGrid.implicitHeight + Math.ceil(genericColorGrid.dotSize * 0.05)
visible: Theme.currentThemeCategory === "generic" && Theme.currentTheme !== Theme.dynamic && Theme.currentThemeName !== "custom" visible: Theme.currentThemeCategory === "generic" && Theme.currentTheme !== Theme.dynamic && Theme.currentThemeName !== "custom"
Grid { Grid {
@@ -939,7 +939,7 @@ Item {
text: I18n.tr("Browse Themes", "browse themes button") text: I18n.tr("Browse Themes", "browse themes button")
iconName: "store" iconName: "store"
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
onClicked: themeBrowser.show() onClicked: showThemeBrowser()
} }
} }
} }
@@ -2041,7 +2041,18 @@ Item {
} }
} }
ThemeBrowser { LazyLoader {
id: themeBrowser id: themeBrowserLoader
active: false
ThemeBrowser {
id: themeBrowserItem
}
}
function showThemeBrowser() {
themeBrowserLoader.active = true;
if (themeBrowserLoader.item)
themeBrowserLoader.item.show();
} }
} }

View File

@@ -139,7 +139,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: mainWallpaperBrowser.open() onClicked: root.openMainWallpaperBrowser()
} }
} }
@@ -476,7 +476,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: lightWallpaperBrowser.open() onClicked: root.openLightWallpaperBrowser()
} }
} }
@@ -660,7 +660,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: darkWallpaperBrowser.open() onClicked: root.openDarkWallpaperBrowser()
} }
} }
@@ -1242,53 +1242,83 @@ Item {
} }
} }
FileBrowserModal { function openMainWallpaperBrowser() {
id: mainWallpaperBrowser mainWallpaperBrowserLoader.active = true;
parentModal: root.parentModal if (mainWallpaperBrowserLoader.item)
browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title") mainWallpaperBrowserLoader.item.open();
browserIcon: "wallpaper" }
browserType: "wallpaper"
showHiddenFiles: true function openLightWallpaperBrowser() {
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] lightWallpaperBrowserLoader.active = true;
onFileSelected: path => { if (lightWallpaperBrowserLoader.item)
if (SessionData.perMonitorWallpaper) { lightWallpaperBrowserLoader.item.open();
SessionData.setMonitorWallpaper(selectedMonitorName, path); }
} else {
SessionData.setWallpaper(path); function openDarkWallpaperBrowser() {
darkWallpaperBrowserLoader.active = true;
if (darkWallpaperBrowserLoader.item)
darkWallpaperBrowserLoader.item.open();
}
LazyLoader {
id: mainWallpaperBrowserLoader
active: false
FileBrowserModal {
parentModal: root.parentModal
browserTitle: I18n.tr("Select Wallpaper", "wallpaper file browser title")
browserIcon: "wallpaper"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
if (SessionData.perMonitorWallpaper) {
SessionData.setMonitorWallpaper(selectedMonitorName, path);
} else {
SessionData.setWallpaper(path);
}
close();
} }
close();
} }
} }
FileBrowserModal { LazyLoader {
id: lightWallpaperBrowser id: lightWallpaperBrowserLoader
parentModal: root.parentModal active: false
browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title")
browserIcon: "light_mode" FileBrowserModal {
browserType: "wallpaper" parentModal: root.parentModal
showHiddenFiles: true browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title")
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] browserIcon: "light_mode"
onFileSelected: path => { browserType: "wallpaper"
SessionData.wallpaperPathLight = path; showHiddenFiles: true
SessionData.syncWallpaperForCurrentMode(); fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
SessionData.saveSettings(); onFileSelected: path => {
close(); SessionData.wallpaperPathLight = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
}
} }
} }
FileBrowserModal { LazyLoader {
id: darkWallpaperBrowser id: darkWallpaperBrowserLoader
parentModal: root.parentModal active: false
browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title")
browserIcon: "dark_mode" FileBrowserModal {
browserType: "wallpaper" parentModal: root.parentModal
showHiddenFiles: true browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title")
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] browserIcon: "dark_mode"
onFileSelected: path => { browserType: "wallpaper"
SessionData.wallpaperPathDark = path; showHiddenFiles: true
SessionData.syncWallpaperForCurrentMode(); fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
SessionData.saveSettings(); onFileSelected: path => {
close(); SessionData.wallpaperPathDark = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
}
} }
} }
} }

View File

@@ -19,6 +19,7 @@ StyledRect {
property string iconName: "" property string iconName: ""
property bool collapsible: false property bool collapsible: false
property bool expanded: true property bool expanded: true
property real headerLeftPadding: 0
default property alias content: contentColumn.children default property alias content: contentColumn.children
property alias headerActions: headerActionsRow.children property alias headerActions: headerActionsRow.children
@@ -115,6 +116,7 @@ StyledRect {
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: root.headerLeftPadding
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -142,7 +144,7 @@ StyledRect {
Row { Row {
id: headerActionsRow id: headerActionsRow
anchors.right: caretIcon.left anchors.right: root.collapsible ? caretIcon.left : parent.right
anchors.rightMargin: root.collapsible ? Theme.spacingS : 0 anchors.rightMargin: root.collapsible ? Theme.spacingS : 0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -172,6 +174,7 @@ StyledRect {
} }
MouseArea { MouseArea {
visible: root.collapsible
anchors.left: caretIcon.left anchors.left: caretIcon.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top

View File

@@ -51,6 +51,7 @@ StyledRect {
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
width: parent.width
DankIcon { DankIcon {
id: headerIcon id: headerIcon
@@ -69,6 +70,8 @@ StyledRect {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: root.title !== "" visible: root.title !== ""
width: parent.width - (headerIcon.visible ? headerIcon.width + parent.spacing : 0)
horizontalAlignment: Text.AlignLeft
} }
} }

View File

@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -382,6 +383,7 @@ Item {
widgetObj.showMicPercent = SettingsData.controlCenterShowMicPercent; widgetObj.showMicPercent = SettingsData.controlCenterShowMicPercent;
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon; widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon; widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon;
} }
if (widgetId === "diskUsage") if (widgetId === "diskUsage")
widgetObj.mountPath = "/"; widgetObj.mountPath = "/";
@@ -400,6 +402,24 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function cloneWidgetData(widget) {
if (typeof widget === "string")
return {
"id": widget,
"enabled": true
};
var result = {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
}
return result;
}
function handleItemEnabledChanged(sectionId, itemId, enabled) { function handleItemEnabledChanged(sectionId, itemId, enabled) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
@@ -407,42 +427,8 @@ Item {
var widgetId = typeof widget === "string" ? widget : widget.id; var widgetId = typeof widget === "string" ? widget : widget.id;
if (widgetId !== itemId) if (widgetId !== itemId)
continue; continue;
var newWidget = cloneWidgetData(widget);
if (typeof widget === "string") { newWidget.enabled = enabled;
widgets[i] = {
"id": widget,
"enabled": enabled
};
break;
}
var newWidget = {
"id": widget.id,
"enabled": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
else if (widget.id === "gpuTemp")
newWidget.selectedGpuIndex = 0;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
else if (widget.id === "gpuTemp")
newWidget.pciId = "";
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[i] = newWidget; widgets[i] = newWidget;
break; break;
} }
@@ -455,128 +441,36 @@ Item {
function handleSpacerSizeChanged(sectionId, widgetIndex, newSize) { function handleSpacerSizeChanged(sectionId, widgetIndex, newSize) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) { if (widgetIndex < 0 || widgetIndex >= widgets.length)
setWidgetsForSection(sectionId, widgets);
return; return;
}
var widget = widgets[widgetIndex]; var widget = widgets[widgetIndex];
var widgetId = typeof widget === "string" ? widget : widget.id; var widgetId = typeof widget === "string" ? widget : widget.id;
if (widgetId !== "spacer") { if (widgetId !== "spacer")
setWidgetsForSection(sectionId, widgets);
return; return;
} var newWidget = cloneWidgetData(widget);
newWidget.size = newSize;
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"size": newSize
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"size": newSize
};
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleGpuSelectionChanged(sectionId, widgetIndex, selectedGpuIndex) { function handleGpuSelectionChanged(sectionId, widgetIndex, selectedGpuIndex) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) { if (widgetIndex < 0 || widgetIndex >= widgets.length)
setWidgetsForSection(sectionId, widgets);
return; return;
}
var pciId = DgopService.availableGpus && DgopService.availableGpus.length > selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : ""; var pciId = DgopService.availableGpus && DgopService.availableGpus.length > selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : "";
var widget = widgets[widgetIndex]; var newWidget = cloneWidgetData(widgets[widgetIndex]);
if (typeof widget === "string") { newWidget.selectedGpuIndex = selectedGpuIndex;
widgets[widgetIndex] = { newWidget.pciId = pciId;
"id": widget,
"enabled": true,
"selectedGpuIndex": selectedGpuIndex,
"pciId": pciId
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"selectedGpuIndex": selectedGpuIndex,
"pciId": pciId
};
if (widget.size !== undefined)
newWidget.size = widget.size;
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleDiskMountSelectionChanged(sectionId, widgetIndex, mountPath) { function handleDiskMountSelectionChanged(sectionId, widgetIndex, mountPath) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) { if (widgetIndex < 0 || widgetIndex >= widgets.length)
setWidgetsForSection(sectionId, widgets);
return; return;
} var newWidget = cloneWidgetData(widgets[widgetIndex]);
newWidget.mountPath = mountPath;
var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"mountPath": mountPath
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"mountPath": mountPath
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -585,32 +479,8 @@ Item {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) if (widgetIndex < 0 || widgetIndex >= widgets.length)
return; return;
var newWidget = cloneWidgetData(widgets[widgetIndex]);
var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widget = {
"id": widget,
"enabled": true
};
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled !== undefined ? widget.enabled : true,
"showNetworkIcon": widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon,
"showBluetoothIcon": widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon,
"showAudioIcon": widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon,
"showAudioPercent": widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent,
"showVpnIcon": widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon,
"showBrightnessIcon": widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon,
"showBrightnessPercent": widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent,
"showMicIcon": widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon,
"showMicPercent": widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent,
"showBatteryIcon": widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon,
"showPrinterIcon": widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon
};
newWidget[settingName] = value; newWidget[settingName] = value;
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -635,46 +505,8 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
return; return;
} }
var newWidget = cloneWidgetData(widgets[widgetIndex]);
var widget = widgets[widgetIndex]; newWidget.minimumWidth = enabled;
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"minimumWidth": enabled
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"minimumWidth": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.showSwap !== undefined)
newWidget.showSwap = widget.showSwap;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -685,141 +517,41 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
return; return;
} }
var newWidget = cloneWidgetData(widgets[widgetIndex]);
var widget = widgets[widgetIndex]; newWidget.showSwap = enabled;
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"showSwap": enabled
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"showSwap": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.minimumWidth !== undefined)
newWidget.minimumWidth = widget.minimumWidth;
if (widget.mediaSize !== undefined)
newWidget.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined)
newWidget.clockCompactMode = widget.clockCompactMode;
if (widget.focusedWindowCompactMode !== undefined)
newWidget.focusedWindowCompactMode = widget.focusedWindowCompactMode;
if (widget.runningAppsCompactMode !== undefined)
newWidget.runningAppsCompactMode = widget.runningAppsCompactMode;
if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleCompactModeChanged(sectionId, widgetId, value) { function handleCompactModeChanged(sectionId, widgetId, value) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i]; var widget = widgets[i];
var currentId = typeof widget === "string" ? widget : widget.id; var currentId = typeof widget === "string" ? widget : widget.id;
if (currentId !== widgetId) if (currentId !== widgetId)
continue; continue;
if (typeof widget === "string") { var newWidget = cloneWidgetData(widget);
widgets[i] = {
"id": widget,
"enabled": true
};
widget = widgets[i];
} else {
var newWidget = {
"id": widget.id,
"enabled": widget.enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.minimumWidth !== undefined)
newWidget.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined)
newWidget.showSwap = widget.showSwap;
if (widget.mediaSize !== undefined)
newWidget.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined)
newWidget.clockCompactMode = widget.clockCompactMode;
if (widget.focusedWindowCompactMode !== undefined)
newWidget.focusedWindowCompactMode = widget.focusedWindowCompactMode;
if (widget.runningAppsCompactMode !== undefined)
newWidget.runningAppsCompactMode = widget.runningAppsCompactMode;
if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[i] = newWidget;
widget = newWidget;
}
switch (widgetId) { switch (widgetId) {
case "music": case "music":
widget.mediaSize = value; newWidget.mediaSize = value;
break; break;
case "clock": case "clock":
widget.clockCompactMode = value; newWidget.clockCompactMode = value;
break; break;
case "focusedWindow": case "focusedWindow":
widget.focusedWindowCompactMode = value; newWidget.focusedWindowCompactMode = value;
break; break;
case "runningApps": case "runningApps":
widget.runningAppsCompactMode = value; newWidget.runningAppsCompactMode = value;
break; break;
case "keyboard_layout_name": case "keyboard_layout_name":
widget.keyboardLayoutNameCompactMode = value; newWidget.keyboardLayoutNameCompactMode = value;
break; break;
} }
widgets[i] = newWidget;
break; break;
} }
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -866,6 +598,8 @@ Item {
item.showBatteryIcon = widget.showBatteryIcon; item.showBatteryIcon = widget.showBatteryIcon;
if (widget.showPrinterIcon !== undefined) if (widget.showPrinterIcon !== undefined)
item.showPrinterIcon = widget.showPrinterIcon; item.showPrinterIcon = widget.showPrinterIcon;
if (widget.showScreenSharingIcon !== undefined)
item.showScreenSharingIcon = widget.showScreenSharingIcon;
if (widget.minimumWidth !== undefined) if (widget.minimumWidth !== undefined)
item.minimumWidth = widget.minimumWidth; item.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined) if (widget.showSwap !== undefined)
@@ -916,14 +650,28 @@ Item {
}); });
} }
WidgetSelectionPopup { LazyLoader {
id: widgetSelectionPopup id: widgetSelectionPopupLoader
parentModal: widgetsTab.parentModal active: false
onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId, targetSection); WidgetSelectionPopup {
id: widgetSelectionPopupItem
parentModal: widgetsTab.parentModal
onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId, targetSection);
}
} }
} }
function showWidgetSelectionPopup(sectionId) {
widgetSelectionPopupLoader.active = true;
if (!widgetSelectionPopupLoader.item)
return;
widgetSelectionPopupLoader.item.targetSection = sectionId;
widgetSelectionPopupLoader.item.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopupLoader.item.show();
}
DankFlickable { DankFlickable {
anchors.fill: parent anchors.fill: parent
clip: true clip: true
@@ -1113,9 +861,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder); widgetsTab.handleItemOrderChanged(sectionId, newOrder);
} }
onAddWidget: sectionId => { onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId; showWidgetSelectionPopup(sectionId);
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
} }
onRemoveWidget: (sectionId, index) => { onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index); widgetsTab.removeWidgetFromSection(sectionId, index);
@@ -1170,9 +916,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder); widgetsTab.handleItemOrderChanged(sectionId, newOrder);
} }
onAddWidget: sectionId => { onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId; showWidgetSelectionPopup(sectionId);
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
} }
onRemoveWidget: (sectionId, index) => { onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index); widgetsTab.removeWidgetFromSection(sectionId, index);
@@ -1227,9 +971,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder); widgetsTab.handleItemOrderChanged(sectionId, newOrder);
} }
onAddWidget: sectionId => { onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId; showWidgetSelectionPopup(sectionId);
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
} }
onRemoveWidget: (sectionId, index) => { onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index); widgetsTab.removeWidgetFromSection(sectionId, index);

View File

@@ -31,6 +31,19 @@ Column {
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled) signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled) signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
function cloneWidgetData(widget) {
var result = {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
}
return result;
}
width: parent.width width: parent.width
height: implicitHeight height: implicitHeight
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -727,13 +740,7 @@ Column {
var newItems = root.items.slice(); var newItems = root.items.slice();
var draggedItem = newItems.splice(index, 1)[0]; var draggedItem = newItems.splice(index, 1)[0];
newItems.splice(newIndex, 0, draggedItem); newItems.splice(newIndex, 0, draggedItem);
root.itemOrderChanged(newItems.map(item => { root.itemOrderChanged(newItems.map(item => root.cloneWidgetData(item)));
return ({
"id": item.id,
"enabled": item.enabled,
"size": item.size
});
}));
} }
} }
delegateItem.x = 0; delegateItem.x = 0;
@@ -875,6 +882,11 @@ Column {
icon: "print", icon: "print",
label: I18n.tr("Printer"), label: I18n.tr("Printer"),
setting: "showPrinterIcon" setting: "showPrinterIcon"
},
{
icon: "screen_record",
label: I18n.tr("Screen Sharing"),
setting: "showScreenSharingIcon"
} }
] ]
@@ -907,6 +919,8 @@ Column {
return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon; return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
case "showPrinterIcon": case "showPrinterIcon":
return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon; return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
case "showScreenSharingIcon":
return wd?.showScreenSharingIcon ?? SettingsData.controlCenterShowScreenSharingIcon;
default: default:
return false; return false;
} }

View File

@@ -63,15 +63,15 @@ Item {
onToggled: checked => SettingsData.set("showWorkspaceApps", checked) onToggled: checked => SettingsData.set("showWorkspaceApps", checked)
} }
Row { Item {
width: parent.width - Theme.spacingL width: parent.width
spacing: Theme.spacingL height: maxAppsColumn.height
visible: SettingsData.showWorkspaceApps visible: SettingsData.showWorkspaceApps
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
anchors.left: parent.left
anchors.leftMargin: Theme.spacingL
Column { Column {
id: maxAppsColumn
x: Theme.spacingL
width: 120 width: 120
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -80,14 +80,15 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
horizontalAlignment: Text.AlignLeft
} }
DankTextField { DankTextField {
width: 100 width: 100
height: 28 height: 28
placeholderText: "#ffffff" placeholderText: "3"
text: SettingsData.maxWorkspaceIcons text: SettingsData.maxWorkspaceIcons
maximumLength: 7 maximumLength: 2
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
topPadding: Theme.spacingXS topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS bottomPadding: Theme.spacingXS
@@ -114,12 +115,13 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "workspacesPerMonitor" settingKey: "workspaceFollowFocus"
tags: ["workspace", "per-monitor", "multi-monitor"] tags: ["workspace", "focus", "follow", "monitor"]
text: I18n.tr("Per-Monitor Workspaces") text: I18n.tr("Follow Monitor Focus")
description: I18n.tr("Show only workspaces belonging to each specific monitor.") description: I18n.tr("Show workspaces of the currently focused monitor")
checked: SettingsData.workspacesPerMonitor checked: SettingsData.workspaceFollowFocus
onToggled: checked => SettingsData.set("workspacesPerMonitor", checked) visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
onToggled: checked => SettingsData.set("workspaceFollowFocus", checked)
} }
SettingsToggleRow { SettingsToggleRow {
@@ -153,6 +155,189 @@ Item {
} }
} }
SettingsCard {
width: parent.width
iconName: "palette"
title: I18n.tr("Workspace Appearance")
settingKey: "workspaceAppearance"
SettingsButtonGroupRow {
text: I18n.tr("Focused Color")
model: ["pri", "s", "sc", "sch", "none"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
case "none":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch", "none"];
SettingsData.set("workspaceColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsButtonGroupRow {
text: I18n.tr("Unfocused Color")
model: ["def", "s", "sc", "sch"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch"];
SettingsData.set("workspaceUnfocusedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
}
SettingsButtonGroupRow {
text: I18n.tr("Urgent Color")
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
model: ["err", "pri", "sec", "s", "sc"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return 1;
case "secondary":
return 2;
case "s":
return 3;
case "sc":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "primary", "secondary", "s", "sc"];
SettingsData.set("workspaceUrgentColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsToggleRow {
settingKey: "workspaceFocusedBorderEnabled"
tags: ["workspace", "border", "outline", "focused", "ring"]
text: I18n.tr("Focused Border")
description: I18n.tr("Show an outline ring around the focused workspace indicator")
checked: SettingsData.workspaceFocusedBorderEnabled
onToggled: checked => SettingsData.set("workspaceFocusedBorderEnabled", checked)
}
Column {
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.workspaceFocusedBorderEnabled
leftPadding: Theme.spacingM
SettingsButtonGroupRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Border Color")
model: [I18n.tr("Surface"), I18n.tr("Secondary"), I18n.tr("Primary")]
currentIndex: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return 0;
case "secondary":
return 1;
case "primary":
return 2;
default:
return 2;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let newColor = "primary";
switch (index) {
case 0:
newColor = "surfaceText";
break;
case 1:
newColor = "secondary";
break;
case 2:
newColor = "primary";
break;
}
SettingsData.set("workspaceFocusedBorderColor", newColor);
}
}
SettingsSliderRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Thickness")
value: SettingsData.workspaceFocusedBorderThickness
minimum: 1
maximum: 6
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("workspaceFocusedBorderThickness", newValue)
}
}
}
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "label" iconName: "label"

View File

@@ -14,7 +14,7 @@ Scope {
property bool isClosing: false property bool isClosing: false
property bool releaseKeyboard: false property bool releaseKeyboard: false
readonly property bool spotlightModalOpen: PopoutService.spotlightModal?.spotlightOpen ?? false readonly property bool spotlightModalOpen: PopoutService.spotlightModal?.spotlightOpen ?? false
property bool overlayActive: (NiriService.inOverview && !spotlightModalOpen) || searchActive property bool overlayActive: NiriService.inOverview || searchActive
function showSpotlight(screenName) { function showSpotlight(screenName) {
isClosing = false; isClosing = false;

View File

@@ -33,8 +33,8 @@ Item {
property var iconToWindowRatio: 0.25 property var iconToWindowRatio: 0.25
property var iconToWindowRatioCompact: 0.45 property var iconToWindowRatioCompact: 0.45
property var entry: DesktopEntries.heuristicLookup(windowData?.class) property var entry: DesktopEntries.heuristicLookup(Paths.moddedAppId(windowData?.class ?? ""))
property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing") property var iconPath: Paths.getAppIcon(windowData?.class ?? "", entry) || Quickshell.iconPath("application-x-executable", "image-missing")
property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth property bool compactMode: Theme.fontSizeSmall * 4 > targetWindowHeight || Theme.fontSizeSmall * 4 > targetWindowWidth
x: initX x: initX

View File

@@ -510,6 +510,16 @@ Singleton {
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream) objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
} }
Connections {
target: Pipewire
function onDefaultAudioSinkChanged() {
if (soundsAvailable) {
Qt.callLater(root.destroySoundPlayers);
Qt.callLater(root.createSoundPlayers);
}
}
}
function setVolume(percentage) { function setVolume(percentage) {
if (!root.sink?.audio) { if (!root.sink?.audio) {
return "No audio sink available"; return "No audio sink available";

View File

@@ -11,7 +11,7 @@ Singleton {
id: root id: root
readonly property string currentVersion: "1.2" readonly property string currentVersion: "1.2"
readonly property bool changelogEnabled: false readonly property bool changelogEnabled: true
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell" readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion
@@ -37,7 +37,27 @@ Singleton {
Component.onCompleted: { Component.onCompleted: {
if (!changelogEnabled) if (!changelogEnabled)
return; return;
changelogCheckProcess.running = true; if (FirstLaunchService.checkComplete)
handleFirstLaunchResult();
}
function handleFirstLaunchResult() {
if (FirstLaunchService.isFirstLaunch) {
checkComplete = true;
changelogDismissed = true;
touchMarkerProcess.running = true;
} else {
changelogCheckProcess.running = true;
}
}
Connections {
target: FirstLaunchService
function onCheckCompleteChanged() {
if (FirstLaunchService.checkComplete && root.changelogEnabled && !root.checkComplete)
root.handleFirstLaunchResult();
}
} }
function showChangelog() { function showChangelog() {
@@ -66,9 +86,7 @@ Singleton {
root.changelogDismissed = true; root.changelogDismissed = true;
break; break;
case "show": case "show":
if (typeof FirstLaunchService === "undefined" || !FirstLaunchService.isFirstLaunch) { root.changelogRequested();
root.changelogRequested();
}
break; break;
} }
} }

View File

@@ -316,15 +316,16 @@ Singleton {
const totalKB = mem.total || 0; const totalKB = mem.total || 0;
const availableKB = mem.available || 0; const availableKB = mem.available || 0;
const freeKB = mem.free || 0; const freeKB = mem.free || 0;
const usedKB = mem.used !== undefined ? mem.used : (totalKB - availableKB);
totalMemoryMB = totalKB / 1024; totalMemoryMB = totalKB / 1024;
availableMemoryMB = availableKB / 1024; availableMemoryMB = availableKB / 1024;
freeMemoryMB = freeKB / 1024; freeMemoryMB = freeKB / 1024;
usedMemoryMB = totalMemoryMB - availableMemoryMB; usedMemoryMB = usedKB / 1024;
memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0; memoryUsage = mem.usedPercent !== undefined ? mem.usedPercent : (totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0);
totalMemoryKB = totalKB; totalMemoryKB = totalKB;
usedMemoryKB = totalKB - availableKB; usedMemoryKB = usedKB;
totalSwapKB = mem.swaptotal || 0; totalSwapKB = mem.swaptotal || 0;
usedSwapKB = (mem.swaptotal || 0) - (mem.swapfree || 0); usedSwapKB = (mem.swaptotal || 0) - (mem.swapfree || 0);

View File

@@ -74,12 +74,10 @@ Singleton {
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
const result = data.trim(); const result = data.trim();
root.checkComplete = true;
if (result === "first") { if (result === "first") {
root.isFirstLaunch = true; root.isFirstLaunch = true;
console.info("FirstLaunchService: First launch detected, greeter will be shown"); console.info("FirstLaunchService: First launch detected, greeter will be shown");
root.greeterRequested();
} else if (result === "existing_user") { } else if (result === "existing_user") {
root.isFirstLaunch = false; root.isFirstLaunch = false;
console.info("FirstLaunchService: Existing user detected, silently creating marker"); console.info("FirstLaunchService: Existing user detected, silently creating marker");
@@ -87,6 +85,11 @@ Singleton {
} else { } else {
root.isFirstLaunch = false; root.isFirstLaunch = false;
} }
root.checkComplete = true;
if (root.isFirstLaunch)
root.greeterRequested();
} }
} }
} }

View File

@@ -1,5 +1,5 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtCore import QtCore
import QtQuick import QtQuick
@@ -501,8 +501,12 @@ Singleton {
return Actions.buildSpawnAction(command, args); return Actions.buildSpawnAction(command, args);
} }
function buildShellAction(shellCmd) { function buildShellAction(shellCmd, shell) {
return Actions.buildShellAction(currentProvider, shellCmd); return Actions.buildShellAction(currentProvider, shellCmd, shell);
}
function getShellFromAction(action) {
return Actions.getShellFromAction(action);
} }
function parseSpawnCommand(action) { function parseSpawnCommand(action) {

View File

@@ -1,5 +1,5 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtCore import QtCore
import QtQuick import QtQuick
@@ -27,6 +27,10 @@ Singleton {
property bool inOverview: false property bool inOverview: false
property var casts: []
property bool hasCasts: casts.length > 0
property bool hasActiveCast: casts.some(c => c.is_active)
property int currentKeyboardLayoutIndex: 0 property int currentKeyboardLayoutIndex: 0
property var keyboardLayoutNames: [] property var keyboardLayoutNames: []
@@ -356,6 +360,15 @@ Singleton {
case 'ScreenshotCaptured': case 'ScreenshotCaptured':
handleScreenshotCaptured(event.ScreenshotCaptured); handleScreenshotCaptured(event.ScreenshotCaptured);
break; break;
case 'CastsChanged':
handleCastsChanged(event.CastsChanged);
break;
case 'CastStartedOrChanged':
handleCastStartedOrChanged(event.CastStartedOrChanged);
break;
case 'CastStopped':
handleCastStopped(event.CastStopped);
break;
} }
} }
@@ -649,6 +662,28 @@ Singleton {
} }
} }
function handleCastsChanged(data) {
casts = data.casts || [];
}
function handleCastStartedOrChanged(data) {
if (!data.cast)
return;
const cast = data.cast;
const existingIndex = casts.findIndex(c => c.stream_id === cast.stream_id);
if (existingIndex >= 0) {
const updatedCasts = [...casts];
updatedCasts[existingIndex] = cast;
casts = updatedCasts;
} else {
casts = [...casts, cast];
}
}
function handleCastStopped(data) {
casts = casts.filter(c => c.stream_id !== data.stream_id);
}
function updateCurrentOutputWorkspaces() { function updateCurrentOutputWorkspaces() {
if (!currentOutput) { if (!currentOutput) {
currentOutputWorkspaces = allWorkspaces; currentOutputWorkspaces = allWorkspaces;

View File

@@ -32,7 +32,6 @@ Singleton {
property int maxIngressPerSecond: 20 property int maxIngressPerSecond: 20
property double _lastIngressSec: 0 property double _lastIngressSec: 0
property int _ingressCountThisSec: 0 property int _ingressCountThisSec: 0
property int maxStoredNotifications: SettingsData.notificationHistoryMaxCount
property var _dismissQueue: [] property var _dismissQueue: []
property int _dismissBatchSize: 8 property int _dismissBatchSize: 8
@@ -340,30 +339,6 @@ Singleton {
historyFileView.writeAdapter(); historyFileView.writeAdapter();
} }
function _trimStored() {
if (notifications.length > maxStoredNotifications) {
const overflow = notifications.length - maxStoredNotifications;
const toDrop = [];
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
const w = notifications[i];
if (w && w.notification && w.urgency !== NotificationUrgency.Critical) {
toDrop.push(w);
}
}
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
const w = notifications[i];
if (w && w.notification && toDrop.indexOf(w) === -1) {
toDrop.push(w);
}
}
for (const w of toDrop) {
try {
w.notification.dismiss();
} catch (e) {}
}
}
}
function onOverlayOpen() { function onOverlayOpen() {
popupsDisabled = true; popupsDisabled = true;
addGate.stop(); addGate.stop();
@@ -493,7 +468,6 @@ Singleton {
root.allWrappers.push(wrapper); root.allWrappers.push(wrapper);
if (!isTransient) { if (!isTransient) {
root.notifications.push(wrapper); root.notifications.push(wrapper);
_trimStored();
if (_shouldSaveToHistory(notif.urgency)) { if (_shouldSaveToHistory(notif.urgency)) {
root.addToHistory(wrapper); root.addToHistory(wrapper);
} }
@@ -529,10 +503,8 @@ Singleton {
readonly property Timer timer: Timer { readonly property Timer timer: Timer {
interval: { interval: {
if (!wrapper.notification) { if (!wrapper.notification)
return 5000; return 5000;
}
switch (wrapper.notification.urgency) { switch (wrapper.notification.urgency) {
case NotificationUrgency.Low: case NotificationUrgency.Low:
return SettingsData.notificationTimeoutLow; return SettingsData.notificationTimeoutLow;
@@ -601,37 +573,38 @@ Singleton {
} }
required property Notification notification required property Notification notification
readonly property string summary: notification.summary readonly property string summary: notification?.summary ?? ""
readonly property string body: notification.body readonly property string body: notification?.body ?? ""
readonly property string htmlBody: { readonly property string htmlBody: {
if (body && (body.includes('<') && body.includes('>'))) { if (!body)
return "";
if (body.includes('<') && body.includes('>'))
return body; return body;
}
return Markdown2Html.markdownToHtml(body); return Markdown2Html.markdownToHtml(body);
} }
readonly property string appIcon: notification.appIcon readonly property string appIcon: notification?.appIcon ?? ""
readonly property string appName: { readonly property string appName: {
if (!notification)
return "app";
if (notification.appName == "") { if (notification.appName == "") {
const entry = DesktopEntries.heuristicLookup(notification.desktopEntry); const entry = DesktopEntries.heuristicLookup(notification.desktopEntry);
if (entry && entry.name) { if (entry && entry.name)
return entry.name.toLowerCase(); return entry.name.toLowerCase();
}
} }
return notification.appName || "app"; return notification.appName || "app";
} }
readonly property string desktopEntry: notification.desktopEntry readonly property string desktopEntry: notification?.desktopEntry ?? ""
readonly property string image: notification.image readonly property string image: notification?.image ?? ""
readonly property string cleanImage: { readonly property string cleanImage: {
if (!image) { if (!image)
return ""; return "";
}
return Paths.strip(image); return Paths.strip(image);
} }
readonly property int urgency: notification.urgency readonly property int urgency: notification?.urgency ?? 1
readonly property list<NotificationAction> actions: notification.actions readonly property list<NotificationAction> actions: notification?.actions ?? []
readonly property Connections conn: Connections { readonly property Connections conn: Connections {
target: wrapper.notification.Retainable target: wrapper.notification?.Retainable ?? null
function onDropped(): void { function onDropped(): void {
root.allWrappers = root.allWrappers.filter(w => w !== wrapper); root.allWrappers = root.allWrappers.filter(w => w !== wrapper);
@@ -743,6 +716,8 @@ Singleton {
} }
const next = notificationQueue.shift(); const next = notificationQueue.shift();
if (!next)
return;
next.seq = ++seqCounter; next.seq = ++seqCounter;
visibleNotifications = [...visibleNotifications, next]; visibleNotifications = [...visibleNotifications, next];
@@ -805,7 +780,7 @@ Singleton {
const groups = {}; const groups = {};
for (const notif of notifications) { for (const notif of notifications) {
if (!notif) if (!notif || !notif.notification)
continue; continue;
const groupKey = getGroupKey(notif); const groupKey = getGroupKey(notif);
if (!groups[groupKey]) { if (!groups[groupKey]) {
@@ -823,14 +798,15 @@ Singleton {
groups[groupKey].latestNotification = groups[groupKey].notifications[0]; groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length; groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) { if (notif.notification?.hasInlineReply)
groups[groupKey].hasInlineReply = true; groups[groupKey].hasInlineReply = true;
}
} }
return Object.values(groups).sort((a, b) => { return Object.values(groups).sort((a, b) => {
const aUrgency = a.latestNotification.urgency || NotificationUrgency.Low; if (!a.latestNotification || !b.latestNotification)
const bUrgency = b.latestNotification.urgency || NotificationUrgency.Low; return 0;
const aUrgency = a.latestNotification.urgency ?? NotificationUrgency.Low;
const bUrgency = b.latestNotification.urgency ?? NotificationUrgency.Low;
if (aUrgency !== bUrgency) { if (aUrgency !== bUrgency) {
return bUrgency - aUrgency; return bUrgency - aUrgency;
} }
@@ -842,7 +818,7 @@ Singleton {
const groups = {}; const groups = {};
for (const notif of popups) { for (const notif of popups) {
if (!notif) if (!notif || !notif.notification)
continue; continue;
const groupKey = getGroupKey(notif); const groupKey = getGroupKey(notif);
if (!groups[groupKey]) { if (!groups[groupKey]) {
@@ -860,12 +836,13 @@ Singleton {
groups[groupKey].latestNotification = groups[groupKey].notifications[0]; groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length; groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) { if (notif.notification?.hasInlineReply)
groups[groupKey].hasInlineReply = true; groups[groupKey].hasInlineReply = true;
}
} }
return Object.values(groups).sort((a, b) => { return Object.values(groups).sort((a, b) => {
if (!a.latestNotification || !b.latestNotification)
return 0;
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime(); return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
}); });
} }

View File

@@ -287,11 +287,14 @@ Singleton {
return false; return false;
} }
// MODIFICATION: Treat Launchers as persistent instances like Daemons if (isDaemon) {
if (isDaemon || isLauncher) { const newDaemons = Object.assign({}, pluginDaemonComponents);
newDaemons[pluginId] = comp;
pluginDaemonComponents = newDaemons;
} else if (isLauncher) {
const instance = comp.createObject(root, { const instance = comp.createObject(root, {
"pluginId": pluginId, "pluginId": pluginId,
"pluginService": root // Inject PluginService "pluginService": root
}); });
if (!instance) { if (!instance) {
console.error("PluginService: failed to instantiate plugin:", pluginId, comp.errorString()); console.error("PluginService: failed to instantiate plugin:", pluginId, comp.errorString());
@@ -302,15 +305,9 @@ Singleton {
newInstances[pluginId] = instance; newInstances[pluginId] = instance;
pluginInstances = newInstances; pluginInstances = newInstances;
if (isDaemon) { const newLaunchers = Object.assign({}, pluginLauncherComponents);
const newDaemons = Object.assign({}, pluginDaemonComponents); newLaunchers[pluginId] = comp;
newDaemons[pluginId] = comp; pluginLauncherComponents = newLaunchers;
pluginDaemonComponents = newDaemons;
} else {
const newLaunchers = Object.assign({}, pluginLauncherComponents);
newLaunchers[pluginId] = comp;
pluginLauncherComponents = newLaunchers;
}
} else if (isDesktop) { } else if (isDesktop) {
const newDesktop = Object.assign({}, pluginDesktopComponents); const newDesktop = Object.assign({}, pluginDesktopComponents);
newDesktop[pluginId] = comp; newDesktop[pluginId] = comp;

View File

@@ -27,7 +27,9 @@ Singleton {
property var colorPickerModal: null property var colorPickerModal: null
property var notificationModal: null property var notificationModal: null
property var wifiPasswordModal: null property var wifiPasswordModal: null
property var wifiPasswordModalLoader: null
property var polkitAuthModal: null property var polkitAuthModal: null
property var polkitAuthModalLoader: null
property var bluetoothPairingModal: null property var bluetoothPairingModal: null
property var networkInfoModal: null property var networkInfoModal: null
@@ -416,11 +418,17 @@ Singleton {
} }
function showWifiPasswordModal(ssid) { function showWifiPasswordModal(ssid) {
wifiPasswordModal?.show(ssid); if (wifiPasswordModalLoader)
wifiPasswordModalLoader.active = true;
if (wifiPasswordModal)
wifiPasswordModal.show(ssid);
} }
function showHiddenNetworkModal() { function showHiddenNetworkModal() {
wifiPasswordModal?.showHidden(); if (wifiPasswordModalLoader)
wifiPasswordModalLoader.active = true;
if (wifiPasswordModal)
wifiPasswordModal.showHidden();
} }
function hideWifiPasswordModal() { function hideWifiPasswordModal() {

View File

@@ -96,7 +96,7 @@ Singleton {
"paru": archBasedPMSettings, "paru": archBasedPMSettings,
"dnf": fedoraBasedPMSettings "dnf": fedoraBasedPMSettings
} }
readonly property list<string> supportedDistributions: ["arch", "cachyos", "manjaro", "endeavouros", "fedora"] readonly property list<string> supportedDistributions: ["arch", "artix", "cachyos", "manjaro", "endeavouros", "fedora"]
readonly property int updateCount: availableUpdates.length readonly property int updateCount: availableUpdates.length
readonly property bool helperAvailable: pkgManager !== "" && distributionSupported readonly property bool helperAvailable: pkgManager !== "" && distributionSupported

View File

@@ -745,6 +745,7 @@ Singleton {
hourly_forecast.push({ hourly_forecast.push({
"time": formatTime(hourly.time[i]), "time": formatTime(hourly.time[i]),
"rawTime": hourly.time[i],
"temp": Math.round(tempC), "temp": Math.round(tempC),
"tempF": Math.round(tempF), "tempF": Math.round(tempF),
"feelsLike": Math.round(feelsLikeC), "feelsLike": Math.round(feelsLikeC),
@@ -753,7 +754,7 @@ Singleton {
"humidity": Math.round(hourly.relative_humidity_2m?.[i] || 0), "humidity": Math.round(hourly.relative_humidity_2m?.[i] || 0),
"wind": Math.round(hourly.wind_speed_10m?.[i] || 0), "wind": Math.round(hourly.wind_speed_10m?.[i] || 0),
"pressure": Math.round(hourly.surface_pressure?.[i] || 0), "pressure": Math.round(hourly.surface_pressure?.[i] || 0),
"precipitationProbability": Math.round(hourly.precipitation_probability_max?.[0] || 0), "precipitationProbability": Math.round(hourly.precipitation_probability?.[i] || 0),
"visibility": Math.round(hourly.visibility?.[i] || 0), "visibility": Math.round(hourly.visibility?.[i] || 0),
"isDay": isDay "isDay": isDay
}); });
@@ -778,7 +779,9 @@ Singleton {
"tempMaxF": Math.round(tempMaxF), "tempMaxF": Math.round(tempMaxF),
"precipitationProbability": Math.round(daily.precipitation_probability_max?.[i] || 0), "precipitationProbability": Math.round(daily.precipitation_probability_max?.[i] || 0),
"sunrise": daily.sunrise?.[i] ? formatTime(daily.sunrise[i]) : "", "sunrise": daily.sunrise?.[i] ? formatTime(daily.sunrise[i]) : "",
"sunset": daily.sunset?.[i] ? formatTime(daily.sunset[i]) : "" "sunset": daily.sunset?.[i] ? formatTime(daily.sunset[i]) : "",
"rawSunrise": daily.sunrise?.[i] || "",
"rawSunset": daily.sunset?.[i] || ""
}); });
} }
} }
@@ -805,6 +808,8 @@ Singleton {
"wind": Math.round(current.wind_speed_10m || 0) + " " + (currentUnits.wind_speed_10m || 'm/s'), "wind": Math.round(current.wind_speed_10m || 0) + " " + (currentUnits.wind_speed_10m || 'm/s'),
"sunrise": formatTime(daily.sunrise?.[0]) || "06:00", "sunrise": formatTime(daily.sunrise?.[0]) || "06:00",
"sunset": formatTime(daily.sunset?.[0]) || "18:00", "sunset": formatTime(daily.sunset?.[0]) || "18:00",
"rawSunrise": daily.sunrise?.[0] || "",
"rawSunset": daily.sunset?.[0] || "",
"uv": 0, "uv": 0,
"pressure": Math.round(current.surface_pressure || 0), "pressure": Math.round(current.surface_pressure || 0),
"precipitationProbability": Math.round(daily.precipitation_probability_max?.[0] || 0), "precipitationProbability": Math.round(daily.precipitation_probability_max?.[0] || 0),

View File

@@ -1 +1 @@
v1.2-unstable v1.2.3

View File

@@ -34,7 +34,10 @@ Image {
return; return;
} }
Paths.mkdir(Paths.imagecache); Paths.mkdir(Paths.imagecache);
source = cachePath || encodedImagePath; const hash = djb2Hash(imagePath);
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : "";
const encoded = "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/');
source = cPath || encoded;
} }
onStatusChanged: { onStatusChanged: {

View File

@@ -94,10 +94,10 @@ Flow {
border.color: "transparent" border.color: "transparent"
border.width: 0 border.width: 0
topLeftRadius: (visualFirst || selected) ? Theme.cornerRadius : 4 topLeftRadius: (visualFirst || selected) ? Theme.cornerRadius : Math.min(4, Theme.cornerRadius)
bottomLeftRadius: (visualFirst || selected) ? Theme.cornerRadius : 4 bottomLeftRadius: (visualFirst || selected) ? Theme.cornerRadius : Math.min(4, Theme.cornerRadius)
topRightRadius: (visualLast || selected) ? Theme.cornerRadius : 4 topRightRadius: (visualLast || selected) ? Theme.cornerRadius : Math.min(4, Theme.cornerRadius)
bottomRightRadius: (visualLast || selected) ? Theme.cornerRadius : 4 bottomRightRadius: (visualLast || selected) ? Theme.cornerRadius : Math.min(4, Theme.cornerRadius)
Behavior on width { Behavior on width {
enabled: root.userInteracted enabled: root.userInteracted

View File

@@ -26,6 +26,8 @@ Item {
"endeavouros": "\u{f322}", "endeavouros": "\u{f322}",
"manjaro": "\u{f160a}", "manjaro": "\u{f160a}",
"opensuse": "\u{f314}", "opensuse": "\u{f314}",
"artix": "\u{f31f}",
"void": "\u{f32e}",
// --- special types --- // --- special types ---
"folder": "\u{F024B}", "folder": "\u{F024B}",

View File

@@ -96,7 +96,7 @@ Item {
setBarContext(pos, bottomGap); setBarContext(pos, bottomGap);
} }
readonly property bool useBackgroundWindow: true readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab
function open() { function open() {
if (!screen) if (!screen)
@@ -304,20 +304,37 @@ Item {
anchors { anchors {
left: true left: true
top: true top: true
right: !useBackgroundWindow
bottom: !useBackgroundWindow
} }
WlrLayershell.margins { WlrLayershell.margins {
left: root.alignedX - shadowBuffer left: useBackgroundWindow ? (root.alignedX - shadowBuffer) : 0
top: root.alignedY - shadowBuffer top: useBackgroundWindow ? (root.alignedY - shadowBuffer) : 0
} }
implicitWidth: root.alignedWidth + (shadowBuffer * 2) implicitWidth: useBackgroundWindow ? (root.alignedWidth + (shadowBuffer * 2)) : 0
implicitHeight: root.alignedHeight + (shadowBuffer * 2) implicitHeight: useBackgroundWindow ? (root.alignedHeight + (shadowBuffer * 2)) : 0
MouseArea {
anchors.fill: parent
enabled: !useBackgroundWindow && shouldBeVisible && backgroundInteractive
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
z: -1
onClicked: mouse => {
const clickX = mouse.x;
const clickY = mouse.y;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
backgroundClicked();
}
}
Item { Item {
id: contentContainer id: contentContainer
x: shadowBuffer x: useBackgroundWindow ? shadowBuffer : root.alignedX
y: shadowBuffer y: useBackgroundWindow ? shadowBuffer : root.alignedY
width: root.alignedWidth width: root.alignedWidth
height: root.alignedHeight height: root.alignedHeight

View File

@@ -32,6 +32,8 @@ StyledRect {
property color leftIconColor: Theme.surfaceVariantText property color leftIconColor: Theme.surfaceVariantText
property color leftIconFocusedColor: Theme.primary property color leftIconFocusedColor: Theme.primary
property bool showClearButton: false property bool showClearButton: false
property bool showPasswordToggle: false
property bool passwordVisible: false
property color backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) property color backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
property color focusedBorderColor: Theme.primary property color focusedBorderColor: Theme.primary
property color normalBorderColor: Theme.outlineMedium property color normalBorderColor: Theme.outlineMedium
@@ -40,7 +42,14 @@ StyledRect {
property int focusedBorderWidth: 2 property int focusedBorderWidth: 2
property real cornerRadius: Theme.cornerRadius property real cornerRadius: Theme.cornerRadius
readonly property real leftPadding: Theme.spacingM + (leftIconName ? leftIconSize + Theme.spacingM : 0) readonly property real leftPadding: Theme.spacingM + (leftIconName ? leftIconSize + Theme.spacingM : 0)
readonly property real rightPadding: Theme.spacingM + (showClearButton && text.length > 0 ? 24 + Theme.spacingM : 0) readonly property real rightPadding: {
let p = Theme.spacingM;
if (showPasswordToggle)
p += 24 + Theme.spacingS;
if (showClearButton && text.length > 0)
p += 24 + Theme.spacingS;
return p;
}
property real topPadding: Theme.spacingM property real topPadding: Theme.spacingM
property real bottomPadding: Theme.spacingM property real bottomPadding: Theme.spacingM
property bool ignoreLeftRightKeys: false property bool ignoreLeftRightKeys: false
@@ -97,8 +106,8 @@ StyledRect {
anchors.left: leftIcon.visible ? leftIcon.right : parent.left anchors.left: leftIcon.visible ? leftIcon.right : parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.right: clearButton.visible ? clearButton.left : parent.right anchors.right: rightButtonsRow.left
anchors.rightMargin: Theme.spacingM anchors.rightMargin: rightButtonsRow.visible ? Theme.spacingS : Theme.spacingM
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: root.topPadding anchors.topMargin: root.topPadding
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -151,33 +160,64 @@ StyledRect {
} }
} }
StyledRect { Row {
id: clearButton id: rightButtonsRow
width: 24
height: 24
radius: 12
color: clearArea.containsMouse ? Theme.outlineStrong : "transparent"
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: showClearButton && text.length > 0 spacing: Theme.spacingXS
visible: showPasswordToggle || (showClearButton && text.length > 0)
DankIcon { StyledRect {
anchors.centerIn: parent id: passwordToggleButton
name: "close"
size: 16 width: 24
color: clearArea.containsMouse ? Theme.outline : Theme.surfaceVariantText height: 24
radius: 12
color: passwordToggleArea.containsMouse ? Theme.outlineStrong : "transparent"
visible: showPasswordToggle
DankIcon {
anchors.centerIn: parent
name: passwordVisible ? "visibility_off" : "visibility"
size: 16
color: passwordToggleArea.containsMouse ? Theme.outline : Theme.surfaceVariantText
}
MouseArea {
id: passwordToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: passwordVisible = !passwordVisible
}
} }
MouseArea { StyledRect {
id: clearArea id: clearButton
anchors.fill: parent width: 24
hoverEnabled: true height: 24
cursorShape: Qt.PointingHandCursor radius: 12
onClicked: { color: clearArea.containsMouse ? Theme.outlineStrong : "transparent"
textInput.text = ""; visible: showClearButton && text.length > 0
DankIcon {
anchors.centerIn: parent
name: "close"
size: 16
color: clearArea.containsMouse ? Theme.outline : Theme.surfaceVariantText
}
MouseArea {
id: clearArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: textInput.text = ""
} }
} }
} }

View File

@@ -1,4 +1,4 @@
pragma ComponentBehavior pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@@ -1273,6 +1273,7 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
RowLayout { RowLayout {
visible: optionsRow.argConfig?.base !== "screenshot-window"
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankToggle { DankToggle {
@@ -1441,8 +1442,9 @@ Item {
onTextChanged: { onTextChanged: {
if (root._actionType !== "shell") if (root._actionType !== "shell")
return; return;
var shell = Actions.getShellFromAction(root.editAction);
root.updateEdit({ root.updateEdit({
"action": Actions.buildShellAction(KeybindsService.currentProvider, text) "action": Actions.buildShellAction(KeybindsService.currentProvider, text, shell)
}); });
} }
} }

View File

@@ -1,3 +1,4 @@
[templates.dmsmango] [templates.dmsmango]
input_path = 'SHELL_DIR/matugen/templates/mango-colors.conf' input_path = 'SHELL_DIR/matugen/templates/mango-colors.conf'
output_path = 'CONFIG_DIR/mango/dms/colors.conf' output_path = 'CONFIG_DIR/mango/dms/colors.conf'
post_hook = 'sh -c "mmsg -d reload_config 2>&1 || true"'

View File

@@ -23,7 +23,7 @@
// Focus + active indicators // Focus + active indicators
// //
"focusBorder": "{{colors.primary.dark.hex}}", "focusBorder": "{{colors.primary.dark.hex}}",
"selection.background": "{{colors.primary_container.dark.hex}}66", "selection.background": "{{colors.primary_container.dark.hex}}99",
// //
// Title bar, activity bar, sidebar // Title bar, activity bar, sidebar
// //
@@ -127,7 +127,7 @@
"editor.lineHighlightBackground": "{{colors.surface_container.dark.hex}}66", "editor.lineHighlightBackground": "{{colors.surface_container.dark.hex}}66",
"editor.wordHighlightBackground": "{{colors.secondary.dark.hex}}22", "editor.wordHighlightBackground": "{{colors.secondary.dark.hex}}22",
"editor.wordHighlightStrongBackground": "{{colors.tertiary.dark.hex}}22", "editor.wordHighlightStrongBackground": "{{colors.tertiary.dark.hex}}22",
"editor.selectionBackground": "{{colors.primary_container.dark.hex}}66", "editor.selectionBackground": "{{colors.primary_container.dark.hex}}99",
"editor.inactiveSelectionBackground": "{{colors.primary_container.dark.hex}}33", "editor.inactiveSelectionBackground": "{{colors.primary_container.dark.hex}}33",
"editorWhitespace.foreground": "{{colors.outline.dark.hex}}66", "editorWhitespace.foreground": "{{colors.outline.dark.hex}}66",
"editorIndentGuide.background1": "{{colors.outline.dark.hex}}33", "editorIndentGuide.background1": "{{colors.outline.dark.hex}}33",
@@ -186,7 +186,8 @@
"terminal.ansiBrightBlue": "{{dank16.color12.dark.hex}}", "terminal.ansiBrightBlue": "{{dank16.color12.dark.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.dark.hex}}", "terminal.ansiBrightMagenta": "{{dank16.color13.dark.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.dark.hex}}", "terminal.ansiBrightCyan": "{{dank16.color14.dark.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.dark.hex}}" "terminal.ansiBrightWhite": "{{dank16.color15.dark.hex}}",
"terminal.selectionBackground": "{{colors.primary_container.dark.hex}}99"
}, },
// //
// Token colors // Token colors

Some files were not shown because too many files have changed in this diff Show More