diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg new file mode 100644 index 000000000..e639be9bf --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg new file mode 100644 index 000000000..b916cb4aa --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg new file mode 100644 index 000000000..af0ed6eaa --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg new file mode 100644 index 000000000..cc53bbc8c --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg new file mode 100644 index 000000000..4709d3d1e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg new file mode 100644 index 000000000..023ae50bd --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml b/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml new file mode 100644 index 000000000..e4c6ef725 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml @@ -0,0 +1,44 @@ +pragma ComponentBehavior: Bound +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Wayland + +Scope { + id: root + required property Component contentComponent + + Loader { + active: PolkitService.active + sourceComponent: Variants { + model: Quickshell.screens + delegate: PanelWindow { + id: panelWindow + required property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + bottom: true + } + + color: "transparent" + WlrLayershell.namespace: "quickshell:polkit" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + + Loader { + anchors.fill: parent + sourceComponent: root.contentComponent + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml b/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml index e1c54a42f..50f04f675 100644 --- a/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml +++ b/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml @@ -6,37 +6,10 @@ import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland -Scope { +FullscreenPolkitWindow { id: root - - Loader { - active: PolkitService.active - sourceComponent: Variants { - model: Quickshell.screens - delegate: PanelWindow { - id: panelWindow - required property var modelData - screen: modelData - - anchors { - top: true - left: true - right: true - bottom: true - } - - color: "transparent" - WlrLayershell.namespace: "quickshell:polkit" - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - WlrLayershell.layer: WlrLayer.Overlay - exclusionMode: ExclusionMode.Ignore - - PolkitContent { - anchors.fill: parent - } - } - } + contentComponent: Component { + PolkitContent {} } } diff --git a/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml b/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml index baef7f0b5..a78c9ce93 100644 --- a/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml +++ b/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml @@ -66,12 +66,7 @@ Item { WindowDialogParagraph { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft - text: { - if (!PolkitService.flow) return; - return PolkitService.flow.message.endsWith(".") - ? PolkitService.flow.message.slice(0, -1) - : PolkitService.flow.message - } + text: PolkitService.cleanMessage } MaterialTextField { @@ -79,11 +74,7 @@ Item { Layout.fillWidth: true focus: true enabled: PolkitService.interactionAvailable - placeholderText: { - const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? ""; - const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt; - return cleanedInputPrompt || (root.usePasswordChars ? Translation.tr("Password") : Translation.tr("Input")) - } + placeholderText: PolkitService.cleanPrompt echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal onAccepted: root.submit(); diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml index 9f114609f..764d91ca7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml @@ -67,7 +67,7 @@ Button { } } - CloseButton { + WindowCloseButton { id: closeButton } } @@ -91,46 +91,14 @@ Button { } } - component CloseButton: Button { - id: reusableCloseButton + component WindowCloseButton: CloseButton { visible: root.hovered Layout.leftMargin: 4 implicitHeight: 30 implicitWidth: 30 + radius: Looks.radius.large - root.padding onClicked: { root.toplevel.close(); } - - Rectangle { - z: 0 - color: "transparent" - anchors.fill: closeButtonBg - anchors.margins: -1 - opacity: closeButtonBg.opacity - border.width: 1 - radius: closeButtonBg.radius + 1 - border.color: Looks.colors.bg2Border - } - - background: Rectangle { - id: closeButtonBg - z: 1 - opacity: reusableCloseButton.hovered ? 1 : 0 - radius: Looks.radius.large - root.padding - color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger - Behavior on opacity { - animation: Looks.transition.opacity.createObject(this) - } - Behavior on color { - animation: Looks.transition.color.createObject(this) - } - } - - contentItem: FluentIcon { - z: 2 - anchors.centerIn: parent - icon: "dismiss" - implicitSize: 10 - } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml new file mode 100644 index 000000000..3345d0cc8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks +import qs.modules.waffle.bar +import Quickshell + +Button { + id: reusableCloseButton + implicitHeight: 30 + implicitWidth: 30 + property alias radius: closeButtonBg.radius + + Rectangle { + z: 0 + color: "transparent" + anchors.fill: closeButtonBg + anchors.margins: -1 + opacity: closeButtonBg.opacity + border.width: 1 + radius: closeButtonBg.radius + 1 + border.color: Looks.colors.bg2Border + } + + background: Rectangle { + id: closeButtonBg + z: 1 + opacity: reusableCloseButton.hovered ? 1 : 0 + color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + Behavior on color { + animation: Looks.transition.color.createObject(this) + } + } + + contentItem: FluentIcon { + z: 2 + anchors.centerIn: parent + icon: "dismiss" + implicitSize: 10 + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index b018b0629..b0d8b91f8 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -101,7 +101,7 @@ Singleton { property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency) property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency) property color bg1Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, root.contentTransparency) - property color bg2Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base, root.backgroundTransparency) + property color bg2Base: root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base property color bg2: ColorUtils.transparentize(root.dark ? root.darkColors.bg2 : root.lightColors.bg2, root.contentTransparency) property color bg2Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, root.contentTransparency) property color bg2Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, root.contentTransparency) @@ -147,6 +147,7 @@ Singleton { property int regular: Font.Medium property int strong: Font.DemiBold property int stronger: (Font.DemiBold + 2*Font.Bold) / 3 + property int strongest: Font.Bold } property QtObject pixelSize: QtObject { property real normal: 11 diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml index 6f71c65bb..bd0f2fce4 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml @@ -17,4 +17,6 @@ Kirigami.Icon { roundToIconSize: false fallback: root.iconName source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback + + color: Looks.colors.fg } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml index 0da156893..b9bc558d7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml @@ -8,7 +8,7 @@ Text { color: Looks.colors.fg font { - hintingPreference: Font.PreferFullHinting + hintingPreference: Font.PreferDefaultHinting family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.normal weight: Looks.font.weight.regular diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml new file mode 100644 index 000000000..a666cec50 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml @@ -0,0 +1,27 @@ +import qs.modules.common +import QtQuick +import QtQuick.Controls.FluentWinUI3 +import QtQuick.Controls + +TextField { + id: root + + clip: true + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + color: Looks.colors.fg + + font { + hintingPreference: Font.PreferDefaultHinting + family: Looks.font.family.ui + pixelSize: Looks.font.pixelSize.normal + weight: Looks.font.weight.regular + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: true + cursorShape: Qt.IBeamCursor + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml b/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml new file mode 100644 index 000000000..1218efb69 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml @@ -0,0 +1,208 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Rectangle { + id: root + + color: "#000000" + readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true + + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + PolkitService.cancel(); + } + } + + StyledImage { + anchors.fill: parent + source: Config.options.background.wallpaperPath + fillMode: Image.PreserveAspectCrop + + Rectangle { + anchors.fill: parent + color: ColorUtils.transparentize("#000000", 0.31) + + PolkitDialog { + id: dialog + DragHandler { + target: null + property real startX: dialog.x + property real startY: dialog.y + onActiveChanged: { + if (!active) return; + startX = dialog.x; + startY = dialog.y; + } + xAxis.onActiveValueChanged: { + dialog.x = Math.round(startX + xAxis.activeValue); + } + yAxis.onActiveValueChanged: { + dialog.y = Math.round(startY + yAxis.activeValue); + } + } + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) + } + } + } + + component PolkitDialog: WPane { + borderColor: Looks.colors.ambientShadow + + contentItem: WPanelPageColumn { + PolkitDialogHeader { + Layout.fillWidth: true + } + BodyRectangle { + id: dialogBody + implicitHeight: bodyContent.implicitHeight + 48 + implicitWidth: 434 + color: Looks.colors.bg1Base + + ColumnLayout { + id: bodyContent + anchors.fill: parent + anchors.margins: 24 + spacing: 20 + + RowLayout { + Layout.fillWidth: true + spacing: 15 + + WAppIcon { + iconName: PolkitService.flow?.iconName ?? "window-shield" + fallback: PolkitService.flow?.iconName == "" ? `${Looks.iconsPath}/window-shield` : PolkitService.flow.iconName + isMask: PolkitService.flow?.iconName === "" + tryCustomIcon: false + } + WText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Looks.font.pixelSize.larger + font.weight: Looks.font.weight.strongest + text: { + const iconName = PolkitService.flow?.iconName ?? ""; + if (iconName === "") + return Translation.tr("Command-line-invoked Action"); + const desktopEntry = DesktopEntries.applications.values.find(entry => { + return entry.icon == iconName; + }); + return desktopEntry ? desktopEntry.name : Translation.tr("Unknown Application"); + } + } + } + + WText { + Layout.fillWidth: true + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + text: PolkitService.cleanMessage + } + + WTextField { + id: inputField + Layout.fillWidth: true + focus: true + enabled: PolkitService.interactionAvailable + placeholderText: PolkitService.cleanPrompt + echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal + onAccepted: PolkitService.submit(inputField.text) + + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + PolkitService.cancel(); + } + } + + Component.onCompleted: forceActiveFocus() + Connections { + target: PolkitService + function onInteractionAvailableChanged() { + if (!PolkitService.interactionAvailable) + return; + inputField.text = ""; + inputField.forceActiveFocus(); + } + } + } + } + } + BodyRectangle { + implicitHeight: 80 + color: Looks.colors.bgPanelFooterBase + RowLayout { + anchors.fill: parent + anchors.margins: 24 + spacing: 8 + uniformCellSizes: true + + WButton { + Layout.fillWidth: true + implicitHeight: 32 + colBackground: Looks.colors.bg1 + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Yes") + onClicked: PolkitService.submit(inputField.text) + } + WButton { + Layout.fillWidth: true + implicitHeight: 32 + horizontalAlignment: Text.AlignHCenter + checked: true + text: Translation.tr("No") + onClicked: PolkitService.cancel() + } + } + } + } + } + + component PolkitDialogHeader: BodyRectangle { + implicitHeight: headerContent.implicitHeight + color: Looks.colors.bg2Base + + CloseButton { + anchors { + top: parent.top + right: parent.right + } + radius: 0 + implicitWidth: 32 + implicitHeight: 32 + + onClicked: { + PolkitService.cancel(); + } + } + + ColumnLayout { + id: headerContent + anchors.fill: parent + anchors.leftMargin: 24 + anchors.rightMargin: 24 + spacing: 18 + + WText { + Layout.topMargin: 20 + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + text: Translation.tr("Polkit") + } + WText { + Layout.fillWidth: true + Layout.bottomMargin: 12 + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + text: Translation.tr("Do you want to allow this app to make changes to your device?") + font.pixelSize: Looks.font.pixelSize.xlarger + font.weight: Looks.font.weight.strongest + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml b/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml new file mode 100644 index 000000000..8aa7d5672 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml @@ -0,0 +1,15 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Wayland + +FullscreenPolkitWindow { + id: root + contentComponent: Component { + WPolkitContent {} + } +} diff --git a/dots/.config/quickshell/ii/services/PolkitService.qml b/dots/.config/quickshell/ii/services/PolkitService.qml index 56576f4f4..758f0b0b8 100644 --- a/dots/.config/quickshell/ii/services/PolkitService.qml +++ b/dots/.config/quickshell/ii/services/PolkitService.qml @@ -11,6 +11,18 @@ Singleton { property alias active: polkitAgent.isActive property alias flow: polkitAgent.flow property bool interactionAvailable: false + property string cleanMessage: { + if (!root.flow) return ""; + return root.flow.message.endsWith(".") + ? root.flow.message.slice(0, -1) + : root.flow.message + } + property string cleanPrompt: { + const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? ""; + const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt; + const usePasswordChars = !PolkitService.flow?.responseVisible ?? true + return cleanedInputPrompt || (usePasswordChars ? Translation.tr("Password") : Translation.tr("Input")) + } function cancel() { root.flow.cancelAuthenticationRequest() diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index 55dc7bce9..eebd15e22 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -32,6 +32,7 @@ import qs.modules.waffle.background import qs.modules.waffle.bar import qs.modules.waffle.notificationCenter import qs.modules.waffle.onScreenDisplay +import qs.modules.waffle.polkit import qs.modules.waffle.startMenu import qs.modules.waffle.sessionScreen @@ -84,6 +85,7 @@ ShellRoot { PanelLoader { identifier: "wBackground"; component: WaffleBackground {} } PanelLoader { identifier: "wNotificationCenter"; component: WaffleNotificationCenter {} } PanelLoader { identifier: "wOnScreenDisplay"; component: WaffleOSD {} } + PanelLoader { identifier: "wPolkit"; component: WafflePolkit {} } PanelLoader { identifier: "wStartMenu"; component: WaffleStartMenu {} } PanelLoader { identifier: "wSessionScreen"; component: WaffleSessionScreen {} } ReloadPopup {} @@ -98,7 +100,7 @@ ShellRoot { property list families: ["ii", "waffle"] property var panelFamilies: ({ "ii": ["iiBar", "iiBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiScreenCorners", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiVerticalBar", "iiWallpaperSelector"], - "waffle": ["wActionCenter", "wBar", "wBackground", "wNotificationCenter", "wOnScreenDisplay", "wStartMenu", "iiCheatsheet", "iiLock", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiPolkit", "iiRegionSelector", "wSessionScreen", "iiWallpaperSelector"], + "waffle": ["wActionCenter", "wBar", "wBackground", "wNotificationCenter", "wOnScreenDisplay", "wPolkit", "wStartMenu", "iiCheatsheet", "iiLock", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiRegionSelector", "wSessionScreen", "iiWallpaperSelector"], }) function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily)