1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

Compare commits

...

50 Commits

Author SHA1 Message Date
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
bbedward
5ae2cd1dfb i18n: fix RTL in plugin settings 2026-01-08 19:16:55 -05:00
bbedward
eece811fb0 i18n: more RTL repairs 2026-01-08 18:45:38 -05:00
bbedward
1ff1f3a7f2 i18n: more RTL layout enhancements 2026-01-08 16:11:30 -05:00
bbedward
a21a846bf5 wallpaper: encode image URIs
fixes #1306
2026-01-08 14:32:12 -05:00
Anton Kesy
f5f21e738a fix typos (#1304) 2026-01-08 14:10:24 -05:00
bbedward
033e62418a hyprland: fix cursor setting 2026-01-08 09:30:52 -05:00
bbedward
3c69e8b1cc revert readme 2026-01-07 22:59:28 -05:00
bbedward
118be27796 update readme 2026-01-07 22:56:16 -05:00
bbedward
721d35d417 readme:update vid url 2026-01-07 22:54:38 -05:00
bbedward
7bc3d5910d settings: fade to lock and monitor off by default on 2026-01-07 21:31:12 -05:00
144 changed files with 6768 additions and 2338 deletions

1
.gitignore vendored
View File

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

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, Y, exec, dms ipc call dankdash wallpaper
bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview
bind = SUPER, X, exec, dms ipc call powermenu toggle
# === Cheat sheet
bind = SUPER SHIFT, Slash, exec, dms ipc call keybinds toggle hyprland

View File

@@ -106,7 +106,7 @@ windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture
windowrule = float on, match:class ^(zoom)$
# DMS windows floating by default
# ! Hyprland doesnt size these windows correctly so disabling by default here
# ! Hyprland doesn't size these windows correctly so disabling by default here
# windowrule = float on, match:class ^(org.quickshell)$
layerrule = no_anim on, match:namespace ^(quickshell)$

View File

@@ -15,6 +15,8 @@ binds {
Mod+M hotkey-overlay-title="Task Manager" {
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" {
spawn "dms" "ipc" "call" "settings" "focusOrToggle";
}

View File

@@ -325,24 +325,30 @@ func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string {
}
actionNode := bindNode.Children[0]
kdlStr := strings.TrimSpace(actionNode.String())
if kdlStr == "" {
actionName := actionNode.Name.String()
if actionName == "" {
return ""
}
return n.kdlActionToInternal(kdlStr)
}
func (n *NiriProvider) kdlActionToInternal(kdlAction string) string {
parts := n.parseActionParts(kdlAction)
if len(parts) == 0 {
return kdlAction
parts := []string{actionName}
for _, arg := range actionNode.Arguments {
val := arg.ValueString()
if val == "" {
parts = append(parts, `""`)
} else {
parts = append(parts, val)
}
}
for i, part := range parts {
if part == "" {
parts[i] = `""`
if actionNode.Properties != nil {
if val, ok := actionNode.Properties.Get("focus"); ok {
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, "cursor", filepath.Join(homeDir, ".cursor/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:
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
}

View File

@@ -31,7 +31,7 @@ import (
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
// These mime types wont be stored in history
// These mime types won't be stored in history
var sensitiveMimeTypes = []string{
"x-kde-passwordManagerHint",
}

View File

@@ -124,27 +124,23 @@ func (sc *SharedContext) eventDispatcher() {
}
for {
sc.drainCmdQueue()
select {
case <-sc.stopChan:
return
default:
}
sc.drainCmdQueue()
n, err := unix.Poll(pollFds, 50)
if err != nil {
if err == unix.EINTR {
continue
}
_, err := unix.Poll(pollFds, -1)
switch {
case err == unix.EINTR:
continue
case err != nil:
log.Errorf("Poll error: %v", err)
return
}
if n == 0 {
continue
}
if pollFds[1].Revents&unix.POLLIN != 0 {
var buf [64]byte
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 err := ctx.Dispatch(); err != nil {
if !os.IsTimeout(err) {
log.Errorf("Wayland connection error: %v", err)
return
}
}
if pollFds[0].Revents&unix.POLLIN == 0 {
continue
}
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() {
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()
unix.Close(sc.wakeR)
unix.Close(sc.wakeW)
if sc.display != nil {
sc.display.Context().Close()
if sc.display == nil {
return
}
sc.display.Context().Close()
}

View File

@@ -96,7 +96,7 @@ func (c *CUPSClient) RejectJobs(printer string) error {
return err
}
// AddPrinterToClass adds a printer to a class, if the class does not exists it will be crated
// AddPrinterToClass adds a printer to a class, if the class does not exists it will be created
func (c *CUPSClient) AddPrinterToClass(class, printer string) error {
attributes, err := c.GetPrinterAttributes(class, []string{AttributeMemberURIs})
if err != nil && !IsNotExistsError(err) {

View File

@@ -19,7 +19,8 @@ in
]
++ lib.optional cfg.enableDynamicTheming pkgs.matugen
++ 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: {
source = plugin.src;

View File

@@ -70,6 +70,12 @@ in
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 = {
package = lib.mkPackageOption dmsPkgs "quickshell" {
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)
];
version =
pkgs.lib.removePrefix "v" (pkgs.lib.trim (builtins.readFile ./quickshell/VERSION))
+ "+date="
+ mkDate (self.lastModifiedDate or "19700101")
+ "_"
+ (self.shortRev or "dirty");
let
rawVersion = pkgs.lib.removePrefix "v" (pkgs.lib.trim (builtins.readFile ./quickshell/VERSION));
cleanVersion = builtins.replaceStrings [ " " ] [ "" ] rawVersion;
dateSuffix = "+date=" + mkDate (self.lastModifiedDate or "19700101");
revSuffix = "_" + (self.shortRev or "dirty");
in
"${cleanVersion}${dateSuffix}${revSuffix}";
in
{
dms-shell = pkgs.buildGoModule (
@@ -83,7 +85,7 @@
ldflags = [
"-s"
"-w"
"-X main.Version=${version}"
"-X 'main.Version=${version}'"
];
nativeBuildInputs = with pkgs; [

View File

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

View File

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

View File

@@ -46,18 +46,19 @@ Singleton {
}
function moddedAppId(appId: string): string {
if (appId === "Spotify")
return "spotify";
if (appId === "beepertexts")
return "beeper";
if (appId === "home assistant desktop")
return "homeassistant-desktop";
if (appId.includes("com.transmissionbt.transmission")) {
if (DesktopEntries.heuristicLookup("transmission-gtk"))
return "transmission-gtk";
if (DesktopEntries.heuristicLookup("transmission"))
return "transmission";
return "transmission-gtk";
const subs = SettingsData.appIdSubstitutions || [];
for (let i = 0; i < subs.length; i++) {
const sub = subs[i];
if (sub.type === "exact" && appId === sub.pattern) {
return sub.replacement;
} else if (sub.type === "contains" && appId.includes(sub.pattern)) {
return sub.replacement;
} else if (sub.type === "regex") {
const match = appId.match(new RegExp(sub.pattern));
if (match) {
return sub.replacement.replace(/\$(\d+)/g, (_, n) => match[n] || "");
}
}
}
return appId;
}
@@ -68,8 +69,8 @@ Singleton {
}
const moddedId = moddedAppId(appId);
if (moddedId.toLowerCase().includes("steam_app")) {
return "";
if (moddedId !== appId) {
return Quickshell.iconPath(moddedId, true);
}
return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : "";

View File

@@ -1,5 +1,5 @@
pragma Singleton
pragma ComponentBehavior
pragma ComponentBehavior: Bound
import QtCore
import QtQuick
@@ -200,10 +200,16 @@ Singleton {
property bool showWorkspaceApps: false
property bool groupWorkspaceApps: true
property int maxWorkspaceIcons: 3
property bool workspacesPerMonitor: true
property bool workspaceFollowFocus: false
property bool showOccupiedWorkspacesOnly: false
property bool reverseScrolling: 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 bool waveProgressEnabled: true
property bool scrollTitleEnabled: true
@@ -215,6 +221,7 @@ Singleton {
property bool keyboardLayoutNameCompactMode: false
property bool runningAppsCurrentWorkspace: false
property bool runningAppsGroupByApp: false
property var appIdSubstitutions: []
property string centeringMode: "index"
property string clockDateFormat: ""
property string lockDateFormat: ""
@@ -318,9 +325,9 @@ Singleton {
property int batteryChargeLimit: 100
property bool lockBeforeSuspend: false
property bool loginctlLockIntegration: true
property bool fadeToLockEnabled: false
property bool fadeToLockEnabled: true
property int fadeToLockGracePeriod: 5
property bool fadeToDpmsEnabled: false
property bool fadeToDpmsEnabled: true
property int fadeToDpmsGracePeriod: 5
property string launchPrefix: ""
property var brightnessDevicePins: ({})
@@ -397,6 +404,7 @@ Singleton {
property int notificationTimeoutLow: 5000
property int notificationTimeoutNormal: 5000
property int notificationTimeoutCritical: 0
property bool notificationCompactMode: false
property int notificationPopupPosition: SettingsData.Position.Top
property bool notificationHistoryEnabled: true
property int notificationHistoryMaxCount: 50
@@ -530,6 +538,7 @@ Singleton {
property var desktopWidgetPositions: ({})
property var desktopWidgetGridSettings: ({})
property var desktopWidgetInstances: []
property var desktopWidgetGroups: []
function getDesktopWidgetGridSetting(screenKey, property, defaultValue) {
const val = desktopWidgetGridSettings?.[screenKey]?.[property];
@@ -681,6 +690,38 @@ Singleton {
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) {
const source = getDesktopWidgetInstance(instanceId);
if (!source)
@@ -713,6 +754,110 @@ Singleton {
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 forceDockLayoutRefresh
signal widgetDataChanged
@@ -1836,6 +1981,48 @@ Singleton {
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) {
var stored = registryThemeVariants[themeId];
if (typeof stored === "string")

View File

@@ -546,7 +546,7 @@ Singleton {
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode)
SessionData.setLightMode(light);
if (!isGreeterMode) {
// Skip with matugen becuase, our script runner will do it.
// Skip with matugen because, our script runner will do it.
if (!matugenAvailable) {
PortalService.setLightMode(light);
}

View File

@@ -1,5 +1,5 @@
.pragma library
// This exists only beacause I haven't been able to get linkColor to work with MarkdownText
// This exists only because I haven't been able to get linkColor to work with MarkdownText
// May not be necessary if that's possible tbh.
function markdownToHtml(text) {
if (!text) return "";

View File

@@ -94,10 +94,16 @@ var SPEC = {
showWorkspaceApps: { def: false },
maxWorkspaceIcons: { def: 3 },
groupWorkspaceApps: { def: true },
workspacesPerMonitor: { def: true },
workspaceFollowFocus: { def: false },
showOccupiedWorkspacesOnly: { def: false },
reverseScrolling: { 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: {} },
waveProgressEnabled: { def: true },
scrollTitleEnabled: { def: true },
@@ -109,6 +115,13 @@ var SPEC = {
keyboardLayoutNameCompactMode: { def: false },
runningAppsCurrentWorkspace: { 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" },
clockDateFormat: { def: "" },
lockDateFormat: { def: "" },
@@ -177,9 +190,9 @@ var SPEC = {
batteryChargeLimit: { def: 100 },
lockBeforeSuspend: { def: false },
loginctlLockIntegration: { def: true },
fadeToLockEnabled: { def: false },
fadeToLockEnabled: { def: true },
fadeToLockGracePeriod: { def: 5 },
fadeToDpmsEnabled: { def: false },
fadeToDpmsEnabled: { def: true },
fadeToDpmsGracePeriod: { def: 5 },
launchPrefix: { def: "" },
brightnessDevicePins: { def: {} },
@@ -255,6 +268,7 @@ var SPEC = {
notificationTimeoutLow: { def: 5000 },
notificationTimeoutNormal: { def: 5000 },
notificationTimeoutCritical: { def: 0 },
notificationCompactMode: { def: false },
notificationPopupPosition: { def: 0 },
notificationHistoryEnabled: { def: true },
notificationHistoryMaxCount: { def: 50 },
@@ -388,6 +402,8 @@ var SPEC = {
desktopWidgetInstances: { def: [] },
desktopWidgetGroups: { def: [] },
builtInPluginSettings: { def: {} }
};

View File

@@ -442,17 +442,15 @@ Item {
PopoutService.settingsModalLoader = settingsModalLoader;
}
onActiveChanged: {
if (active && item) {
PopoutService.settingsModal = item;
PopoutService._onSettingsModalLoaded();
}
}
SettingsModal {
id: settingsModal
property bool wasShown: false
Component.onCompleted: {
PopoutService.settingsModal = settingsModal;
PopoutService._onSettingsModalLoaded();
}
onVisibleChanged: {
if (visible) {
wasShown = true;

View File

@@ -599,6 +599,39 @@ Item {
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"
}
@@ -764,11 +797,9 @@ Item {
const modal = PopoutService.settingsModal;
if (modal) {
if (type === "wallpaper") {
modal.wallpaperBrowser.allowStacking = false;
modal.wallpaperBrowser.open();
modal.openWallpaperBrowser(false);
} else if (type === "profile") {
modal.profileBrowser.allowStacking = false;
modal.profileBrowser.open();
modal.openProfileBrowser(false);
}
} else {
PopoutService.openSettings();
@@ -1035,7 +1066,7 @@ Item {
const instances = SettingsData.desktopWidgetInstances || [];
if (instances.length === 0)
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 {
@@ -1046,9 +1077,115 @@ Item {
if (!instance)
return `DESKTOP_WIDGET_NOT_FOUND: ${instanceId}`;
const enabled = instance.enabled ?? true;
const overlay = instance.config?.showOnOverlay ?? 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"

View File

@@ -128,7 +128,7 @@ FloatingWindow {
iconName: "open_in_new"
backgroundColor: Theme.surfaceContainerHighest
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 {

View File

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

View File

@@ -8,6 +8,9 @@ import qs.Widgets
FocusScope {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string docsDir: StandardPaths.writableLocation(StandardPaths.DocumentsLocation)
property string musicDir: StandardPaths.writableLocation(StandardPaths.MusicLocation)
@@ -52,6 +55,12 @@ FocusScope {
signal fileSelected(string path)
signal closeRequested
function encodeFileUrl(path) {
if (!path)
return "";
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
}
function initialize() {
loadSettings();
currentPath = getLastPath();
@@ -188,7 +197,7 @@ FocusScope {
function handleSaveFile(filePath) {
var normalizedPath = filePath;
if (!normalizedPath.startsWith("file://")) {
normalizedPath = "file://" + filePath;
normalizedPath = encodeFileUrl(filePath);
}
var exists = false;
@@ -274,7 +283,7 @@ FocusScope {
nameFilters: fileExtensions
showFiles: true
showDirs: true
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
folder: encodeFileUrl(currentPath || homeDir)
sortField: {
switch (sortBy) {
case "name":

View File

@@ -21,67 +21,67 @@ StyledRect {
signal itemSelected(int index, string path, string name, bool isDir)
function getFileExtension(fileName) {
const parts = fileName.split('.')
const parts = fileName.split('.');
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase()
return parts[parts.length - 1].toLowerCase();
}
return ""
return "";
}
function determineFileType(fileName) {
const ext = getFileExtension(fileName)
const ext = getFileExtension(fileName);
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"];
if (imageExts.includes(ext)) {
return "image"
return "image";
}
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"];
if (videoExts.includes(ext)) {
return "video"
return "video";
}
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"];
if (audioExts.includes(ext)) {
return "audio"
return "audio";
}
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"];
if (codeExts.includes(ext)) {
return "code"
return "code";
}
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"];
if (docExts.includes(ext)) {
return "document"
return "document";
}
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"];
if (archiveExts.includes(ext)) {
return "archive"
return "archive";
}
if (!ext || fileName.indexOf('.') === -1) {
return "binary"
return "binary";
}
return "file"
return "file";
}
function isImageFile(fileName) {
if (!fileName) {
return false
return false;
}
return determineFileType(fileName) === "image"
return determineFileType(fileName) === "image";
}
function getIconForFile(fileName) {
const lowerName = fileName.toLowerCase()
const lowerName = fileName.toLowerCase();
if (lowerName.startsWith("dockerfile")) {
return "docker"
return "docker";
}
const ext = fileName.split('.').pop()
return ext || ""
const ext = fileName.split('.').pop();
return ext || "";
}
width: weMode ? 245 : iconSizes[iconSizeIndex] + 16
@@ -89,21 +89,21 @@ StyledRect {
radius: Theme.cornerRadius
color: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
return Theme.surfacePressed
return Theme.surfacePressed;
return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
return mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent";
}
border.color: keyboardNavigationActive && delegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.width: (keyboardNavigationActive && delegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
if (keyboardNavigationActive && delegateRoot.index === selectedIndex)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir);
}
onSelectedIndexChanged: {
if (keyboardNavigationActive && selectedIndex === delegateRoot.index)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
itemSelected(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir);
}
Column {
@@ -115,30 +115,31 @@ StyledRect {
height: weMode ? 165 : (iconSizes[iconSizeIndex] - 8)
anchors.horizontalCenter: parent.horizontalCenter
CachingImage {
Image {
id: gridPreviewImage
anchors.fill: parent
anchors.margins: 2
property var weExtensions: [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tga"]
property int weExtIndex: 0
source: {
if (weMode && delegateRoot.fileIsDir) {
return "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
}
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? ("file://" + delegateRoot.filePath) : ""
property string imagePath: {
if (weMode && delegateRoot.fileIsDir)
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? delegateRoot.filePath : "";
}
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
onStatusChanged: {
if (weMode && delegateRoot.fileIsDir && status === Image.Error) {
if (weExtIndex < weExtensions.length - 1) {
weExtIndex++
source = "file://" + delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]
weExtIndex++;
} else {
source = ""
imagePath = "";
}
}
}
fillMode: Image.PreserveAspectCrop
maxCacheSize: weMode ? 225 : iconSizes[iconSizeIndex]
sourceSize.width: weMode ? 225 : iconSizes[iconSizeIndex]
sourceSize.height: weMode ? 225 : iconSizes[iconSizeIndex]
asynchronous: true
visible: false
}
@@ -198,7 +199,7 @@ StyledRect {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir)
itemClicked(delegateRoot.index, delegateRoot.filePath, delegateRoot.fileName, delegateRoot.fileIsDir);
}
}
}

View File

@@ -20,97 +20,97 @@ StyledRect {
signal itemSelected(int index, string path, string name, bool isDir)
function getFileExtension(fileName) {
const parts = fileName.split('.')
const parts = fileName.split('.');
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase()
return parts[parts.length - 1].toLowerCase();
}
return ""
return "";
}
function determineFileType(fileName) {
const ext = getFileExtension(fileName)
const ext = getFileExtension(fileName);
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"]
const imageExts = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "ico"];
if (imageExts.includes(ext)) {
return "image"
return "image";
}
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"]
const videoExts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"];
if (videoExts.includes(ext)) {
return "video"
return "video";
}
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"]
const audioExts = ["mp3", "wav", "flac", "ogg", "m4a", "aac", "wma"];
if (audioExts.includes(ext)) {
return "audio"
return "audio";
}
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"]
const codeExts = ["js", "ts", "jsx", "tsx", "py", "go", "rs", "c", "cpp", "h", "java", "kt", "swift", "rb", "php", "html", "css", "scss", "json", "xml", "yaml", "yml", "toml", "sh", "bash", "zsh", "fish", "qml", "vue", "svelte"];
if (codeExts.includes(ext)) {
return "code"
return "code";
}
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"]
const docExts = ["txt", "md", "pdf", "doc", "docx", "odt", "rtf"];
if (docExts.includes(ext)) {
return "document"
return "document";
}
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"]
const archiveExts = ["zip", "tar", "gz", "bz2", "xz", "7z", "rar"];
if (archiveExts.includes(ext)) {
return "archive"
return "archive";
}
if (!ext || fileName.indexOf('.') === -1) {
return "binary"
return "binary";
}
return "file"
return "file";
}
function isImageFile(fileName) {
if (!fileName) {
return false
return false;
}
return determineFileType(fileName) === "image"
return determineFileType(fileName) === "image";
}
function getIconForFile(fileName) {
const lowerName = fileName.toLowerCase()
const lowerName = fileName.toLowerCase();
if (lowerName.startsWith("dockerfile")) {
return "docker"
return "docker";
}
const ext = fileName.split('.').pop()
return ext || ""
const ext = fileName.split('.').pop();
return ext || "";
}
function formatFileSize(size) {
if (size < 1024)
return size + " B"
return size + " B";
if (size < 1024 * 1024)
return (size / 1024).toFixed(1) + " KB"
return (size / 1024).toFixed(1) + " KB";
if (size < 1024 * 1024 * 1024)
return (size / (1024 * 1024)).toFixed(1) + " MB"
return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB"
return (size / (1024 * 1024)).toFixed(1) + " MB";
return (size / (1024 * 1024 * 1024)).toFixed(1) + " GB";
}
height: 44
radius: Theme.cornerRadius
color: {
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
return Theme.surfacePressed
return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
return Theme.surfacePressed;
return listMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent";
}
border.color: keyboardNavigationActive && listDelegateRoot.index === selectedIndex ? Theme.primary : "transparent"
border.width: (keyboardNavigationActive && listDelegateRoot.index === selectedIndex) ? 2 : 0
Component.onCompleted: {
if (keyboardNavigationActive && listDelegateRoot.index === selectedIndex)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir);
}
onSelectedIndexChanged: {
if (keyboardNavigationActive && selectedIndex === listDelegateRoot.index)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
itemSelected(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir);
}
Row {
@@ -124,12 +124,15 @@ StyledRect {
height: 28
anchors.verticalCenter: parent.verticalCenter
CachingImage {
Image {
id: listPreviewImage
anchors.fill: parent
source: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? ("file://" + listDelegateRoot.filePath) : ""
property string imagePath: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? listDelegateRoot.filePath : ""
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
fillMode: Image.PreserveAspectCrop
maxCacheSize: 32
sourceSize.width: 32
sourceSize.height: 32
asynchronous: true
visible: false
}
@@ -203,7 +206,7 @@ StyledRect {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir)
itemClicked(listDelegateRoot.index, listDelegateRoot.filePath, listDelegateRoot.fileName, listDelegateRoot.fileIsDir);
}
}
}

View File

@@ -45,8 +45,12 @@ FloatingWindow {
parentModal.shouldHaveFocus = false;
parentModal.allowFocusOverride = true;
}
content.reset();
Qt.callLater(() => content.forceActiveFocus());
Qt.callLater(() => {
if (content) {
content.reset();
content.forceActiveFocus();
}
});
} else {
if (parentModal && "allowFocusOverride" in parentModal) {
parentModal.allowFocusOverride = false;
@@ -56,27 +60,35 @@ FloatingWindow {
}
}
FileBrowserContent {
id: content
Loader {
id: contentLoader
anchors.fill: parent
focus: true
closeOnEscape: false
windowControls: windowControls
active: fileBrowserModal.visible
sourceComponent: FileBrowserContent {
id: content
anchors.fill: parent
focus: true
closeOnEscape: false
windowControls: fileBrowserModal.windowControlsRef
browserTitle: fileBrowserModal.browserTitle
browserIcon: fileBrowserModal.browserIcon
browserType: fileBrowserModal.browserType
fileExtensions: fileBrowserModal.fileExtensions
showHiddenFiles: fileBrowserModal.showHiddenFiles
saveMode: fileBrowserModal.saveMode
defaultFileName: fileBrowserModal.defaultFileName
browserTitle: fileBrowserModal.browserTitle
browserIcon: fileBrowserModal.browserIcon
browserType: fileBrowserModal.browserType
fileExtensions: fileBrowserModal.fileExtensions
showHiddenFiles: fileBrowserModal.showHiddenFiles
saveMode: fileBrowserModal.saveMode
defaultFileName: fileBrowserModal.defaultFileName
Component.onCompleted: initialize()
Component.onCompleted: initialize()
onFileSelected: path => fileBrowserModal.fileSelected(path)
onCloseRequested: fileBrowserModal.close()
onFileSelected: path => fileBrowserModal.fileSelected(path)
onCloseRequested: fileBrowserModal.close()
}
}
property alias content: contentLoader.item
property alias windowControlsRef: windowControls
FloatingWindowControls {
id: windowControls
targetWindow: fileBrowserModal

View File

@@ -33,8 +33,12 @@ DankModal {
if (parentPopout) {
parentPopout.customKeyboardFocus = WlrKeyboardFocus.None;
}
content.reset();
Qt.callLater(() => content.forceActiveFocus());
Qt.callLater(() => {
if (contentLoader.item) {
contentLoader.item.reset();
contentLoader.item.forceActiveFocus();
}
});
}
onDialogClosed: {
@@ -43,8 +47,7 @@ DankModal {
}
}
directContent: FileBrowserContent {
id: content
content: FileBrowserContent {
focus: true
browserTitle: fileBrowserSurfaceModal.browserTitle

View File

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

View File

@@ -74,9 +74,7 @@ Rectangle {
if (root.parentModal) {
root.parentModal.allowFocusOverride = true;
root.parentModal.shouldHaveFocus = false;
if (root.parentModal.profileBrowser) {
root.parentModal.profileBrowser.open();
}
root.parentModal.openProfileBrowser();
}
}
}
@@ -130,6 +128,7 @@ Rectangle {
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -138,6 +137,7 @@ Rectangle {
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,6 +40,12 @@ Variants {
id: root
anchors.fill: parent
function encodeFileUrl(path) {
if (!path)
return "";
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
}
property string source: SessionData.getMonitorWallpaper(modelData.name) || ""
property bool isColorSource: source.startsWith("#")
@@ -83,7 +89,7 @@ Variants {
isInitialized = true;
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
setWallpaperImmediate(formattedSource);
isInitialized = true;
}
@@ -100,7 +106,7 @@ Variants {
return;
}
const formattedSource = source.startsWith("file://") ? source : "file://" + source;
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
if (!isInitialized || !currentWallpaper.source) {
setWallpaperImmediate(formattedSource);

View File

@@ -5,6 +5,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string iconName: ""
property string text: ""
property string secondaryText: ""
@@ -80,6 +83,7 @@ Rectangle {
color: isActive ? Theme.primaryText : Theme.surfaceText
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
Typography {
@@ -90,6 +94,7 @@ Rectangle {
visible: text.length > 0
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Row {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var availableWidgets: []
property Item popoutContent: null
@@ -103,6 +106,7 @@ Row {
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Typography {
@@ -111,6 +115,7 @@ Row {
color: Theme.outline
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property bool editMode: false
signal powerButtonClicked

View File

@@ -5,6 +5,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string iconName: ""
property string text: ""

View File

@@ -8,6 +8,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property bool hasInputVolumeSliderInCC: {
const widgets = SettingsData.controlCenterWidgets || [];
return widgets.some(widget => widget.id === "inputVolumeSlider");
@@ -198,6 +201,7 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -207,6 +211,7 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -8,6 +8,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property bool hasVolumeSliderInCC: {
const widgets = SettingsData.controlCenterWidgets || [];
return widgets.some(widget => widget.id === "volumeSlider");
@@ -210,6 +213,7 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -219,6 +223,7 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -5,6 +5,11 @@ import qs.Services
import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
@@ -110,6 +115,7 @@ Rectangle {
visible: text.length > 0
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -249,6 +255,7 @@ Rectangle {
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.8)
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var device: null
property bool modalVisible: false
property var parentItem

View File

@@ -10,6 +10,9 @@ import qs.Modals
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: {
if (height > 0) {
return height
@@ -237,6 +240,7 @@ Rectangle {
font.weight: modelData.connected ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Row {
@@ -463,6 +467,7 @@ Rectangle {
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Row {

View File

@@ -7,6 +7,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string initialDeviceName: ""
property string instanceId: ""
property string screenName: ""
@@ -303,6 +306,7 @@ Rectangle {
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -311,6 +315,7 @@ Rectangle {
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -328,6 +333,7 @@ Rectangle {
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string currentMountPath: "/"
property string instanceId: ""
@@ -128,6 +131,7 @@ Rectangle {
font.weight: modelData.mount === currentMountPath ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -137,6 +141,7 @@ Rectangle {
elide: Text.ElideRight
width: parent.width
visible: modelData.mount !== "/"
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -145,6 +150,7 @@ Rectangle {
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -9,6 +9,9 @@ import qs.Modals
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
implicitHeight: {
if (height > 0) {
return height;
@@ -34,6 +37,10 @@ Rectangle {
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: {
if (DMSService.apiVersion < 5) {
return 1;
@@ -43,19 +50,24 @@ Rectangle {
return 1;
}
const pref = NetworkService.userPreference;
const status = NetworkService.networkStatus;
let index = 1;
if (pref === "ethernet") {
index = 0;
} else if (pref === "wifi") {
index = 1;
} else {
index = status === "ethernet" ? 0 : 1;
if (!hasEthernetAvailable) {
return 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 {
@@ -114,7 +126,7 @@ Rectangle {
DankButtonGroup {
id: preferenceControls
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
visible: hasBothConnectionTypes && NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
buttonHeight: 28
textSize: Theme.fontSizeSmall

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Row {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var defaultSink: AudioService.sink
property color sliderTrackColor: "transparent"

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Row {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string deviceName: ""
property string instanceId: ""
property string screenName: ""

View File

@@ -1,12 +1,13 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string iconName: ""
property color iconColor: Theme.surfaceText
property string labelText: ""

View File

@@ -5,6 +5,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string iconName: ""
property color iconColor: Theme.surfaceText
property string primaryText: ""
@@ -137,6 +140,7 @@ Rectangle {
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
StyledText {
width: parent.width
@@ -146,6 +150,7 @@ Rectangle {
visible: text.length > 0
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
}

View File

@@ -1,8 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Widgets
Rectangle {
id: root
@@ -24,6 +20,4 @@ Rectangle {
sourceComponent: root.content
asynchronous: true
}
}

View File

@@ -5,6 +5,9 @@ import qs.Widgets
StyledRect {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string primaryMessage: ""
property string secondaryMessage: ""
@@ -37,6 +40,7 @@ StyledRect {
color: Theme.warning
font.weight: Font.Medium
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -45,6 +49,7 @@ StyledRect {
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: text.length > 0
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Row {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var defaultSource: AudioService.source
property color sliderTrackColor: "transparent"

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property bool isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
property bool enabled: BatteryService.batteryAvailable

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string mountPath: "/"
property string instanceId: ""
@@ -84,6 +87,7 @@ Rectangle {
color: Theme.surfaceVariantText
elide: Text.ElideMiddle
width: Math.min(implicitWidth, root.width - Theme.iconSizeSmall - Theme.spacingM)
horizontalAlignment: Text.AlignLeft
}
StyledText {

View File

@@ -5,6 +5,9 @@ import qs.Widgets
Rectangle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string iconName: ""
property string text: ""
property bool isActive: false
@@ -90,6 +93,7 @@ Rectangle {
font.weight: Font.Medium
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -100,6 +104,7 @@ Rectangle {
visible: text.length > 0
elide: Text.ElideRight
wrapMode: Text.NoWrap
horizontalAlignment: Text.AlignLeft
}
}
}

View File

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

View File

@@ -56,6 +56,13 @@ BasePill {
}
}
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
root.updateDesktopEntry();
}
}
function updateDesktopEntry() {
if (activeWindow && activeWindow.appId) {
const moddedId = Paths.moddedAppId(activeWindow.appId);
@@ -148,29 +155,9 @@ BasePill {
}
}
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId)
return false;
const moddedId = Paths.moddedAppId(activeWindow.appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
Text {
anchors.centerIn: parent
visible: {
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");
}
visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready
text: {
if (!activeWindow || !activeWindow.appId)
return "?";

View File

@@ -19,7 +19,7 @@ BasePill {
anchors.centerIn: parent
name: SessionData.doNotDisturb ? "notifications_off" : "notifications"
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 {
@@ -35,6 +35,6 @@ BasePill {
}
onRightClicked: {
SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
}
}

View File

@@ -69,6 +69,7 @@ Item {
property int _desktopEntriesUpdateTrigger: 0
property int _toplevelsUpdateTrigger: 0
property int _appIdSubstitutionsTrigger: 0
readonly property var sortedToplevels: {
_toplevelsUpdateTrigger;
@@ -95,6 +96,13 @@ Item {
_desktopEntriesUpdateTrigger++;
}
}
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
_appIdSubstitutionsTrigger++;
}
}
readonly property var groupedWindows: {
if (!SettingsData.runningAppsGroupByApp) {
return [];
@@ -364,6 +372,7 @@ Item {
height: Theme.barIconSize(root.barThickness)
source: {
root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appId)
return "";
const moddedId = Paths.moddedAppId(appId);
@@ -384,27 +393,9 @@ Item {
}
}
DankIcon {
anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness)
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
// Fallback text if no icon found
Text {
anchors.centerIn: parent
visible: {
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
visible: !iconImg.visible
text: {
root._desktopEntriesUpdateTrigger;
if (!appId)
@@ -502,8 +493,10 @@ Item {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top");
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
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) {
@@ -614,6 +607,7 @@ Item {
height: Theme.barIconSize(root.barThickness)
source: {
root._desktopEntriesUpdateTrigger;
root._appIdSubstitutionsTrigger;
if (!appId)
return "";
const moddedId = Paths.moddedAppId(appId);
@@ -634,26 +628,9 @@ Item {
}
}
DankIcon {
anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness)
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
Text {
anchors.centerIn: parent
visible: {
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
visible: !iconImg.visible
text: {
root._desktopEntriesUpdateTrigger;
if (!appId)
@@ -751,8 +728,10 @@ Item {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top");
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
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
width: parent.width
height: 28
radius: 0
radius: Theme.cornerRadius
color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
StyledText {
@@ -1261,7 +1261,7 @@ Item {
visible: entryStack.count > 0
width: parent.width
height: 28
radius: 0
radius: Theme.cornerRadius
color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
Row {
@@ -1309,11 +1309,10 @@ Item {
width: menuColumn.width
height: menuEntry?.isSeparator ? 1 : 28
radius: 0
radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
color: {
if (menuEntry?.isSeparator) {
if (menuEntry?.isSeparator)
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
}
return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0);
}

View File

@@ -24,6 +24,26 @@ Item {
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)
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);
}
@@ -107,7 +127,7 @@ Item {
}
function getSwayActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
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);
} else {
const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName);
@@ -163,7 +183,7 @@ Item {
}
function getHyprlandActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
return Hyprland.focusedWorkspace?.id || 1;
}
@@ -183,7 +203,7 @@ Item {
if (wsNumber <= 0) {
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) {
return [];
}
@@ -211,7 +231,7 @@ Item {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
} else if (CompositorService.isDwl) {
const output = DwlService.getOutputState(root.screenName);
const output = DwlService.getOutputState(root.effectiveScreenName);
if (output && output.tags) {
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
isActiveWs = tag ? (tag.state === 1) : false;
@@ -244,15 +264,13 @@ Item {
const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${keyBase}_${i}` : keyBase;
if (!byApp[key]) {
const moddedId = Paths.moddedAppId(keyBase);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
const isQuickshell = keyBase === "org.quickshell";
const desktopEntry = DesktopEntries.heuristicLookup(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] = {
"type": "icon",
"icon": icon,
"isSteamApp": isSteamApp,
"isQuickshell": isQuickshell,
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
"count": 1,
@@ -308,7 +326,7 @@ Item {
}
let workspaces;
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
workspaces = NiriService.getCurrentOutputWorkspaceNumbers();
} else {
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName).map(ws => ws.idx + 1);
@@ -320,7 +338,7 @@ Item {
}
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)
return false;
if (workspace.is_active)
@@ -334,7 +352,7 @@ Item {
return 1;
}
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentWorkspaceNumber();
}
@@ -343,14 +361,13 @@ Item {
}
function getDwlTags() {
if (!DwlService.dwlAvailable) {
if (!DwlService.dwlAvailable)
return [];
}
const output = DwlService.getOutputState(root.screenName);
if (!output || !output.tags || output.tags.length === 0) {
const targetScreen = root.effectiveScreenName;
const output = DwlService.getOutputState(targetScreen);
if (!output || !output.tags || output.tags.length === 0)
return [];
}
if (SettingsData.dwlShowAllTags) {
return output.tags.map(tag => ({
@@ -361,7 +378,7 @@ Item {
}));
}
const visibleTagIndices = DwlService.getVisibleTags(root.screenName);
const visibleTagIndices = DwlService.getVisibleTags(targetScreen);
return visibleTagIndices.map(tagIndex => {
const tagData = output.tags.find(t => t.tag === tagIndex);
return {
@@ -374,12 +391,10 @@ Item {
}
function getDwlActiveTags() {
if (!DwlService.dwlAvailable) {
if (!DwlService.dwlAvailable)
return [];
}
const activeTags = DwlService.getActiveTags(root.screenName);
return activeTags;
return DwlService.getActiveTags(root.effectiveScreenName);
}
function getExtWorkspaceWorkspaces() {
@@ -790,6 +805,68 @@ Item {
readonly property real visualWidth: baseWidth + iconsExtraWidth
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 {
id: mouseArea
anchors.fill: parent
@@ -850,7 +927,7 @@ Item {
if (root.useExtWorkspace) {
wsData = modelData;
} 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) {
wsData = modelData;
} else if (CompositorService.isDwl) {
@@ -892,16 +969,61 @@ Item {
width: root.isVertical ? root.barThickness : visualWidth
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 {
id: visualContent
width: delegateRoot.visualWidth
height: delegateRoot.visualHeight
anchors.centerIn: parent
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.color: isUrgent ? Theme.error : Theme.withAlpha(Theme.error, 0)
border.color: isUrgent ? urgentColor : "transparent"
Behavior on width {
NumberAnimation {
@@ -1013,7 +1135,7 @@ Item {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isSteamApp && !modelData.isQuickshell
visible: !modelData.isQuickshell
}
IconImage {
@@ -1025,19 +1147,10 @@ Item {
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
}
}
DankIcon {
anchors.centerIn: parent
size: root.appIconSize
name: "sports_esports"
color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp
}
MouseArea {
id: rowAppMouseArea
anchors.fill: parent
@@ -1116,7 +1229,7 @@ Item {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isSteamApp && !modelData.isQuickshell
visible: !modelData.isQuickshell
}
IconImage {
@@ -1128,19 +1241,10 @@ Item {
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
}
}
DankIcon {
anchors.centerIn: parent
size: root.appIconSize
name: "sports_esports"
color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp
}
MouseArea {
id: colAppMouseArea
anchors.fill: parent
@@ -1264,6 +1368,9 @@ Item {
function onWorkspaceNameIconsChanged() {
delegateRoot.updateAllData();
}
function onAppIdSubstitutionsChanged() {
delegateRoot.updateAllData();
}
}
Connections {
target: DwlService

View File

@@ -6,6 +6,9 @@ import qs.Widgets
Card {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
Component.onCompleted: DgopService.addRef("system")
Component.onDestruction: DgopService.removeRef("system")
@@ -44,9 +47,11 @@ Card {
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3
horizontalAlignment: Text.AlignLeft
}
Row {
anchors.left: parent.left
spacing: Theme.spacingS
SystemLogo {
@@ -76,10 +81,12 @@ Card {
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
horizontalAlignment: Text.AlignLeft
}
}
Row {
anchors.left: parent.left
spacing: Theme.spacingS
DankIcon {

View File

@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Common
import qs.Services
import qs.Widgets
@@ -8,7 +7,10 @@ import qs.Widgets
Card {
id: root
signal clicked()
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
signal clicked
Component.onCompleted: WeatherService.addRef()
Component.onDestruction: WeatherService.removeRef()
@@ -60,10 +62,12 @@ Card {
anchors.verticalCenter: parent.verticalCenter
StyledText {
anchors.left: parent.left
text: {
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp
if (temp === undefined || temp === null) return "--°" + (SettingsData.useFahrenheit ? "F" : "C")
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C")
const temp = SettingsData.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp;
if (temp === undefined || temp === null)
return "--°" + (SettingsData.useFahrenheit ? "F" : "C");
return temp + "°" + (SettingsData.useFahrenheit ? "F" : "C");
}
font.pixelSize: Theme.fontSizeXLarge + 4
color: Theme.surfaceText
@@ -76,6 +80,7 @@ Card {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
elide: Text.ElideRight
width: parent.parent.parent.width - 48 - Theme.spacingL * 2
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -1,7 +1,6 @@
import Qt.labs.folderlistmodel
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Common
import qs.Modals.FileBrowser
@@ -311,7 +310,7 @@ Item {
showFiles: true
showDirs: false
sortField: FolderListModel.Name
folder: wallpaperDir ? "file://" + wallpaperDir : ""
folder: wallpaperDir ? "file://" + wallpaperDir.split('/').map(s => encodeURIComponent(s)).join('/') : ""
}
FileBrowserSurfaceModal {
@@ -401,7 +400,9 @@ Item {
currentIndex = clampedIndex;
positionViewAtIndex(clampedIndex, GridView.Contain);
}
Qt.callLater(() => { enableAnimation = true; });
Qt.callLater(() => {
enableAnimation = true;
});
}
Connections {

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 maxTemp: WeatherService.formatTemp(root.forecastData?.tempMax)

View File

@@ -4,7 +4,6 @@ import QtQuick.Shapes
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.DankBar.Widgets
Item {
id: root
@@ -261,7 +260,17 @@ Item {
StyledText {
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
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.left: sunriseIcon.right
@@ -285,7 +294,17 @@ Item {
StyledText {
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
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.left: sunsetIcon.right
@@ -324,14 +343,14 @@ Item {
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 {
id: dateStepperInner
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
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 {
id: dateStepperInnerPadding
@@ -420,13 +439,14 @@ Item {
}
Rectangle {
id: suffix
visible: !SettingsData.use24HourClock
anchors.verticalCenter: parent.verticalCenter
anchors.left: minuteStepper.right
anchors.leftMargin: 2 * parent.space
StyledText {
isMonospace: true
anchors.horizontalCenter: parent.horizontalCenter
text: dateStepper.splitDate[5]
text: dateStepper.splitDate[5] ?? ""
font.pixelSize: Theme.fontSizeSmall
x: -Theme.fontSizeSmall / 2
y: -Theme.fontSizeSmall / 2

View File

@@ -49,6 +49,13 @@ Item {
updateDesktopEntry();
}
}
Connections {
target: SettingsData
function onAppIdSubstitutionsChanged() {
updateDesktopEntry();
}
}
property bool isWindowFocused: {
if (!appData) {
return false;
@@ -392,20 +399,6 @@ 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 {
width: actualIconSize
height: actualIconSize

View File

@@ -15,6 +15,12 @@ import qs.Modules.Lock
Item {
id: root
function encodeFileUrl(path) {
if (!path)
return "";
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
}
readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS")
property string screenName: ""
property string randomFact: ""
@@ -162,7 +168,7 @@ Item {
var _ = SessionData.perMonitorWallpaper;
var __ = SessionData.monitorWallpapers;
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
}
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
smooth: true
@@ -356,14 +362,10 @@ Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
imageSource: {
if (PortalService.profileImage === "") {
if (PortalService.profileImage === "")
return "";
}
if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage;
}
if (PortalService.profileImage.startsWith("/"))
return encodeFileUrl(PortalService.profileImage);
return PortalService.profileImage;
}
fallbackIcon: "person"
@@ -1154,7 +1156,7 @@ Item {
required property string modelData
FolderListModel {
folder: "file://" + modelData
folder: encodeFileUrl(modelData)
nameFilters: ["*.desktop"]
showDirs: false
showDotAndDotDot: false

View File

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

View File

@@ -14,6 +14,12 @@ import qs.Widgets
Item {
id: root
function encodeFileUrl(path) {
if (!path)
return "";
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
}
property string passwordBuffer: ""
property bool demoMode: false
property string screenName: ""
@@ -28,6 +34,13 @@ Item {
signal unlockRequested
function resetLockState() {
lockerReadySent = false;
lockerReadyArmed = true;
unlocking = false;
pamState = "";
}
function pickRandomFact() {
randomFact = Facts.getRandomFact();
}
@@ -170,7 +183,7 @@ Item {
anchors.fill: parent
source: {
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
}
fillMode: Theme.getFillMode(SettingsData.wallpaperFillMode)
smooth: true
@@ -659,14 +672,10 @@ Item {
Layout.preferredWidth: 60
Layout.preferredHeight: 60
imageSource: {
if (PortalService.profileImage === "") {
if (PortalService.profileImage === "")
return "";
}
if (PortalService.profileImage.startsWith("/")) {
return "file://" + PortalService.profileImage;
}
if (PortalService.profileImage.startsWith("/"))
return encodeFileUrl(PortalService.profileImage);
return PortalService.profileImage;
}
fallbackIcon: "person"

View File

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

View File

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

View File

@@ -10,9 +10,17 @@ Rectangle {
required property var historyItem
property bool isSelected: 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
height: 116
height: baseCardHeight + contentItem.extraHeight
radius: Theme.cornerRadius
clip: true
@@ -65,23 +73,28 @@ Rectangle {
}
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.left: parent.left
anchors.right: parent.right
anchors.topMargin: 12
anchors.leftMargin: 16
anchors.rightMargin: 56
height: 92
anchors.topMargin: cardPadding
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
height: collapsedContentHeight + extraHeight
DankCircularImage {
id: iconContainer
readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== ""
width: 63
height: 63
width: iconSize
height: iconSize
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 14
imageSource: {
if (hasNotificationImage)
@@ -116,60 +129,79 @@ Rectangle {
Rectangle {
anchors.left: iconContainer.right
anchors.leftMargin: 12
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.bottomMargin: contentSpacing
color: "transparent"
Item {
Column {
width: parent.width
height: parent.height
anchors.top: parent.top
anchors.topMargin: -2
spacing: compactMode ? 1 : 2
Column {
StyledText {
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 {
width: parent.width
text: {
const timeStr = NotificationService.formatHistoryTime(historyItem.timestamp);
const appName = historyItem.appName || "";
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName;
StyledText {
text: historyItem.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
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 {
text: historyItem.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
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)
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink)
mouse.accepted = false;
}
onReleased: mouse => {
if (parent.hoveredLink)
mouse.accepted = false;
}
}
}
}
@@ -179,11 +211,11 @@ Rectangle {
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: 12
anchors.rightMargin: 16
anchors.topMargin: cardPadding
anchors.rightMargin: Theme.spacingL
iconName: "close"
iconSize: 18
buttonSize: 28
iconSize: compactMode ? 16 : 18
buttonSize: compactMode ? 24 : 28
onClicked: NotificationService.removeFromHistory(historyItem.id)
}
}

View File

@@ -83,23 +83,54 @@ Item {
}
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("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 }
{
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("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) {
const retention = SettingsData.notificationHistoryMaxAgeDays;
if (filter.key === "older") {
if (retention === 0) return true;
if (retention === 0)
return true;
return retention > 2 && retention < 7 || retention > 30;
}
if (retention === 0) return true;
if (filter.maxDays === 0) return true;
if (retention === 0)
return true;
if (filter.maxDays === 0)
return true;
return filter.maxDays <= retention;
}
@@ -119,10 +150,15 @@ Item {
const retention = SettingsData.notificationHistoryMaxAgeDays;
for (let i = 0; i < allFilters.length; i++) {
const f = allFilters[i];
if (!filterRelevantForRetention(f)) continue;
if (!filterRelevantForRetention(f))
continue;
const count = countForFilter(f.key);
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;
@@ -165,6 +201,14 @@ Item {
function enableAutoScroll() {
}
function removeWithScrollPreserve(itemId) {
historyListView.savedY = historyListView.contentY;
NotificationService.removeFromHistory(itemId);
Qt.callLater(() => {
historyListView.forceLayout();
});
}
Column {
anchors.fill: parent
spacing: Theme.spacingS
@@ -201,14 +245,66 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
}
delegate: HistoryNotificationCard {
delegate: Item {
id: delegateRoot
required property var modelData
required property int index
property real swipeOffset: 0
property bool isDismissing: false
readonly property real dismissThreshold: width * 0.35
width: ListView.view.width
historyItem: modelData
isSelected: root.keyboardActive && root.selectedIndex === index
keyboardNavigationActive: root.keyboardActive
height: historyCard.height
clip: true
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 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
height: {
if (expanded) {
return expandedContent.height + 28;
}
const baseHeight = 116;
if (descriptionExpanded) {
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2);
}
return baseHeight;
}
height: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight)
radius: Theme.cornerRadius
Behavior on border.color {
@@ -97,24 +97,28 @@ Rectangle {
Item {
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.left: parent.left
anchors.right: parent.right
anchors.topMargin: 12
anchors.leftMargin: 16
anchors.rightMargin: 56
height: 92
anchors.topMargin: cardPadding
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
height: collapsedContentHeight + extraHeight
visible: !expanded
DankCircularImage {
id: iconContainer
readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== ""
width: 63
height: 63
width: iconSize
height: iconSize
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 14
imageSource: {
if (hasNotificationImage)
@@ -147,9 +151,9 @@ Rectangle {
}
Rectangle {
width: 18
height: 18
radius: 9
width: badgeSize
height: badgeSize
radius: badgeSize / 2
color: Theme.primary
anchors.top: parent.top
anchors.right: parent.right
@@ -161,7 +165,7 @@ Rectangle {
anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText
font.pixelSize: 9
font.pixelSize: compactMode ? 8 : 9
font.weight: Font.Bold
}
}
@@ -171,87 +175,79 @@ Rectangle {
id: textContainer
anchors.left: iconContainer.right
anchors.leftMargin: 12
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.bottomMargin: contentSpacing
color: "transparent"
Item {
Column {
width: parent.width
height: parent.height
anchors.top: parent.top
anchors.topMargin: -2
spacing: compactMode ? 1 : 2
Column {
StyledText {
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 {
width: parent.width
text: {
const timeStr = (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.timeStr) || "";
const appName = (notificationGroup && notificationGroup.appName) || "";
return timeStr.length > 0 ? `${appName} ${timeStr}` : appName;
StyledText {
text: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.summary) || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
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 {
text: (notificationGroup && notificationGroup.latestNotification && notificationGroup.latestNotification.summary) || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
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 : 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;
}
}
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.left: parent.left
anchors.right: parent.right
anchors.topMargin: 14
anchors.bottomMargin: 14
anchors.topMargin: cardPadding
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
spacing: -1
spacing: compactMode ? Theme.spacingXS : Theme.spacingS
visible: expanded
Item {
width: parent.width
height: 40
height: compactMode ? 32 : 40
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 56
anchors.rightMargin: Theme.spacingL + (compactMode ? 32 : 40)
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
@@ -294,9 +289,9 @@ Rectangle {
}
Rectangle {
width: 18
height: 18
radius: 9
width: badgeSize
height: badgeSize
radius: badgeSize / 2
color: Theme.primary
visible: (notificationGroup?.count || 0) > 1
anchors.verticalCenter: parent.verticalCenter
@@ -305,7 +300,7 @@ Rectangle {
anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText
font.pixelSize: 9
font.pixelSize: compactMode ? 8 : 9
font.weight: Font.Bold
}
}
@@ -314,7 +309,7 @@ Rectangle {
Column {
width: parent.width
spacing: 16
spacing: compactMode ? Theme.spacingS : Theme.spacingL
Repeater {
id: notificationRepeater
@@ -328,23 +323,23 @@ Rectangle {
required property int index
readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false
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
height: {
const baseHeight = 120;
if (messageExpanded) {
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
if (bodyText.implicitHeight > twoLineHeight + 2) {
const extraHeight = bodyText.implicitHeight - twoLineHeight;
return baseHeight + extraHeight;
}
}
return baseHeight;
if (!messageExpanded)
return expandedBaseHeight;
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
if (bodyText.implicitHeight > twoLineHeight + 2)
return expandedBaseHeight + bodyText.implicitHeight - twoLineHeight;
return expandedBaseHeight;
}
radius: Theme.cornerRadius
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.width: isSelected ? 1 : 1
border.width: 1
Behavior on border.color {
ColorAnimation {
@@ -359,19 +354,19 @@ Rectangle {
Item {
anchors.fill: parent
anchors.margins: 12
anchors.bottomMargin: 8
anchors.margins: compactMode ? Theme.spacingS : Theme.spacingM
anchors.bottomMargin: contentSpacing
DankCircularImage {
id: messageIcon
readonly property bool hasNotificationImage: modelData?.image && modelData.image !== ""
width: 48
height: 48
width: expandedIconSize
height: expandedIconSize
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 32
anchors.topMargin: compactMode ? Theme.spacingM : Theme.spacingXL
imageSource: {
if (hasNotificationImage)
@@ -397,9 +392,9 @@ Rectangle {
Item {
anchors.left: messageIcon.right
anchors.leftMargin: 12
anchors.leftMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: 12
anchors.rightMargin: Theme.spacingM
anchors.top: parent.top
anchors.bottom: parent.bottom
@@ -408,8 +403,8 @@ Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: buttonArea.top
anchors.bottomMargin: 4
spacing: 2
anchors.bottomMargin: contentSpacing
spacing: compactMode ? 1 : 2
StyledText {
width: parent.width
@@ -477,12 +472,12 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 30
height: actionButtonHeight + contentSpacing
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 8
spacing: contentSpacing
Repeater {
model: modelData?.actions || []
@@ -490,18 +485,17 @@ Rectangle {
Rectangle {
property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50)
height: 24
radius: 4
width: Math.max(actionText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText {
id: actionText
text: {
const baseText = modelData.text || "View";
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0)) {
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
return `${baseText} (${index + 1})`;
}
return baseText;
}
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
@@ -518,9 +512,8 @@ Rectangle {
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: {
if (modelData && modelData.invoke) {
if (modelData && modelData.invoke)
modelData.invoke();
}
}
}
}
@@ -529,9 +522,9 @@ Rectangle {
Rectangle {
property bool isHovered: false
width: Math.max(clearText.implicitWidth + 12, 50)
height: 24
radius: 4
width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText {
@@ -563,11 +556,11 @@ Rectangle {
Row {
visible: !expanded
anchors.right: clearButton.left
anchors.rightMargin: 8
anchors.right: clearButton.visible ? clearButton.left : parent.right
anchors.rightMargin: clearButton.visible ? contentSpacing : Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
spacing: 8
anchors.bottomMargin: contentSpacing
spacing: contentSpacing
Repeater {
model: notificationGroup?.latestNotification?.actions || []
@@ -575,9 +568,9 @@ Rectangle {
Rectangle {
property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50)
height: 24
radius: 4
width: Math.max(actionText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText {
@@ -616,15 +609,16 @@ Rectangle {
id: clearButton
property bool isHovered: false
readonly property int actionCount: (notificationGroup?.latestNotification?.actions || []).length
visible: !expanded
visible: !expanded && actionCount < 3
anchors.right: parent.right
anchors.rightMargin: 16
anchors.rightMargin: Theme.spacingL
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
width: Math.max(clearText.implicitWidth + 12, 50)
height: 24
radius: 4
anchors.bottomMargin: contentSpacing
width: Math.max(clearText.implicitWidth + Theme.spacingM, compactMode ? 40 : 50)
height: actionButtonHeight
radius: Theme.spacingXS
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
StyledText {
@@ -660,18 +654,18 @@ Rectangle {
id: fixedControls
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: 12
anchors.rightMargin: 16
width: 60
height: 28
anchors.topMargin: cardPadding
anchors.rightMargin: Theme.spacingL
width: compactMode ? 52 : 60
height: compactMode ? 24 : 28
DankActionButton {
anchors.left: parent.left
anchors.top: parent.top
visible: (notificationGroup?.count || 0) > 1
iconName: expanded ? "expand_less" : "expand_more"
iconSize: 18
buttonSize: 28
iconSize: compactMode ? 16 : 18
buttonSize: compactMode ? 24 : 28
onClicked: {
root.userInitiatedExpansion = true;
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
@@ -682,8 +676,8 @@ Rectangle {
anchors.right: parent.right
anchors.top: parent.top
iconName: "close"
iconSize: 18
buttonSize: 28
iconSize: compactMode ? 16 : 18
buttonSize: compactMode ? 24 : 28
onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
}
}

View File

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

View File

@@ -1,4 +1,5 @@
import QtQuick
import qs.Common
import qs.Services
QtObject {
@@ -6,7 +7,12 @@ QtObject {
property var modelData
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 var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set()

View File

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

View File

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

View File

@@ -63,7 +63,13 @@ SettingsCard {
DankActionButton {
id: menuButton
iconName: "more_vert"
onClicked: actionsMenu.open()
onClicked: {
if (actionsMenu.opened) {
actionsMenu.close();
return;
}
actionsMenu.open();
}
Popup {
id: actionsMenu
@@ -71,7 +77,7 @@ SettingsCard {
y: parent.height + Theme.spacingXS
width: 160
padding: Theme.spacingXS
modal: true
modal: false
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
@@ -199,6 +205,7 @@ SettingsCard {
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: 80
horizontalAlignment: Text.AlignLeft
}
DankTextField {
@@ -217,6 +224,73 @@ SettingsCard {
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 {
text: I18n.tr("Show on Overlay")
checked: instanceData?.config?.showOnOverlay ?? false
@@ -265,6 +339,38 @@ SettingsCard {
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 {
width: parent.width
height: ipcColumn.height + Theme.spacingM * 2
@@ -280,6 +386,8 @@ SettingsCard {
text: I18n.tr("Command")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Rectangle {

View File

@@ -10,21 +10,50 @@ import qs.Modules.Settings.Widgets
Item {
id: root
property var expandedStates: ({})
property var parentModal: null
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
DesktopWidgetBrowser {
id: widgetBrowser
parentModal: root.parentModal
onWidgetAdded: widgetType => {
ToastService.showInfo(I18n.tr("Widget added"));
property var expandedStates: ({})
property var groupCollapsedStates: ({})
property var parentModal: null
property string editingGroupId: ""
property string newGroupName: ""
readonly property var allInstances: SettingsData.desktopWidgetInstances || []
readonly property var allGroups: SettingsData.desktopWidgetGroups || []
function showWidgetBrowser() {
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 {
id: desktopPluginBrowser
parentModal: root.parentModal
typeFilter: "desktop-widget"
LazyLoader {
id: desktopPluginBrowserLoader
active: false
PluginBrowser {
parentModal: root.parentModal
typeFilter: "desktop-widget"
}
}
DankFlickable {
@@ -56,6 +85,7 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}
Row {
@@ -64,72 +94,518 @@ Item {
DankButton {
text: I18n.tr("Add Widget")
iconName: "add"
onClicked: widgetBrowser.show()
onClicked: root.showWidgetBrowser()
}
DankButton {
text: I18n.tr("Browse Plugins")
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 {
id: instancesColumn
id: ungroupedSection
width: parent.width
spacing: Theme.spacingM
visible: SettingsData.desktopWidgetInstances.length > 0
visible: ungroupedInstances.length > 0
Repeater {
id: instanceRepeater
model: ScriptModel {
id: instancesModel
objectProp: "id"
values: SettingsData.desktopWidgetInstances
readonly property var ungroupedInstances: root.allInstances.filter(inst => {
if (!inst.group)
return true;
return !root.allGroups.some(g => g.id === inst.group);
})
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 {
required property var modelData
required property int index
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var states = Object.assign({}, root.groupCollapsedStates);
states["_ungrouped"] = !(states["_ungrouped"] ?? false);
root.groupCollapsedStates = states;
}
}
}
readonly property string instanceIdRef: modelData.id
readonly property var liveInstanceData: {
const instances = SettingsData.desktopWidgetInstances || [];
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
Column {
width: parent.width
spacing: Theme.spacingM
visible: !(root.groupCollapsedStates["_ungrouped"] ?? false)
leftPadding: root.allGroups.length > 0 ? Theme.spacingM : 0
Repeater {
model: ScriptModel {
objectProp: "id"
values: ungroupedSection.ungroupedInstances
}
width: instancesColumn.width
instanceData: liveInstanceData
isExpanded: root.expandedStates[instanceIdRef] ?? false
Item {
id: ungroupedDelegateItem
required property var modelData
required property int index
onExpandedChanged: {
if (expanded === (root.expandedStates[instanceIdRef] ?? false))
return;
var states = Object.assign({}, root.expandedStates);
states[instanceIdRef] = expanded;
root.expandedStates = states;
}
property bool held: ungroupedDragArea.pressed
property real originalY: y
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: {
SettingsData.removeDesktopWidgetInstance(instanceIdRef);
ToastService.showInfo(I18n.tr("Widget removed"));
width: ungroupedSection.width - (root.allGroups.length > 0 ? Theme.spacingM : 0)
height: ungroupedCard.height
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 {
visible: SettingsData.desktopWidgetInstances.length === 0
visible: root.allInstances.length === 0
text: I18n.tr("No widgets added. Click \"Add Widget\" to get started.")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
horizontalAlignment: Text.AlignLeft
}
SettingsCard {
@@ -143,6 +619,7 @@ Item {
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
@@ -162,23 +639,29 @@ Item {
Column {
spacing: 2
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 40 - Theme.spacingM
StyledText {
text: I18n.tr("Move Widget")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
text: I18n.tr("Right-click and drag anywhere on the widget")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
@@ -198,18 +681,23 @@ Item {
Column {
spacing: 2
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 40 - Theme.spacingM
StyledText {
text: I18n.tr("Resize Widget")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
text: I18n.tr("Right-click and drag the bottom-right corner")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}

View File

@@ -8,6 +8,9 @@ import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
function getBarComponentsFromSettings() {
const bars = SettingsData.barConfigs || [];
return bars.map(bar => ({
@@ -165,6 +168,8 @@ Item {
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -173,6 +178,7 @@ Item {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -194,6 +200,7 @@ Item {
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignLeft
}
Item {
@@ -270,6 +277,8 @@ Item {
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Row {
@@ -352,6 +361,8 @@ Item {
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -360,6 +371,7 @@ Item {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -373,6 +385,8 @@ Item {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Column {

View File

@@ -9,6 +9,9 @@ import qs.Widgets
Item {
id: keybindsTab
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var parentModal: null
property string selectedCategory: ""
property string searchQuery: ""
@@ -210,6 +213,8 @@ Item {
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -219,6 +224,7 @@ Item {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -327,6 +333,7 @@ Item {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}

View File

@@ -356,7 +356,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "open_in_new"
title: I18n.tr("Niri Integration")
title: I18n.tr("Niri Integration").replace("Niri", "niri")
visible: CompositorService.isNiri
SettingsToggleRow {

View File

@@ -144,6 +144,15 @@ Item {
checked: SettingsData.notificationOverlayEnabled
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 {

View File

@@ -6,6 +6,9 @@ import qs.Widgets
StyledRect {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var pluginData: null
property string expandedPluginId: ""
property bool hasUpdate: false
@@ -167,6 +170,7 @@ StyledRect {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
@@ -338,6 +342,7 @@ StyledRect {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
visible: root.pluginDescription !== ""
horizontalAlignment: Text.AlignLeft
}
Flow {

View File

@@ -1,4 +1,5 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -6,6 +7,9 @@ import qs.Widgets
FocusScope {
id: pluginsTab
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string expandedPluginId: ""
property bool isRefreshingPlugins: false
property var parentModal: null
@@ -61,18 +65,23 @@ FocusScope {
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
width: parent.width - Theme.iconSize - Theme.spacingM
StyledText {
text: I18n.tr("Plugin Management")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
text: I18n.tr("Manage and configure plugins for extending DMS functionality")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -117,6 +126,7 @@ FocusScope {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -169,6 +179,7 @@ FocusScope {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
@@ -199,7 +210,7 @@ FocusScope {
iconName: "store"
enabled: DMSService.dmsAvailable
onClicked: {
pluginBrowser.show();
showPluginBrowser();
}
}
@@ -247,6 +258,8 @@ FocusScope {
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -254,6 +267,8 @@ FocusScope {
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
font.family: "monospace"
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -262,6 +277,7 @@ FocusScope {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
}
}
}
@@ -285,6 +301,8 @@ FocusScope {
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignLeft
}
Column {
@@ -365,9 +383,11 @@ FocusScope {
Connections {
target: DMSService
function onPluginsListReceived(plugins) {
pluginBrowser.isLoading = false;
pluginBrowser.allPlugins = plugins;
pluginBrowser.updateFilteredPlugins();
if (!pluginBrowserLoader.item)
return;
pluginBrowserLoader.item.isLoading = false;
pluginBrowserLoader.item.allPlugins = plugins;
pluginBrowserLoader.item.updateFilteredPlugins();
}
function onInstalledPluginsReceived(plugins) {
var pluginMap = {};
@@ -393,22 +413,36 @@ FocusScope {
}
Component.onCompleted: {
pluginBrowser.parentModal = pluginsTab.parentModal;
if (DMSService.dmsAvailable && DMSService.apiVersion >= 8)
DMSService.listInstalled();
if (PopoutService.pendingPluginInstall)
Qt.callLater(() => pluginBrowser.show());
Qt.callLater(showPluginBrowser);
}
Connections {
target: PopoutService
function onPendingPluginInstallChanged() {
if (PopoutService.pendingPluginInstall)
pluginBrowser.show();
showPluginBrowser();
}
}
PluginBrowser {
id: pluginBrowser
LazyLoader {
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)
}
}
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)
DMSService.listInstalledThemes();
if (PopoutService.pendingThemeInstall)
Qt.callLater(() => themeBrowser.show());
Qt.callLater(() => showThemeBrowser());
templateCheckProcess.running = true;
if (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl)
checkCursorIncludeStatus();
@@ -169,7 +169,7 @@ Item {
target: PopoutService
function onPendingThemeInstallChanged() {
if (PopoutService.pendingThemeInstall)
themeBrowser.show();
showThemeBrowser();
}
}
@@ -307,7 +307,7 @@ Item {
Item {
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"
Grid {
@@ -389,7 +389,7 @@ Item {
CachingImage {
anchors.fill: parent
anchors.margins: 1
source: Theme.wallpaperPath ? "file://" + Theme.wallpaperPath : ""
imagePath: (Theme.wallpaperPath && !Theme.wallpaperPath.startsWith("#")) ? Theme.wallpaperPath : ""
fillMode: Image.PreserveAspectCrop
visible: Theme.wallpaperPath && !Theme.wallpaperPath.startsWith("#")
layer.enabled: true
@@ -939,7 +939,7 @@ Item {
text: I18n.tr("Browse Themes", "browse themes button")
iconName: "store"
anchors.horizontalCenter: parent.horizontalCenter
onClicked: themeBrowser.show()
onClicked: showThemeBrowser()
}
}
}
@@ -1055,7 +1055,7 @@ Item {
SettingsCard {
tab: "theme"
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides")
title: I18n.tr("Niri Layout Overrides").replace("Niri", "niri")
settingKey: "niriLayout"
iconName: "crop_square"
visible: CompositorService.isNiri
@@ -2041,7 +2041,18 @@ Item {
}
}
ThemeBrowser {
id: themeBrowser
LazyLoader {
id: themeBrowserLoader
active: false
ThemeBrowser {
id: themeBrowserItem
}
}
function showThemeBrowser() {
themeBrowserLoader.active = true;
if (themeBrowserLoader.item)
themeBrowserLoader.item.show();
}
}

View File

@@ -10,6 +10,9 @@ import qs.Modules.Settings.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var parentModal: null
property string selectedMonitorName: {
var screens = Quickshell.screens;
@@ -55,9 +58,9 @@ Item {
CachingImage {
anchors.fill: parent
anchors.margins: 1
source: {
imagePath: {
var currentWallpaper = SessionData.perMonitorWallpaper ? SessionData.getMonitorWallpaper(selectedMonitorName) : SessionData.wallpaperPath;
return (currentWallpaper !== "" && !currentWallpaper.startsWith("#")) ? "file://" + currentWallpaper : "";
return (currentWallpaper !== "" && !currentWallpaper.startsWith("#")) ? currentWallpaper : "";
}
fillMode: Image.PreserveAspectCrop
visible: {
@@ -136,7 +139,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: mainWallpaperBrowser.open()
onClicked: root.openMainWallpaperBrowser()
}
}
@@ -233,6 +236,7 @@ Item {
elide: Text.ElideMiddle
maximumLineCount: 1
width: parent.width
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -245,6 +249,7 @@ Item {
elide: Text.ElideMiddle
maximumLineCount: 1
width: parent.width
horizontalAlignment: Text.AlignLeft
visible: {
var currentWallpaper = SessionData.perMonitorWallpaper ? SessionData.getMonitorWallpaper(selectedMonitorName) : SessionData.wallpaperPath;
return currentWallpaper !== "";
@@ -252,7 +257,9 @@ Item {
}
Row {
anchors.left: parent.left
spacing: Theme.spacingS
layoutDirection: I18n.isRtl ? Qt.RightToLeft : Qt.LeftToRight
visible: {
var currentWallpaper = SessionData.perMonitorWallpaper ? SessionData.getMonitorWallpaper(selectedMonitorName) : SessionData.wallpaperPath;
return currentWallpaper !== "";
@@ -391,9 +398,9 @@ Item {
CachingImage {
anchors.fill: parent
anchors.margins: 1
source: {
imagePath: {
var lightWallpaper = SessionData.wallpaperPathLight;
return (lightWallpaper !== "" && !lightWallpaper.startsWith("#")) ? "file://" + lightWallpaper : "";
return (lightWallpaper !== "" && !lightWallpaper.startsWith("#")) ? lightWallpaper : "";
}
fillMode: Image.PreserveAspectCrop
visible: {
@@ -469,7 +476,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: lightWallpaperBrowser.open()
onClicked: root.openLightWallpaperBrowser()
}
}
@@ -575,9 +582,9 @@ Item {
CachingImage {
anchors.fill: parent
anchors.margins: 1
source: {
imagePath: {
var darkWallpaper = SessionData.wallpaperPathDark;
return (darkWallpaper !== "" && !darkWallpaper.startsWith("#")) ? "file://" + darkWallpaper : "";
return (darkWallpaper !== "" && !darkWallpaper.startsWith("#")) ? darkWallpaper : "";
}
fillMode: Image.PreserveAspectCrop
visible: {
@@ -653,7 +660,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: darkWallpaperBrowser.open()
onClicked: root.openDarkWallpaperBrowser()
}
}
@@ -968,17 +975,9 @@ Item {
SettingsDropdownRow {
id: intervalDropdown
property var intervalOptions: [
"5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds",
"35 seconds", "40 seconds", "45 seconds", "50 seconds", "55 seconds",
"1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours",
"3 hours", "4 hours", "6 hours", "8 hours", "12 hours"
]
property var intervalOptions: ["5 seconds", "10 seconds", "15 seconds", "20 seconds", "25 seconds", "30 seconds", "35 seconds", "40 seconds", "45 seconds", "50 seconds", "55 seconds", "1 minute", "5 minutes", "15 minutes", "30 minutes", "1 hour", "1.5 hours", "2 hours", "3 hours", "4 hours", "6 hours", "8 hours", "12 hours"]
property var intervalValues: [
5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60,
300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200
]
property var intervalValues: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 300, 900, 1800, 3600, 5400, 7200, 10800, 14400, 21600, 28800, 43200]
tab: "wallpaper"
tags: ["interval", "cycling", "time", "frequency"]
settingKey: "wallpaperCyclingInterval"
@@ -1243,53 +1242,83 @@ Item {
}
}
FileBrowserModal {
id: mainWallpaperBrowser
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);
function openMainWallpaperBrowser() {
mainWallpaperBrowserLoader.active = true;
if (mainWallpaperBrowserLoader.item)
mainWallpaperBrowserLoader.item.open();
}
function openLightWallpaperBrowser() {
lightWallpaperBrowserLoader.active = true;
if (lightWallpaperBrowserLoader.item)
lightWallpaperBrowserLoader.item.open();
}
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 {
id: lightWallpaperBrowser
parentModal: root.parentModal
browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title")
browserIcon: "light_mode"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
SessionData.wallpaperPathLight = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
LazyLoader {
id: lightWallpaperBrowserLoader
active: false
FileBrowserModal {
parentModal: root.parentModal
browserTitle: I18n.tr("Select Wallpaper", "light mode wallpaper file browser title")
browserIcon: "light_mode"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
SessionData.wallpaperPathLight = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
}
}
}
FileBrowserModal {
id: darkWallpaperBrowser
parentModal: root.parentModal
browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title")
browserIcon: "dark_mode"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
SessionData.wallpaperPathDark = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
LazyLoader {
id: darkWallpaperBrowserLoader
active: false
FileBrowserModal {
parentModal: root.parentModal
browserTitle: I18n.tr("Select Wallpaper", "dark mode wallpaper file browser title")
browserIcon: "dark_mode"
browserType: "wallpaper"
showHiddenFiles: true
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: path => {
SessionData.wallpaperPathDark = path;
SessionData.syncWallpaperForCurrentMode();
SessionData.saveSettings();
close();
}
}
}
}

View File

@@ -93,7 +93,7 @@ Item {
elide: Text.ElideRight
width: parent.width
visible: root.text !== ""
anchors.left: parent.left
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -103,7 +103,7 @@ Item {
wrapMode: Text.WordWrap
width: parent.width
visible: root.description !== ""
anchors.left: parent.left
horizontalAlignment: Text.AlignLeft
}
}

View File

@@ -19,6 +19,7 @@ StyledRect {
property string iconName: ""
property bool collapsible: false
property bool expanded: true
property real headerLeftPadding: 0
default property alias content: contentColumn.children
property alias headerActions: headerActionsRow.children
@@ -115,6 +116,7 @@ StyledRect {
Row {
anchors.left: parent.left
anchors.leftMargin: root.headerLeftPadding
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
@@ -135,12 +137,14 @@ StyledRect {
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: root.title !== ""
width: implicitWidth
horizontalAlignment: Text.AlignLeft
}
}
Row {
id: headerActionsRow
anchors.right: caretIcon.left
anchors.right: root.collapsible ? caretIcon.left : parent.right
anchors.rightMargin: root.collapsible ? Theme.spacingS : 0
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
@@ -170,6 +174,7 @@ StyledRect {
}
MouseArea {
visible: root.collapsible
anchors.left: caretIcon.left
anchors.right: parent.right
anchors.top: parent.top

View File

@@ -8,6 +8,9 @@ import qs.Widgets
DankDropdown {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string tab: ""
property var tags: []
property string settingKey: ""

View File

@@ -7,6 +7,9 @@ import qs.Widgets
StyledRect {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string tab: ""
property var tags: []
@@ -67,6 +70,7 @@ StyledRect {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
visible: root.description !== ""
}
}

View File

@@ -96,7 +96,7 @@ Item {
color: Theme.surfaceText
visible: root.text !== ""
width: parent.width
anchors.left: parent.left
horizontalAlignment: Text.AlignLeft
}
StyledText {
@@ -106,7 +106,7 @@ Item {
wrapMode: Text.WordWrap
width: parent.width
visible: root.description !== ""
anchors.left: parent.left
horizontalAlignment: Text.AlignLeft
}
}

View File

@@ -7,6 +7,9 @@ import qs.Widgets
StyledRect {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string tab: ""
property var tags: []
@@ -76,6 +79,7 @@ StyledRect {
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignLeft
visible: root.description !== ""
}
}

View File

@@ -8,6 +8,9 @@ import qs.Widgets
DankToggle {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property string tab: ""
property var tags: []
property string settingKey: ""

View File

@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -916,14 +917,28 @@ Item {
});
}
WidgetSelectionPopup {
id: widgetSelectionPopup
parentModal: widgetsTab.parentModal
onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId, targetSection);
LazyLoader {
id: widgetSelectionPopupLoader
active: false
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 {
anchors.fill: parent
clip: true
@@ -1113,9 +1128,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder);
}
onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
showWidgetSelectionPopup(sectionId);
}
onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index);
@@ -1170,9 +1183,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder);
}
onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
showWidgetSelectionPopup(sectionId);
}
onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index);
@@ -1227,9 +1238,7 @@ Item {
widgetsTab.handleItemOrderChanged(sectionId, newOrder);
}
onAddWidget: sectionId => {
widgetSelectionPopup.targetSection = sectionId;
widgetSelectionPopup.allWidgets = widgetsTab.getWidgetsForPopup();
widgetSelectionPopup.show();
showWidgetSelectionPopup(sectionId);
}
onRemoveWidget: (sectionId, index) => {
widgetsTab.removeWidgetFromSection(sectionId, index);

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