1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00

feat: add workspace rename dialog (#1429)

* feat: add workspace rename dialog

- Adds a modal dialog to rename the current workspace
- Supports both Niri (via IPC socket) and Hyprland (via hyprctl dispatch)
- Default keybinding: Ctrl+Shift+R to open the dialog
- Pre-fills with current workspace name
- Allows setting empty name to reset to default

* refactor: wrap WorkspaceRenameModal in LazyLoader

Reduces memory footprint when the modal is not in use.
This commit is contained in:
Kamil Chmielewski
2026-01-23 19:46:34 +01:00
committed by GitHub
parent 775b381987
commit b3ea28c5c4
9 changed files with 321 additions and 1 deletions

View File

@@ -91,6 +91,9 @@ bind = SUPER CTRL, up, movetoworkspace, e-1
bind = SUPER CTRL, U, movetoworkspace, e+1 bind = SUPER CTRL, U, movetoworkspace, e+1
bind = SUPER CTRL, I, movetoworkspace, e-1 bind = SUPER CTRL, I, movetoworkspace, e-1
# === Workspace Management ===
bind = CTRL SHIFT, R, exec, dms ipc call workspace-rename open
# === Move Workspaces === # === Move Workspaces ===
bind = SUPER SHIFT, Page_Down, movetoworkspace, e+1 bind = SUPER SHIFT, Page_Down, movetoworkspace, e+1
bind = SUPER SHIFT, Page_Up, movetoworkspace, e-1 bind = SUPER SHIFT, Page_Up, movetoworkspace, e-1

View File

@@ -133,6 +133,11 @@ binds {
Mod+Ctrl+U { move-column-to-workspace-down; } Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; } Mod+Ctrl+I { move-column-to-workspace-up; }
// === Workspace Management ===
Ctrl+Shift+R hotkey-overlay-title="Rename Workspace" {
spawn "dms" "ipc" "call" "workspace-rename" "open";
}
// === Move Workspaces === // === Move Workspaces ===
Mod+Shift+Page_Down { move-workspace-down; } Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; } Mod+Shift+Page_Up { move-workspace-up; }

View File

@@ -100,7 +100,8 @@ const DMS_ACTIONS = [
{ id: "spawn dms ipc call hypr openOverview", label: "Hyprland: Open Overview", compositor: "hyprland" }, { id: "spawn dms ipc call hypr openOverview", label: "Hyprland: Open Overview", compositor: "hyprland" },
{ id: "spawn dms ipc call hypr closeOverview", label: "Hyprland: Close Overview", compositor: "hyprland" }, { id: "spawn dms ipc call hypr closeOverview", label: "Hyprland: Close Overview", compositor: "hyprland" },
{ id: "spawn dms ipc call wallpaper next", label: "Wallpaper: Next" }, { id: "spawn dms ipc call wallpaper next", label: "Wallpaper: Next" },
{ id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" } { id: "spawn dms ipc call wallpaper prev", label: "Wallpaper: Previous" },
{ id: "spawn dms ipc call workspace-rename open", label: "Workspace: Rename" }
]; ];
const NIRI_ACTIONS = { const NIRI_ACTIONS = {

View File

@@ -644,6 +644,18 @@ Item {
} }
} }
LazyLoader {
id: workspaceRenameModalLoader
active: false
Component.onCompleted: PopoutService.workspaceRenameModalLoader = workspaceRenameModalLoader
WorkspaceRenameModal {
id: workspaceRenameModal
}
}
LazyLoader { LazyLoader {
id: processListModalLoader id: processListModalLoader

View File

@@ -1292,4 +1292,41 @@ Item {
target: "desktopWidget" target: "desktopWidget"
} }
IpcHandler {
function open(): string {
if (!workspaceRenameModalLoader || !workspaceRenameModalLoader.item) {
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
workspaceRenameModalLoader.active = true;
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
workspaceRenameModalLoader.item.show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
function close(): string {
if (!workspaceRenameModalLoader || !workspaceRenameModalLoader.item) {
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
workspaceRenameModalLoader.item.hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
}
function toggle(): string {
if (!workspaceRenameModalLoader || !workspaceRenameModalLoader.item) {
return "WORKSPACE_RENAME_MODAL_NOT_FOUND";
}
if (workspaceRenameModalLoader.item.shouldBeVisible) {
workspaceRenameModalLoader.item.hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
} else {
workspaceRenameModalLoader.active = true;
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
workspaceRenameModalLoader.item.show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
}
target: "workspace-rename"
}
} }

View File

@@ -0,0 +1,232 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
FloatingWindow {
id: root
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
objectName: "workspaceRenameModal"
title: I18n.tr("Rename Workspace")
minimumSize: Qt.size(400, 180)
maximumSize: Qt.size(400, 180)
color: Theme.surfaceContainer
visible: false
function show(name) {
nameInput.text = name;
visible = true;
Qt.callLater(() => nameInput.forceActiveFocus());
}
function hide() {
visible = false;
}
function submitAndClose() {
renameWorkspace(nameInput.text);
hide();
}
function renameWorkspace(name) {
if (CompositorService.isNiri) {
NiriService.renameWorkspace(name);
} else if (CompositorService.isHyprland) {
HyprlandService.renameWorkspace(name);
} else {
console.warn("WorkspaceRenameModal: rename not supported for this compositor");
}
}
onVisibleChanged: {
if (visible) {
Qt.callLater(() => nameInput.forceActiveFocus());
}
}
FocusScope {
id: contentFocusScope
anchors.fill: parent
focus: true
Keys.onEscapePressed: {
hide();
event.accepted = true;
}
Keys.onReturnPressed: {
submitAndClose();
event.accepted = true;
}
Column {
id: contentCol
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingM
Item {
width: parent.width
height: Math.max(headerCol.height, buttonRow.height)
MouseArea {
anchors.fill: parent
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
Column {
id: headerCol
width: parent.width
StyledText {
text: I18n.tr("Enter a new name for this workspace")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
width: parent.width
}
}
}
Row {
id: buttonRow
anchors.right: parent.right
spacing: Theme.spacingXS
DankActionButton {
visible: windowControls.supported && windowControls.canMaximize
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: windowControls.tryToggleMaximize()
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: hide()
}
}
}
Rectangle {
width: parent.width
height: inputFieldHeight
radius: Theme.cornerRadius
color: Theme.surfaceHover
border.color: nameInput.activeFocus ? Theme.primary : Theme.outlineStrong
border.width: nameInput.activeFocus ? 2 : 1
MouseArea {
anchors.fill: parent
onClicked: nameInput.forceActiveFocus()
}
DankTextField {
id: nameInput
anchors.fill: parent
font.pixelSize: Theme.fontSizeMedium
textColor: Theme.surfaceText
placeholderText: I18n.tr("Workspace name")
backgroundColor: "transparent"
enabled: root.visible
onAccepted: submitAndClose()
}
}
Item {
width: parent.width
height: 40
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
border.color: Theme.surfaceVariantAlpha
border.width: 1
StyledText {
id: cancelText
anchors.centerIn: parent
text: I18n.tr("Cancel")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: hide()
}
}
Rectangle {
width: Math.max(80, renameText.contentWidth + Theme.spacingM * 2)
height: 36
radius: Theme.cornerRadius
color: renameArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
StyledText {
id: renameText
anchors.centerIn: parent
text: I18n.tr("Rename")
font.pixelSize: Theme.fontSizeMedium
color: Theme.background
font.weight: Font.Medium
}
MouseArea {
id: renameArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: submitAndClose()
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
FloatingWindowControls {
id: windowControls
targetWindow: root
}
IpcHandler {
target: "workspace-rename"
function open(): string {
const ws = NiriService.workspaces[NiriService.focusedWorkspaceId];
show(ws?.name || "");
return "WORKSPACE_RENAME_MODAL_OPENED";
}
function close(): string {
hide();
return "WORKSPACE_RENAME_MODAL_CLOSED";
}
}
}

View File

@@ -737,4 +737,10 @@ Singleton {
if (callback) callback(response); if (callback) callback(response);
}); });
} }
function renameWorkspace(name, callback) {
sendRequest("extworkspace.renameWorkspace", {
"name": name
}, callback);
}
} }

View File

@@ -308,4 +308,18 @@ decoration {
reloadConfig(); reloadConfig();
}); });
} }
function renameWorkspace(newName) {
if (!Hyprland.focusedWorkspace)
return;
const wsId = Hyprland.focusedWorkspace.id;
if (!wsId)
return;
const fullName = wsId + " " + newName;
Proc.runCommand("hyprland-rename-ws", ["hyprctl", "dispatch", "renameworkspace", String(wsId), fullName], (output, exitCode) => {
if (exitCode !== 0) {
console.warn("HyprlandService: Failed to rename workspace:", output);
}
});
}
} }

View File

@@ -1422,6 +1422,16 @@ Singleton {
return block; return block;
} }
function renameWorkspace(name) {
return send({
"Action": {
"SetWorkspaceName": {
"name": name
}
}
});
}
IpcHandler { IpcHandler {
function screenshot(): string { function screenshot(): string {
if (!CompositorService.isNiri) { if (!CompositorService.isNiri) {