Compare commits
10 Commits
features/a
...
714fadc997
| Author | SHA1 | Date | |
|---|---|---|---|
| 714fadc997 | |||
| 99e2225b51 | |||
| 4aaa1d2d98 | |||
| 14b7e748d4 | |||
| a0b9ea29fe | |||
| 007843db6e | |||
| 764652d826 | |||
| 8dedfe30fa | |||
| bc54a4b5b7 | |||
| d306270dc8 |
@@ -14,6 +14,9 @@ hl.on("hyprland.start", function()
|
|||||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
|
|
||||||
|
-- Sunshine
|
||||||
|
hl.exec_cmd("sleep 1 && hyprctl output create headless")
|
||||||
|
|
||||||
-- Polkit (Polkit Gnome / KDE)
|
-- Polkit (Polkit Gnome / KDE)
|
||||||
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
|
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ hl.on("hyprland.start", function()
|
|||||||
-- hl.exec_cmd("ags")
|
-- hl.exec_cmd("ags")
|
||||||
-- hl.exec_cmd("blueman-applet")
|
-- hl.exec_cmd("blueman-applet")
|
||||||
-- hl.exec_cmd("rog-control-center")
|
-- hl.exec_cmd("rog-control-center")
|
||||||
hl.exec_cmd("waybar")
|
-- hl.exec_cmd("waybar")
|
||||||
hl.exec_cmd("qs") -- quickshell AGS Desktop Overview alternative
|
hl.exec_cmd("qs") -- quickshell AGS Desktop Overview alternative
|
||||||
|
|
||||||
-- clipboard manager
|
-- clipboard manager
|
||||||
@@ -31,7 +34,10 @@ hl.on("hyprland.start", function()
|
|||||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||||
|
|
||||||
-- Starting hypridle to start hyprlock
|
-- Starting hypridle to start hyprlock
|
||||||
hl.exec_cmd("hypridle")
|
-- hl.exec_cmd("hypridle")
|
||||||
|
|
||||||
|
-- Sunshine for streaming media
|
||||||
|
h1.exec_cmd("sunshine")
|
||||||
|
|
||||||
-- Float + center the Bitwarden extension pop-out (window rules can't catch it — see the script)
|
-- Float + center the Bitwarden extension pop-out (window rules can't catch it — see the script)
|
||||||
hl.exec_cmd(scriptsDir .. "/FloatBitwardenPopup.sh")
|
hl.exec_cmd(scriptsDir .. "/FloatBitwardenPopup.sh")
|
||||||
|
|||||||
@@ -11,4 +11,5 @@
|
|||||||
hl.monitor({ output = "desc:LG Electronics LG ULTRAWIDE 0x0002E68A", mode = "2560x1080@60.00000", position = "1920x-740", scale = 1.00000000, transform = 3, vrr = 0 })
|
hl.monitor({ output = "desc:LG Electronics LG ULTRAWIDE 0x0002E68A", mode = "2560x1080@60.00000", position = "1920x-740", scale = 1.00000000, transform = 3, vrr = 0 })
|
||||||
hl.monitor({ output = "desc:HP Inc. HP 524pf VNG447017D", mode = "1920x1080@100.00000", position = "-1920x0", scale = 1.00000000, transform = 0, vrr = 0 })
|
hl.monitor({ output = "desc:HP Inc. HP 524pf VNG447017D", mode = "1920x1080@100.00000", position = "-1920x0", scale = 1.00000000, transform = 0, vrr = 0 })
|
||||||
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 1, bitdepth = 10, cm = "wide" })
|
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 1, bitdepth = 10, cm = "wide" })
|
||||||
|
hl.monitor({ output = "HEADLESS-1", mode = "1728x1080@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })
|
||||||
-- <<<<< TUI AUTO END
|
-- <<<<< TUI AUTO END
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
hl.workspace_rule({ workspace = 1, monitor = "DP-2", default = true })
|
hl.workspace_rule({ workspace = 1, monitor = "DP-2", default = true })
|
||||||
hl.workspace_rule({ workspace = 2, monitor = "DP-1", default = true })
|
hl.workspace_rule({ workspace = 2, monitor = "DP-1", default = true })
|
||||||
hl.workspace_rule({ workspace = 3, monitor = "HDMI-A-1", default = true })
|
hl.workspace_rule({ workspace = 3, monitor = "HDMI-A-1", default = true })
|
||||||
|
hl.workspace_rule({ workspace = 9, monitor = "HEADLESS-1", default = true })
|
||||||
-- hl.workspace_rule({ workspace = 4, monitor = "eDP-1" })
|
-- hl.workspace_rule({ workspace = 4, monitor = "eDP-1" })
|
||||||
-- hl.workspace_rule({ workspace = 5, monitor = "DP-2" })
|
-- hl.workspace_rule({ workspace = 5, monitor = "DP-2" })
|
||||||
-- hl.workspace_rule({ workspace = 6, monitor = "DP-2" })
|
-- hl.workspace_rule({ workspace = 6, monitor = "DP-2" })
|
||||||
|
|||||||
BIN
dotfiles/.config/quickshell/assets/bitwarden.png
Normal file
BIN
dotfiles/.config/quickshell/assets/bitwarden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
27
dotfiles/.config/quickshell/assets/sunshine.svg
Normal file
27
dotfiles/.config/quickshell/assets/sunshine.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Sunshine_Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||||
|
y="0px" width="256px" height="256px" viewBox="10 6 236 245" style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FDD107;}
|
||||||
|
.st1{fill:#F89A1C;}
|
||||||
|
.st2{fill:#EF3E23;}
|
||||||
|
.st3{fill:#F26222;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M118.7688675,20.7120476c0,0-63.8333359,26-74.3333359,83.8333282s37.1666718,91.5,86.3333282,75.3333282
|
||||||
|
s70.3333435-51,81.8333435-86.9999924c0,0-9.3333435,100.4999924-96.1666718,115.4999924s-118.1666641-50-82.1666641-119.8333282
|
||||||
|
C44.2688675,67.0453796,80.5188675,29.6287155,118.7688675,20.7120476z"/>
|
||||||
|
<path class="st1" d="M118.7688675,20.7120476c0,0-41.125,3.6666679-83.25,61.0416679s-28.125,139.125,34.25,149.375
|
||||||
|
s115.8749924-44.875,133.5-82.375s15.1666718-61.4583282,9.75-77.8749924c0,0,0.6666718,36.4166641-13.3333282,59.6666641
|
||||||
|
s-29.75,46.3333282-65.0833282,62.1666718s-74.1666718,13.75-95.4166718-19.25s-5.9166641-76.0833359-0.2916641-85.3333359
|
||||||
|
S72.3938675,33.7953796,118.7688675,20.7120476z"/>
|
||||||
|
<path class="st2" d="M73.0188675,39.6287155c0,0,38.125-28.125,76.8749924-28.125s63,28.25,68.5,52.25s6,54.125-11.5,87.6249924
|
||||||
|
s-37.375,56-79.1249924,76.125s-84.625,2.75-84.625,2.75s25.9769745,25.8750153,71.0509872,16.5
|
||||||
|
c45.0740051-9.375,82.2406769-40.875,98.4073486-69.5s28.7916565-57.3749924,27.6666565-92.2499924s-23.75-54.5-31.25-60.25
|
||||||
|
s-23.1875-17.8125-58.1875-16.5625S86.4563675,29.8162155,73.0188675,39.6287155z"/>
|
||||||
|
<path class="st3" d="M73.0188675,39.6287155c0,0,35-32.8125,82.4374924-32.8125s69.1875,24.8125,78.875,44.6875
|
||||||
|
s21.8125,70-12.1875,122.9999924s-74.625,67.375-93.625,71.625s-42.4311447,4.269165-59.1114044-1.2299957
|
||||||
|
c0,0,35.1947479,8.3966675,66.7780762-7.4366608s51.6666718-32.1666718,74.0833282-68.8333435
|
||||||
|
s25.9166718-72.7499924,22.1666718-93.9166565s-12.1666718-42.4166718-36.5-56.3333359s-56.7291718-10.531251-74.4791641-4.531251
|
||||||
|
S91.9876175,26.4099655,73.0188675,39.6287155z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -46,7 +46,7 @@ PanelWindow {
|
|||||||
rightMargin: Appearance.spacing
|
rightMargin: Appearance.spacing
|
||||||
}
|
}
|
||||||
spacing: Appearance.spacing
|
spacing: Appearance.spacing
|
||||||
//Tray {}
|
PinnedTray {}
|
||||||
TailscaleIndicator { barWindow: bar }
|
TailscaleIndicator { barWindow: bar }
|
||||||
VpnIndicator { barWindow: bar }
|
VpnIndicator { barWindow: bar }
|
||||||
NetworkIndicator { barWindow: bar }
|
NetworkIndicator { barWindow: bar }
|
||||||
|
|||||||
89
dotfiles/.config/quickshell/modules/bar/PinnedTray.qml
Normal file
89
dotfiles/.config/quickshell/modules/bar/PinnedTray.qml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import "../../config"
|
||||||
|
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
spacing: Appearance.spacing * 1.
|
||||||
|
|
||||||
|
|
||||||
|
readonly property var pinned: [
|
||||||
|
{
|
||||||
|
name: "Bitwarden",
|
||||||
|
icon: Qt.resolvedUrl("../../assets/bitwarden.png"),
|
||||||
|
launch: ["bitwarden"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sunshine",
|
||||||
|
|
||||||
|
icon: Qt.resolvedUrl("../../assets/sunshine.svg"),
|
||||||
|
launch: ["systemctl", "--user", "start", "app-dev.lizardbyte.app.Sunshine.service"],
|
||||||
|
secondary: ["xdg-open", "https://xol.twhomelab.local:47990"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function findTrayItem(name) {
|
||||||
|
const needle = name.toLowerCase()
|
||||||
|
for (const item of SystemTray.items.values) {
|
||||||
|
if (item.id.toLowerCase().includes(needle) || item.title.toLowerCase().includes(needle))
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.pinned
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: slot
|
||||||
|
required property var modelData
|
||||||
|
readonly property var trayItem: root.findTrayItem(modelData.name)
|
||||||
|
readonly property bool running: trayItem !== null
|
||||||
|
|
||||||
|
implicitWidth: Appearance.iconSize
|
||||||
|
implicitHeight: Appearance.iconSize
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: slot.running
|
||||||
|
? slot.trayItem.icon
|
||||||
|
: (slot.modelData.icon.includes("://")
|
||||||
|
? slot.modelData.icon
|
||||||
|
: Quickshell.iconPath(slot.modelData.icon))
|
||||||
|
opacity: slot.running ? 1.0 : 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
Process { id: launcher }
|
||||||
|
Process { id: secondaryProc }
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -4
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
if (mouse.button === Qt.RightButton) {
|
||||||
|
if (slot.modelData.secondary) {
|
||||||
|
if (!secondaryProc.running) {
|
||||||
|
secondaryProc.command = slot.modelData.secondary
|
||||||
|
secondaryProc.running = true
|
||||||
|
}
|
||||||
|
} else if (slot.running) {
|
||||||
|
slot.trayItem.secondaryActivate()
|
||||||
|
}
|
||||||
|
} else if (slot.running) {
|
||||||
|
slot.trayItem.activate()
|
||||||
|
} else if (!launcher.running) {
|
||||||
|
launcher.command = slot.modelData.launch
|
||||||
|
launcher.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import Quickshell.Widgets
|
|||||||
import "../../config"
|
import "../../config"
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Appearance.spacing * 0.6
|
spacing: Appearance.spacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: SystemTray.items
|
model: SystemTray.items
|
||||||
|
|||||||
@@ -45,9 +45,41 @@ Item {
|
|||||||
// to connect once the current one has finished disconnecting.
|
// to connect once the current one has finished disconnecting.
|
||||||
property string pendingConnect: ""
|
property string pendingConnect: ""
|
||||||
|
|
||||||
// Surfaced under the header for a failed connect/disconnect; cleared
|
// Surfaced under each provider's own row for a failed connect/
|
||||||
// automatically after a few seconds.
|
// disconnect/settings change; cleared automatically after a few
|
||||||
property string actionStatus: ""
|
// seconds. Kept separate per provider so an error from one doesn't
|
||||||
|
// show up hanging under the other's row.
|
||||||
|
property string mullvadActionStatus: ""
|
||||||
|
property string protonActionStatus: ""
|
||||||
|
|
||||||
|
// --- Mullvad advanced options (auto-connect, relay location, multihop) --
|
||||||
|
// ProtonVPN's CLI is plain-text-only and far less predictable across
|
||||||
|
// versions, so this extra panel is Mullvad-specific rather than a
|
||||||
|
// second ProviderRow feature.
|
||||||
|
|
||||||
|
property bool mullvadOptionsOpen: false
|
||||||
|
property bool mullvadAutoConnect: false
|
||||||
|
property bool mullvadMultihop: false
|
||||||
|
// Human-readable current relay constraint, e.g. "Any" or "Sweden" —
|
||||||
|
// resolved from `relay get`'s "country xx[ city yyy]" syntax against
|
||||||
|
// mullvadCountries once that list has loaded.
|
||||||
|
property string mullvadLocationLabel: "Any"
|
||||||
|
property string mullvadEntryLabel: "Any"
|
||||||
|
// [{name, code}], parsed from `mullvad relay list` and cached for the
|
||||||
|
// life of the panel — the full relay list rarely changes and re-parsing
|
||||||
|
// ~50 countries on every open would just be wasted work.
|
||||||
|
property var mullvadCountries: []
|
||||||
|
// "" | "exit" | "entry" — which location field's picker is open, if any.
|
||||||
|
property string mullvadPickerTarget: ""
|
||||||
|
property string mullvadPickerFilter: ""
|
||||||
|
|
||||||
|
readonly property var mullvadFilteredCountries: {
|
||||||
|
const f = root.mullvadPickerFilter.trim().toLowerCase()
|
||||||
|
if (!f) return root.mullvadCountries
|
||||||
|
return root.mullvadCountries.filter(function(c) {
|
||||||
|
return c.name.toLowerCase().indexOf(f) !== -1 || c.code.indexOf(f) !== -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function shortLine(text) {
|
function shortLine(text) {
|
||||||
const line = String(text || "").trim().split("\n")[0] || ""
|
const line = String(text || "").trim().split("\n")[0] || ""
|
||||||
@@ -82,6 +114,95 @@ Item {
|
|||||||
VpnExclusivity.protonConnected = root.protonState === "connected"
|
VpnExclusivity.protonConnected = root.protonState === "connected"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyMullvadAutoConnect(text) {
|
||||||
|
const m = String(text || "").match(/autoconnect:\s*(on|off)/i)
|
||||||
|
root.mullvadAutoConnect = !!m && m[1].toLowerCase() === "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolves relay get's "any" / "country xx" / "country xx city yyy" /
|
||||||
|
// bare-hostname syntax into a display label. Falls back to the raw
|
||||||
|
// country code (or the raw string) when mullvadCountries hasn't loaded
|
||||||
|
// yet, rather than showing nothing.
|
||||||
|
function describeMullvadLocation(raw) {
|
||||||
|
const s = String(raw || "").trim()
|
||||||
|
if (!s || s === "any") return "Any"
|
||||||
|
const m = s.match(/^country\s+([a-z]{2})\b/i)
|
||||||
|
if (!m) return s
|
||||||
|
const code = m[1].toLowerCase()
|
||||||
|
const country = root.mullvadCountries.find(function(c) { return c.code === code })
|
||||||
|
return country ? country.name : code.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMullvadRelay(text) {
|
||||||
|
const body = String(text || "")
|
||||||
|
const locMatch = body.match(/^\s*Location:\s*(.+)$/mi)
|
||||||
|
const multihopMatch = body.match(/^\s*Multihop state:\s*(\w+)/mi)
|
||||||
|
const entryMatch = body.match(/^\s*Multihop entry:\s*(.+)$/mi)
|
||||||
|
root.mullvadMultihop = !!multihopMatch && multihopMatch[1].toLowerCase() === "enabled"
|
||||||
|
root.mullvadLocationLabel = root.describeMullvadLocation(locMatch ? locMatch[1].trim() : "")
|
||||||
|
root.mullvadEntryLabel = root.describeMullvadLocation(entryMatch ? entryMatch[1].trim() : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only country-level lines are of interest ("Sweden (se)", flush left) —
|
||||||
|
// city ("\tGothenburg (got) @ ...") and server lines are indented and
|
||||||
|
// their parenthesised code is never exactly two letters, so the regex
|
||||||
|
// alone is enough to tell them apart without checking indentation.
|
||||||
|
function applyMullvadCountries(text) {
|
||||||
|
const lines = String(text || "").split("\n")
|
||||||
|
const countries = []
|
||||||
|
for (const line of lines) {
|
||||||
|
const m = line.match(/^(\S.*) \(([a-z]{2})\)\s*$/)
|
||||||
|
if (m) countries.push({ name: m[1].trim(), code: m[2] })
|
||||||
|
}
|
||||||
|
countries.sort(function(a, b) { return a.name.localeCompare(b.name) })
|
||||||
|
root.mullvadCountries = countries
|
||||||
|
// Re-resolve any labels already showing raw codes now that names
|
||||||
|
// are available.
|
||||||
|
if (!mullvadRelayGetProc.running) mullvadRelayGetProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMullvadOptions() {
|
||||||
|
root.mullvadOptionsOpen = !root.mullvadOptionsOpen
|
||||||
|
if (root.mullvadOptionsOpen) {
|
||||||
|
if (!mullvadAutoConnectGetProc.running) mullvadAutoConnectGetProc.running = true
|
||||||
|
if (!mullvadRelayGetProc.running) mullvadRelayGetProc.running = true
|
||||||
|
if (root.mullvadCountries.length === 0 && !mullvadRelayListProc.running) mullvadRelayListProc.running = true
|
||||||
|
} else {
|
||||||
|
root.mullvadPickerTarget = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMullvadPicker(target) {
|
||||||
|
root.mullvadPickerFilter = ""
|
||||||
|
root.mullvadPickerTarget = root.mullvadPickerTarget === target ? "" : target
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickMullvadLocation(code) {
|
||||||
|
const target = root.mullvadPickerTarget
|
||||||
|
root.mullvadPickerTarget = ""
|
||||||
|
root.setMullvadLocation(target, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMullvadAutoConnect(on) {
|
||||||
|
if (mullvadAutoConnectSetProc.running) return
|
||||||
|
mullvadAutoConnectSetProc.command = ["mullvad", "auto-connect", "set", on ? "on" : "off"]
|
||||||
|
mullvadAutoConnectSetProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMullvadMultihop(on) {
|
||||||
|
if (mullvadRelaySetProc.running) return
|
||||||
|
mullvadRelaySetProc.command = ["mullvad", "relay", "set", "multihop", on ? "on" : "off"]
|
||||||
|
mullvadRelaySetProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMullvadLocation(target, code) {
|
||||||
|
if (mullvadRelaySetProc.running) return
|
||||||
|
mullvadRelaySetProc.command = target === "entry"
|
||||||
|
? ["mullvad", "relay", "set", "entry", "location", code]
|
||||||
|
: ["mullvad", "relay", "set", "location", code]
|
||||||
|
mullvadRelaySetProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
function refreshMullvad() {
|
function refreshMullvad() {
|
||||||
if (!root.mullvadInstalled) {
|
if (!root.mullvadInstalled) {
|
||||||
if (!mullvadWhichProc.running) mullvadWhichProc.running = true
|
if (!mullvadWhichProc.running) mullvadWhichProc.running = true
|
||||||
@@ -197,8 +318,8 @@ Item {
|
|||||||
stderr: StdioCollector { id: mullvadConnectErr }
|
stderr: StdioCollector { id: mullvadConnectErr }
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.actionStatus = "Mullvad: " + root.shortLine(mullvadConnectErr.text || "connect failed")
|
root.mullvadActionStatus = root.shortLine(mullvadConnectErr.text || "Connect failed")
|
||||||
actionStatusTimer.restart()
|
mullvadActionStatusTimer.restart()
|
||||||
} else if (!tailscaleDownProc.running) {
|
} else if (!tailscaleDownProc.running) {
|
||||||
tailscaleDownProc.running = true
|
tailscaleDownProc.running = true
|
||||||
}
|
}
|
||||||
@@ -213,8 +334,8 @@ Item {
|
|||||||
stderr: StdioCollector { id: mullvadDisconnectErr }
|
stderr: StdioCollector { id: mullvadDisconnectErr }
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.actionStatus = "Mullvad: " + root.shortLine(mullvadDisconnectErr.text || "disconnect failed")
|
root.mullvadActionStatus = root.shortLine(mullvadDisconnectErr.text || "Disconnect failed")
|
||||||
actionStatusTimer.restart()
|
mullvadActionStatusTimer.restart()
|
||||||
}
|
}
|
||||||
root.refreshMullvad()
|
root.refreshMullvad()
|
||||||
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
||||||
@@ -229,6 +350,77 @@ Item {
|
|||||||
onTriggered: if (mullvadStatusProc.running) mullvadStatusProc.running = false
|
onTriggered: if (mullvadStatusProc.running) mullvadStatusProc.running = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Mullvad advanced options: auto-connect, relay location, multihop --
|
||||||
|
// Only queried while the options section is open (see
|
||||||
|
// root.openMullvadOptions), not on the regular status poll — these
|
||||||
|
// don't change on their own the way tunnel state does.
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadAutoConnectGetProc
|
||||||
|
command: ["mullvad", "auto-connect", "get"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: mullvadAutoConnectGetCollector
|
||||||
|
onStreamFinished: root.applyMullvadAutoConnect(mullvadAutoConnectGetCollector.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadAutoConnectSetProc
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector { id: mullvadAutoConnectSetErr }
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
root.mullvadActionStatus = root.shortLine(mullvadAutoConnectSetErr.text || "Auto-connect change failed")
|
||||||
|
mullvadActionStatusTimer.restart()
|
||||||
|
}
|
||||||
|
if (!mullvadAutoConnectGetProc.running) mullvadAutoConnectGetProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadRelayGetProc
|
||||||
|
command: ["mullvad", "relay", "get"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: mullvadRelayGetCollector
|
||||||
|
onStreamFinished: root.applyMullvadRelay(mullvadRelayGetCollector.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadRelayListProc
|
||||||
|
command: ["mullvad", "relay", "list"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: mullvadRelayListCollector
|
||||||
|
onStreamFinished: root.applyMullvadCountries(mullvadRelayListCollector.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadRelaySetProc
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector { id: mullvadRelaySetErr }
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
root.mullvadActionStatus = root.shortLine(mullvadRelaySetErr.text || "Relay change failed")
|
||||||
|
mullvadActionStatusTimer.restart()
|
||||||
|
}
|
||||||
|
if (!mullvadRelayGetProc.running) mullvadRelayGetProc.running = true
|
||||||
|
// Relay constraint changes don't take effect on an already-open
|
||||||
|
// tunnel by themselves — kick a reconnect so switching location/
|
||||||
|
// multihop while connected actually moves the tunnel, instead of
|
||||||
|
// silently only applying on the next manual reconnect.
|
||||||
|
if (root.mullvadState === "connected" && !mullvadReconnectProc.running) mullvadReconnectProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: mullvadReconnectProc
|
||||||
|
command: ["mullvad", "reconnect"]
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector {}
|
||||||
|
onExited: (exitCode, exitStatus) => root.refreshMullvad()
|
||||||
|
}
|
||||||
|
|
||||||
// --- ProtonVPN ---------------------------------------------------------
|
// --- ProtonVPN ---------------------------------------------------------
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
@@ -261,8 +453,8 @@ Item {
|
|||||||
stderr: StdioCollector { id: protonConnectErr }
|
stderr: StdioCollector { id: protonConnectErr }
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.actionStatus = "ProtonVPN: " + root.shortLine(protonConnectErr.text || "connect failed")
|
root.protonActionStatus = root.shortLine(protonConnectErr.text || "Connect failed")
|
||||||
actionStatusTimer.restart()
|
protonActionStatusTimer.restart()
|
||||||
} else if (!tailscaleDownProc.running) {
|
} else if (!tailscaleDownProc.running) {
|
||||||
tailscaleDownProc.running = true
|
tailscaleDownProc.running = true
|
||||||
}
|
}
|
||||||
@@ -277,8 +469,8 @@ Item {
|
|||||||
stderr: StdioCollector { id: protonDisconnectErr }
|
stderr: StdioCollector { id: protonDisconnectErr }
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.actionStatus = "ProtonVPN: " + root.shortLine(protonDisconnectErr.text || "disconnect failed")
|
root.protonActionStatus = root.shortLine(protonDisconnectErr.text || "Disconnect failed")
|
||||||
actionStatusTimer.restart()
|
protonActionStatusTimer.restart()
|
||||||
}
|
}
|
||||||
root.refreshProton()
|
root.refreshProton()
|
||||||
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
||||||
@@ -305,10 +497,17 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: actionStatusTimer
|
id: mullvadActionStatusTimer
|
||||||
interval: 4000
|
interval: 4000
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: root.actionStatus = ""
|
onTriggered: root.mullvadActionStatus = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: protonActionStatusTimer
|
||||||
|
interval: 4000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.protonActionStatus = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Bar icon ------------------------------------------------------------
|
// --- Bar icon ------------------------------------------------------------
|
||||||
@@ -416,7 +615,13 @@ Item {
|
|||||||
property string detailText: ""
|
property string detailText: ""
|
||||||
property string state: "disconnected"
|
property string state: "disconnected"
|
||||||
property bool installed: true
|
property bool installed: true
|
||||||
|
// When true, shows a chevron that expands a provider-specific
|
||||||
|
// options section below this row (used for Mullvad's advanced
|
||||||
|
// options; ProtonVPN's row leaves this false).
|
||||||
|
property bool expandable: false
|
||||||
|
property bool expanded: false
|
||||||
signal toggle()
|
signal toggle()
|
||||||
|
signal expandToggle()
|
||||||
width: parent ? parent.width : 0
|
width: parent ? parent.width : 0
|
||||||
radius: 0
|
radius: 0
|
||||||
implicitHeight: rowContent.implicitHeight + 12
|
implicitHeight: rowContent.implicitHeight + 12
|
||||||
@@ -477,8 +682,90 @@ Item {
|
|||||||
busy: provRow.state === "connecting" || provRow.state === "disconnecting"
|
busy: provRow.state === "connecting" || provRow.state === "disconnecting"
|
||||||
onToggled: provRow.toggle()
|
onToggled: provRow.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: provRow.expandable
|
||||||
|
text: provRow.expanded ? "expand_less" : "expand_more"
|
||||||
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
|
font.pixelSize: 15
|
||||||
|
color: expandMouse.containsMouse ? Colors.foreground : Colors.muted
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: expandMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -5
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: provRow.expandToggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single clickable "Location: Sweden ›" row inside the Mullvad
|
||||||
|
// options section — used for both the exit location and (when multihop
|
||||||
|
// is on) the entry location. Clicking routes back to root, which opens
|
||||||
|
// the shared country picker below rather than each field owning one.
|
||||||
|
component LocationField: Item {
|
||||||
|
id: locField
|
||||||
|
property string fieldLabel: ""
|
||||||
|
property string valueText: "Any"
|
||||||
|
property bool open: false
|
||||||
|
signal clicked()
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
implicitHeight: locRow.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: locRow
|
||||||
|
width: parent.width
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: locField.fieldLabel
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: locField.valueText
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
font.bold: true
|
||||||
|
color: Colors.foreground
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: locField.open ? "expand_less" : "chevron_right"
|
||||||
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: Colors.muted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: locField.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A failed connect/disconnect/settings-change message, shown directly
|
||||||
|
// under the provider row it came from rather than in one shared line
|
||||||
|
// for both providers — keeps an error attributed to whichever CLI it
|
||||||
|
// actually came from.
|
||||||
|
component StatusLine: Text {
|
||||||
|
property string message: ""
|
||||||
|
visible: message !== ""
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
text: message
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
// --- Panel -----------------------------------------------------------
|
// --- Panel -----------------------------------------------------------
|
||||||
|
|
||||||
@@ -494,7 +781,18 @@ Item {
|
|||||||
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: if (visible) root.refresh()
|
// Leaving mullvadOptionsOpen itself alone across opens (it's a
|
||||||
|
// deliberate "keep this expanded" choice), but the country picker
|
||||||
|
// is transient state scoped to one visit — closing the panel with
|
||||||
|
// it open would otherwise leave a stale filter/list waiting.
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
root.refresh()
|
||||||
|
} else {
|
||||||
|
root.mullvadPickerTarget = ""
|
||||||
|
root.mullvadPickerFilter = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
anchor {
|
anchor {
|
||||||
window: root.barWindow
|
window: root.barWindow
|
||||||
@@ -548,28 +846,202 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: root.actionStatus !== ""
|
|
||||||
width: parent.width
|
|
||||||
text: root.actionStatus
|
|
||||||
font.family: Appearance.fontFamily
|
|
||||||
font.pixelSize: Appearance.fontSizeSmall
|
|
||||||
color: Colors.muted
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
|
// Mullvad's connect row and its advanced options share a
|
||||||
|
// single bordered card — the border wraps the whole
|
||||||
|
// section (row, status line, and options together) and
|
||||||
|
// grows to include the options/picker as they open,
|
||||||
|
// rather than the options having a separate box of
|
||||||
|
// their own below an unbordered connect row.
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
radius: 0
|
||||||
|
color: Colors.background
|
||||||
|
border.color: Colors.muted
|
||||||
|
border.width: 1
|
||||||
|
implicitHeight: mullvadSectionColumn.implicitHeight + 16
|
||||||
|
clip: true
|
||||||
|
// Without this, the card snapped to its new size
|
||||||
|
// instantly while the popup around it was still
|
||||||
|
// animating open, so the border visibly lagged
|
||||||
|
// behind (or cut off) content like the country
|
||||||
|
// picker appearing/disappearing beneath it.
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mullvadSectionColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: 8
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
label: "Mullvad"
|
label: "Mullvad"
|
||||||
subIcon: "gpp_good"
|
subIcon: "gpp_good"
|
||||||
state: root.mullvadState
|
state: root.mullvadState
|
||||||
detailText: root.mullvadDetail
|
detailText: root.mullvadDetail
|
||||||
installed: root.mullvadInstalled
|
installed: root.mullvadInstalled
|
||||||
|
expandable: root.mullvadInstalled
|
||||||
|
expanded: root.mullvadOptionsOpen
|
||||||
onToggle: root.toggleMullvad()
|
onToggle: root.toggleMullvad()
|
||||||
|
onExpandToggle: root.openMullvadOptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusLine { message: root.mullvadActionStatus }
|
||||||
|
|
||||||
|
// Advanced options — auto-connect, exit/entry
|
||||||
|
// relay location, multihop.
|
||||||
|
Column {
|
||||||
|
id: mullvadOptionsColumn
|
||||||
|
width: parent.width
|
||||||
|
visible: root.mullvadOptionsOpen && root.mullvadInstalled
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Auto-connect"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.foreground
|
||||||
|
}
|
||||||
|
ToggleSwitch {
|
||||||
|
checked: root.mullvadAutoConnect
|
||||||
|
busy: mullvadAutoConnectSetProc.running
|
||||||
|
onToggled: root.setMullvadAutoConnect(!root.mullvadAutoConnect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Multihop"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.foreground
|
||||||
|
}
|
||||||
|
ToggleSwitch {
|
||||||
|
checked: root.mullvadMultihop
|
||||||
|
busy: mullvadRelaySetProc.running
|
||||||
|
onToggled: root.setMullvadMultihop(!root.mullvadMultihop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationField {
|
||||||
|
fieldLabel: root.mullvadMultihop ? "Exit" : "Location"
|
||||||
|
valueText: root.mullvadLocationLabel
|
||||||
|
open: root.mullvadPickerTarget === "exit"
|
||||||
|
onClicked: root.openMullvadPicker("exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationField {
|
||||||
|
visible: root.mullvadMultihop
|
||||||
|
fieldLabel: "Entry"
|
||||||
|
valueText: root.mullvadEntryLabel
|
||||||
|
open: root.mullvadPickerTarget === "entry"
|
||||||
|
onClicked: root.openMullvadPicker("entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared country picker for whichever LocationField
|
||||||
|
// was last clicked — filter box plus a capped,
|
||||||
|
// scrollable list ("Any" always pinned first).
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
visible: root.mullvadPickerTarget !== ""
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: 24
|
||||||
|
radius: 0
|
||||||
|
color: Colors.surface
|
||||||
|
border.color: Colors.muted
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
visible: mullvadFilterInput.text.length === 0
|
||||||
|
text: "Search country…"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
}
|
||||||
|
|
||||||
|
TextInput {
|
||||||
|
id: mullvadFilterInput
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
color: Colors.foreground
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
clip: true
|
||||||
|
focus: root.mullvadPickerTarget !== ""
|
||||||
|
text: root.mullvadPickerFilter
|
||||||
|
onTextChanged: root.mullvadPickerFilter = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: mullvadPickerFlick
|
||||||
|
width: parent.width
|
||||||
|
height: Math.min(mullvadPickerColumn.implicitHeight, 150)
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: mullvadPickerColumn.implicitHeight
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: mullvadPickerColumn
|
||||||
|
width: mullvadPickerFlick.width
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [{ name: "Any", code: "any" }].concat(root.mullvadFilteredCountries)
|
||||||
|
Rectangle {
|
||||||
|
required property var modelData
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: 22
|
||||||
|
radius: 0
|
||||||
|
color: countryMouse.containsMouse ? Colors.primary : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: 4
|
||||||
|
anchors.rightMargin: 4
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: modelData.name
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: countryMouse.containsMouse ? Colors.primaryText : Colors.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: countryMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.pickMullvadLocation(modelData.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
label: "ProtonVPN"
|
label: "ProtonVPN"
|
||||||
subIcon: "gpp_good"
|
subIcon: "gpp_good"
|
||||||
@@ -578,6 +1050,8 @@ Item {
|
|||||||
installed: root.protonInstalled
|
installed: root.protonInstalled
|
||||||
onToggle: root.toggleProton()
|
onToggle: root.toggleProton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusLine { message: root.protonActionStatus }
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import "./modules/osd"
|
|||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
// Exclude the Sunshine headless virtual monitor — nobody ever sees a
|
||||||
|
// bar there, but PanelWindow's exclusiveZone still reserves space for
|
||||||
|
// it, which left a black strip cut out of Steam Big Picture's
|
||||||
|
// fullscreen area on that output.
|
||||||
|
model: Quickshell.screens.filter(s => !s.name.startsWith("HEADLESS"))
|
||||||
|
|
||||||
Bar {
|
Bar {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|||||||
Reference in New Issue
Block a user