import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Widgets import qs.Widgets import qs.Common import qs.Services // No external details import; content inlined for consistency DankPopout { id: root property string triggerSection: "right" property var triggerScreen: null function setTriggerPosition(x, y, width, section, screen) { triggerX = x triggerY = y triggerWidth = width triggerSection = section triggerScreen = screen } popupWidth: 360 popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 260 triggerX: Screen.width - 380 - Theme.spacingL triggerY: Theme.barHeight - 4 + SettingsData.topBarSpacing + Theme.spacingS triggerWidth: 70 positioning: "center" WlrLayershell.namespace: "quickshell-vpn" screen: triggerScreen shouldBeVisible: false visible: shouldBeVisible content: Component { Rectangle { id: content implicitHeight: contentColumn.height + Theme.spacingL * 2 color: Theme.popupBackground() radius: Theme.cornerRadius border.color: Theme.outlineMedium border.width: 1 antialiasing: true smooth: true focus: true Keys.onPressed: function (event) { if (event.key === Qt.Key_Escape) { root.close() event.accepted = true } } // Outer subtle shadow rings to match BatteryPopout Rectangle { anchors.fill: parent anchors.margins: -3 color: "transparent" radius: parent.radius + 3 border.color: Qt.rgba(0, 0, 0, 0.05) border.width: 1 z: -3 } Rectangle { anchors.fill: parent anchors.margins: -2 color: "transparent" radius: parent.radius + 2 border.color: Theme.shadowMedium border.width: 1 z: -2 } Rectangle { anchors.fill: parent color: "transparent" border.color: Theme.outlineStrong border.width: 1 radius: parent.radius z: -1 } Column { id: contentColumn anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: Theme.spacingL spacing: Theme.spacingM Item { width: parent.width height: 32 StyledText { text: "VPN Connections" font.pixelSize: Theme.fontSizeLarge color: Theme.surfaceText font.weight: Font.Medium anchors.verticalCenter: parent.verticalCenter } // Close button (matches BatteryPopout) Rectangle { width: 32 height: 32 radius: 16 color: closeArea.containsMouse ? Theme.errorHover : "transparent" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter DankIcon { anchors.centerIn: parent name: "close" size: Theme.iconSize - 4 color: closeArea.containsMouse ? Theme.error : Theme.surfaceText } MouseArea { id: closeArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onPressed: root.close() } } } // Inlined VPN details Rectangle { id: vpnDetail width: parent.width implicitHeight: detailsColumn.implicitHeight + Theme.spacingM * 2 radius: Theme.cornerRadius color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, Theme.getContentBackgroundAlpha() * 0.6) border.color: Theme.outlineStrong border.width: 1 Column { id: detailsColumn anchors.fill: parent anchors.margins: Theme.spacingM spacing: Theme.spacingS RowLayout { spacing: Theme.spacingS width: parent.width StyledText { text: VpnService.connected ? ("Active: " + (VpnService.activeName || "VPN")) : "Active: None" font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceText font.weight: Font.Medium } Item { Layout.fillWidth: true; height: 1 } // Quick connect when not connected // Rectangle { // height: 28 // radius: 14 // color: quickBtnArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight // visible: !VpnService.connected && VpnService.profiles.length > 0 // width: 120 // Layout.alignment: Qt.AlignVCenter | Qt.AlignRight // border.width: 1 // border.color: Theme.outlineLight // // Row { // anchors.centerIn: parent // spacing: Theme.spacingXS // DankIcon { name: "link"; size: Theme.fontSizeSmall; color: Theme.surfaceText } // StyledText { text: "Connect"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceText; font.weight: Font.Medium } // } // // MouseArea { // id: quickBtnArea // anchors.fill: parent // hoverEnabled: true // cursorShape: Qt.PointingHandCursor // onClicked: VpnService.toggle() // } // } } Rectangle { height: 1; width: parent.width; color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) } DankFlickable { width: parent.width height: 160 contentHeight: listCol.height Column { id: listCol width: parent.width spacing: Theme.spacingXS Item { width: parent.width height: VpnService.profiles.length === 0 ? 120 : 0 visible: height > 0 Column { anchors.centerIn: parent spacing: Theme.spacingS DankIcon { name: "playlist_remove"; size: 36; color: Theme.surfaceVariantText; anchors.horizontalCenter: parent.horizontalCenter } StyledText { text: "No VPN profiles found"; font.pixelSize: Theme.fontSizeMedium; color: Theme.surfaceVariantText; anchors.horizontalCenter: parent.horizontalCenter } StyledText { text: "Add a VPN in NetworkManager"; font.pixelSize: Theme.fontSizeSmall; color: Theme.surfaceVariantText; anchors.horizontalCenter: parent.horizontalCenter } } } Repeater { model: VpnService.profiles delegate: Rectangle { required property var modelData width: parent ? parent.width : 300 height: 50 radius: Theme.cornerRadius color: rowArea.containsMouse ? Theme.primaryHoverLight : (modelData.uuid === VpnService.activeUuid ? Theme.primaryPressed : Theme.surfaceLight) border.width: modelData.uuid === VpnService.activeUuid ? 2 : 1 border.color: modelData.uuid === VpnService.activeUuid ? Theme.primary : Theme.outlineLight RowLayout { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.margins: Theme.spacingM spacing: Theme.spacingS DankIcon { name: modelData.uuid === VpnService.activeUuid ? "vpn_lock" : "vpn_key_off" size: Theme.iconSize - 4 color: modelData.uuid === VpnService.activeUuid ? Theme.primary : Theme.surfaceText Layout.alignment: Qt.AlignVCenter } StyledText { text: modelData.name font.pixelSize: Theme.fontSizeMedium color: modelData.uuid === VpnService.activeUuid ? Theme.primary : Theme.surfaceText Layout.alignment: Qt.AlignVCenter } Item { Layout.fillWidth: true; height: 1 } } MouseArea { id: rowArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { if (modelData.uuid === VpnService.activeUuid) { VpnService.disconnect(modelData.uuid) } else { VpnService.connect(modelData.uuid) } } } } } Item { height: 1; width: 1 } } } } } } } } }