Files
DankMaterialShell/quickshell/Modals/WorkspaceRenameModal.qml
Thomas Kroll 6166d51c7c fix(workspace): ignore duplicate submit in rename modal (#3549)
Pressing Enter in the name field fires both the text field's accepted
signal and the dialog's own Return handler, so submitAndClose() runs
twice. The first call renames and hides the window, which clears the
field; the second call then renames to an empty string and unsets the
workspace name. The OK button only fires once, so it was unaffected.

Ignore submits once the window is hidden.

Signed-off-by: Thomas Kroll <[email protected]>
(cherry picked from commit eb0cb80884)
2026-10-07 12:58:37 -04:00

256 lines
8.5 KiB
QML

import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
DankFloatingWindow {
id: root
readonly property var log: Log.scoped("WorkspaceRenameModal")
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
property var aqueousWorkspace: null
property bool renaming: false
objectName: "workspaceRenameModal"
title: I18n.tr("Rename Workspace")
minimumSize: Qt.size(400, 160)
maximumSize: Qt.size(400, 160)
visible: false
onClosed: hide()
function show(name) {
if (CompositorService.isAqueous) {
if (renaming)
return false;
const workspace = AqueousService.workspaces.find(ws => ws.output === AqueousService.seat?.output && ws.active);
if (!AqueousService.available || !AqueousService.seat || !workspace || !AqueousService.capabilities.commands || AqueousService.locked) {
ToastService.showError(I18n.tr("Error"), I18n.tr("Unavailable"));
return false;
}
aqueousWorkspace = {
id: workspace.id,
session: workspace.aqueousSession
};
name = workspace.name;
}
nameInput.text = name;
visible = true;
Qt.callLater(() => nameInput.forceActiveFocus());
return true;
}
function hide() {
visible = false;
}
function submitAndClose() {
if (!root.visible)
return;
if (aqueousWorkspace || CompositorService.isAqueous) {
if (!aqueousWorkspace || renaming)
return;
const target = aqueousWorkspace;
renaming = true;
AqueousService.command("workspace.rename", {
id: target.id,
session: target.session,
name: nameInput.text
}, success => {
if (root.aqueousWorkspace !== target)
return;
root.renaming = false;
if (success)
root.hide();
});
return;
}
renameWorkspace(nameInput.text);
hide();
}
function renameWorkspace(name) {
if (CompositorService.isNiri) {
NiriService.renameWorkspace(name);
} else if (CompositorService.isHyprland) {
HyprlandService.renameWorkspace(name);
} else {
log.warn("rename not supported for this compositor");
}
}
onVisibleChanged: {
if (visible) {
Qt.callLater(() => nameInput.forceActiveFocus());
return;
}
nameInput.text = "";
aqueousWorkspace = null;
renaming = false;
}
FocusScope {
id: contentFocusScope
anchors.fill: parent
focus: true
Keys.onEscapePressed: event => {
hide();
event.accepted = true;
}
Column {
id: contentCol
anchors.centerIn: parent
width: parent.width - Theme.spacingL * 2
spacing: Theme.spacingM
Item {
width: contentCol.width
height: Math.max(headerText.height, buttonRow.height)
MouseArea {
anchors.left: parent.left
anchors.right: buttonRow.left
anchors.rightMargin: Theme.spacingM
height: parent.height
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
}
StyledText {
id: headerText
text: I18n.tr("Enter a new name for this workspace")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
width: parent.width - buttonRow.width - Theme.spacingM
}
Row {
id: buttonRow
anchors.right: parent.right
spacing: Theme.spacingXS
DankActionButton {
visible: 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 && !root.renaming
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 : Theme.withAlpha(Theme.surfaceTextHover, 0)
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
enabled: !root.renaming
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
}
}