Merge branch 'main' into sdfs
This commit is contained in:
commit
b57d596230
35 changed files with 1538 additions and 206 deletions
|
|
@ -55,6 +55,7 @@ Elevation {
|
||||||
function onClicked(): void {
|
function onClicked(): void {
|
||||||
root.itemSelected(item.modelData);
|
root.itemSelected(item.modelData);
|
||||||
root.active = item.modelData;
|
root.active = item.modelData;
|
||||||
|
item.modelData.clicked();
|
||||||
root.expanded = false;
|
root.expanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,11 +267,13 @@ Singleton {
|
||||||
function serializeNotifs(): var {
|
function serializeNotifs(): var {
|
||||||
return {
|
return {
|
||||||
expire: notifs.expire,
|
expire: notifs.expire,
|
||||||
|
fullscreen: notifs.fullscreen,
|
||||||
defaultExpireTimeout: notifs.defaultExpireTimeout,
|
defaultExpireTimeout: notifs.defaultExpireTimeout,
|
||||||
clearThreshold: notifs.clearThreshold,
|
clearThreshold: notifs.clearThreshold,
|
||||||
expandThreshold: notifs.expandThreshold,
|
expandThreshold: notifs.expandThreshold,
|
||||||
actionOnClick: notifs.actionOnClick,
|
actionOnClick: notifs.actionOnClick,
|
||||||
groupPreviewNum: notifs.groupPreviewNum
|
groupPreviewNum: notifs.groupPreviewNum,
|
||||||
|
openExpanded: notifs.openExpanded
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,11 +320,30 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeUtilities(): var {
|
function serializeUtilities(): var {
|
||||||
|
const vpnProviders = [];
|
||||||
|
for (let i = 0; i < utilities.vpn.provider.length; i++) {
|
||||||
|
const p = utilities.vpn.provider[i];
|
||||||
|
const provider = {
|
||||||
|
displayName: p.displayName,
|
||||||
|
enabled: p.enabled,
|
||||||
|
iface: p.iface,
|
||||||
|
name: p.name
|
||||||
|
};
|
||||||
|
if (p.connectCmd && p.connectCmd.length > 0) {
|
||||||
|
provider.connectCmd = p.connectCmd;
|
||||||
|
}
|
||||||
|
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
|
||||||
|
provider.disconnectCmd = p.disconnectCmd;
|
||||||
|
}
|
||||||
|
vpnProviders.push(provider);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: utilities.enabled,
|
enabled: utilities.enabled,
|
||||||
maxToasts: utilities.maxToasts,
|
maxToasts: utilities.maxToasts,
|
||||||
toasts: {
|
toasts: {
|
||||||
configLoaded: utilities.toasts.configLoaded,
|
configLoaded: utilities.toasts.configLoaded,
|
||||||
|
fullscreen: utilities.toasts.fullscreen,
|
||||||
chargingChanged: utilities.toasts.chargingChanged,
|
chargingChanged: utilities.toasts.chargingChanged,
|
||||||
gameModeChanged: utilities.toasts.gameModeChanged,
|
gameModeChanged: utilities.toasts.gameModeChanged,
|
||||||
dndChanged: utilities.toasts.dndChanged,
|
dndChanged: utilities.toasts.dndChanged,
|
||||||
|
|
@ -336,7 +357,7 @@ Singleton {
|
||||||
},
|
},
|
||||||
vpn: {
|
vpn: {
|
||||||
enabled: utilities.vpn.enabled,
|
enabled: utilities.vpn.enabled,
|
||||||
provider: utilities.vpn.provider
|
provider: vpnProviders
|
||||||
},
|
},
|
||||||
quickToggles: utilities.quickToggles
|
quickToggles: utilities.quickToggles
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Quickshell.Io
|
||||||
|
|
||||||
JsonObject {
|
JsonObject {
|
||||||
property bool expire: true
|
property bool expire: true
|
||||||
|
property string fullscreen: "on"
|
||||||
property int defaultExpireTimeout: 5000
|
property int defaultExpireTimeout: 5000
|
||||||
property real clearThreshold: 0.3
|
property real clearThreshold: 0.3
|
||||||
property int expandThreshold: 20
|
property int expandThreshold: 20
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ JsonObject {
|
||||||
|
|
||||||
component Toasts: JsonObject {
|
component Toasts: JsonObject {
|
||||||
property bool configLoaded: true
|
property bool configLoaded: true
|
||||||
|
property string fullscreen: "off"
|
||||||
property bool chargingChanged: true
|
property bool chargingChanged: true
|
||||||
property bool gameModeChanged: true
|
property bool gameModeChanged: true
|
||||||
property bool dndChanged: true
|
property bool dndChanged: true
|
||||||
|
|
@ -61,6 +62,6 @@ JsonObject {
|
||||||
|
|
||||||
component Vpn: JsonObject {
|
component Vpn: JsonObject {
|
||||||
property bool enabled: false
|
property bool enabled: false
|
||||||
property list<var> provider: ["netbird"]
|
property list<var> provider: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Scope {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool launcherInterrupted
|
property bool launcherInterrupted
|
||||||
readonly property bool hasFullscreen: Hypr.focusedWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) ?? false
|
readonly property bool hasFullscreen: Hypr.focusedWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false
|
||||||
|
|
||||||
// qmllint disable unresolved-type
|
// qmllint disable unresolved-type
|
||||||
CustomShortcut {
|
CustomShortcut {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Caelestia.Internal
|
||||||
import Caelestia.Services
|
import Caelestia.Services
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
@ -55,25 +55,29 @@ Item {
|
||||||
service: Audio.cava
|
service: Audio.cava
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
VisualiserBars {
|
||||||
id: content
|
id: bars
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Config.border.thickness
|
anchors.margins: Config.border.thickness
|
||||||
anchors.leftMargin: Visibilities.bars.get(root.screen).exclusiveZone + Appearance.spacing.small * Config.background.visualiser.spacing
|
anchors.leftMargin: Visibilities.bars.get(root.screen).exclusiveZone + Appearance.spacing.small * Config.background.visualiser.spacing
|
||||||
|
|
||||||
Side {
|
values: Audio.cava.values
|
||||||
content: content
|
primaryColor: Qt.alpha(Colours.palette.m3primary, 0.7)
|
||||||
}
|
secondaryColor: Qt.alpha(Colours.palette.m3inversePrimary, 0.7)
|
||||||
Side {
|
rounding: Appearance.rounding.small * Config.background.visualiser.rounding
|
||||||
content: content
|
spacing: Appearance.spacing.small * Config.background.visualiser.spacing
|
||||||
isRight: true
|
animationDuration: Appearance.anim.durations.normal
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on anchors.leftMargin {
|
Behavior on anchors.leftMargin {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FrameAnimation {
|
||||||
|
running: root.opacity > 0 && !bars.settled
|
||||||
|
onTriggered: bars.advance(frameTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,69 +89,4 @@ Item {
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
|
|
||||||
component Side: Repeater {
|
|
||||||
id: side
|
|
||||||
|
|
||||||
required property Item content
|
|
||||||
property bool isRight
|
|
||||||
|
|
||||||
model: Config.services.visualiserBars
|
|
||||||
|
|
||||||
ClippingRectangle {
|
|
||||||
id: bar
|
|
||||||
|
|
||||||
required property int modelData
|
|
||||||
property real value: Math.max(0, Math.min(1, Audio.cava.values[side.isRight ? modelData : side.count - modelData - 1]))
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
x: modelData * ((side.content.width * 0.4) / Config.services.visualiserBars) + (side.isRight ? side.content.width * 0.6 : 0)
|
|
||||||
implicitWidth: (side.content.width * 0.4) / Config.services.visualiserBars - Appearance.spacing.small * Config.background.visualiser.spacing
|
|
||||||
|
|
||||||
y: side.content.height - height
|
|
||||||
implicitHeight: bar.value * side.content.height * 0.4
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
topLeftRadius: Appearance.rounding.small * Config.background.visualiser.rounding
|
|
||||||
topRightRadius: Appearance.rounding.small * Config.background.visualiser.rounding
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
topLeftRadius: parent.topLeftRadius
|
|
||||||
topRightRadius: parent.topRightRadius
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Vertical
|
|
||||||
|
|
||||||
GradientStop {
|
|
||||||
position: 0
|
|
||||||
color: Qt.alpha(Colours.palette.m3primary, 0.7)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
CAnim {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 1
|
|
||||||
color: Qt.alpha(Colours.palette.m3inversePrimary, 0.7)
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
CAnim {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
y: parent.height - height
|
|
||||||
implicitHeight: side.content.height * 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on value {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.small
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ ColumnLayout {
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
required property DrawerVisibilities visibilities
|
required property DrawerVisibilities visibilities
|
||||||
required property BarPopouts.Wrapper popouts
|
required property BarPopouts.Wrapper popouts
|
||||||
|
required property bool fullscreen
|
||||||
readonly property int vPadding: Appearance.padding.large
|
readonly property int vPadding: Appearance.padding.large
|
||||||
|
|
||||||
function closeTray(): void {
|
function closeTray(): void {
|
||||||
|
|
@ -128,6 +129,7 @@ ColumnLayout {
|
||||||
delegate: WrappedLoader {
|
delegate: WrappedLoader {
|
||||||
sourceComponent: Workspaces {
|
sourceComponent: Workspaces {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
|
fullscreen: root.fullscreen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +137,7 @@ ColumnLayout {
|
||||||
roleValue: "activeWindow"
|
roleValue: "activeWindow"
|
||||||
delegate: WrappedLoader {
|
delegate: WrappedLoader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: !root.fullscreen
|
||||||
sourceComponent: ActiveWindow {
|
sourceComponent: ActiveWindow {
|
||||||
bar: root
|
bar: root
|
||||||
monitor: Brightness.getMonitorForScreen(root.screen)
|
monitor: Brightness.getMonitorForScreen(root.screen)
|
||||||
|
|
@ -144,18 +147,21 @@ ColumnLayout {
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "tray"
|
roleValue: "tray"
|
||||||
delegate: WrappedLoader {
|
delegate: WrappedLoader {
|
||||||
|
visible: !root.fullscreen
|
||||||
sourceComponent: Tray {}
|
sourceComponent: Tray {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "clock"
|
roleValue: "clock"
|
||||||
delegate: WrappedLoader {
|
delegate: WrappedLoader {
|
||||||
|
visible: !root.fullscreen
|
||||||
sourceComponent: Clock {}
|
sourceComponent: Clock {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "statusIcons"
|
roleValue: "statusIcons"
|
||||||
delegate: WrappedLoader {
|
delegate: WrappedLoader {
|
||||||
|
visible: !root.fullscreen
|
||||||
sourceComponent: StatusIcons {}
|
sourceComponent: StatusIcons {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ Item {
|
||||||
required property DrawerVisibilities visibilities
|
required property DrawerVisibilities visibilities
|
||||||
required property BarPopouts.Wrapper popouts
|
required property BarPopouts.Wrapper popouts
|
||||||
required property bool disabled
|
required property bool disabled
|
||||||
|
required property bool fullscreen
|
||||||
|
|
||||||
readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth)
|
readonly property int clampedWidth: Math.max(Config.border.minThickness, implicitWidth)
|
||||||
readonly property int padding: Math.max(Appearance.padding.smaller, Config.border.thickness)
|
readonly property int padding: Math.max(Appearance.padding.smaller, Config.border.thickness)
|
||||||
readonly property int contentWidth: Config.bar.sizes.innerWidth + padding * 2
|
readonly property int contentWidth: Config.bar.sizes.innerWidth + padding * 2
|
||||||
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness
|
readonly property int exclusiveZone: !disabled && (Config.bar.persistent || visibilities.bar) ? contentWidth : Config.border.thickness
|
||||||
readonly property bool shouldBeVisible: !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
|
readonly property bool shouldBeVisible: !fullscreen && !disabled && (Config.bar.persistent || visibilities.bar || isHovered)
|
||||||
property bool isHovered
|
property bool isHovered
|
||||||
|
|
||||||
function closeTray(): void {
|
function closeTray(): void {
|
||||||
|
|
@ -33,8 +34,9 @@ Item {
|
||||||
(content.item as Bar)?.handleWheel(y, angleDelta);
|
(content.item as Bar)?.handleWheel(y, angleDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: width > Config.border.thickness
|
clip: true
|
||||||
implicitWidth: Config.border.thickness
|
visible: width > 0
|
||||||
|
implicitWidth: fullscreen ? 0 : Config.border.thickness
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
name: "visible"
|
name: "visible"
|
||||||
|
|
@ -83,6 +85,7 @@ Item {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
visibilities: root.visibilities
|
visibilities: root.visibilities
|
||||||
popouts: root.popouts // qmllint disable incompatible-type
|
popouts: root.popouts // qmllint disable incompatible-type
|
||||||
|
fullscreen: root.fullscreen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ StyledRect {
|
||||||
required property int activeWsId
|
required property int activeWsId
|
||||||
required property Repeater workspaces
|
required property Repeater workspaces
|
||||||
required property Item mask
|
required property Item mask
|
||||||
|
required property bool fullscreen
|
||||||
|
|
||||||
readonly property int currentWsIdx: {
|
readonly property int currentWsIdx: {
|
||||||
let i = activeWsId - 1;
|
let i = activeWsId - 1;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ StyledClippingRect {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
|
required property bool fullscreen
|
||||||
|
|
||||||
readonly property bool onSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name !== ""
|
readonly property bool onSpecial: (Config.bar.workspaces.perMonitorWorkspaces ? Hypr.monitorFor(screen) : Hypr.focusedMonitor)?.lastIpcObject.specialWorkspace?.name !== ""
|
||||||
readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hypr.monitorFor(screen).activeWorkspace?.id ?? 1) : Hypr.activeWsId
|
readonly property int activeWsId: Config.bar.workspaces.perMonitorWorkspaces ? (Hypr.monitorFor(screen).activeWorkspace?.id ?? 1) : Hypr.activeWsId
|
||||||
|
|
@ -36,6 +37,7 @@ StyledClippingRect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
scale: root.onSpecial ? 0.8 : 1
|
scale: root.onSpecial ? 0.8 : 1
|
||||||
opacity: root.onSpecial ? 0.5 : 1
|
opacity: root.onSpecial ? 0.5 : 1
|
||||||
|
visible: !root.fullscreen
|
||||||
|
|
||||||
layer.enabled: root.blur > 0
|
layer.enabled: root.blur > 0
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
|
|
@ -86,6 +88,7 @@ StyledClippingRect {
|
||||||
activeWsId: root.activeWsId
|
activeWsId: root.activeWsId
|
||||||
workspaces: workspaces
|
workspaces: workspaces
|
||||||
mask: layout
|
mask: layout
|
||||||
|
fullscreen: root.fullscreen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ QtObject {
|
||||||
readonly property string icon: "task_alt"
|
readonly property string icon: "task_alt"
|
||||||
readonly property string component: "taskbar/TaskbarPane.qml"
|
readonly property string component: "taskbar/TaskbarPane.qml"
|
||||||
},
|
},
|
||||||
|
QtObject {
|
||||||
|
readonly property string id: "notifications"
|
||||||
|
readonly property string label: "notifications"
|
||||||
|
readonly property string icon: "notifications"
|
||||||
|
readonly property string component: "notifications/NotificationsPane.qml"
|
||||||
|
},
|
||||||
QtObject {
|
QtObject {
|
||||||
readonly property string id: "launcher"
|
readonly property string id: "launcher"
|
||||||
readonly property string label: "launcher"
|
readonly property string label: "launcher"
|
||||||
|
|
@ -60,7 +66,7 @@ QtObject {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getByIndex(index: int): QtObject {
|
function getByIndex(index: int): var {
|
||||||
if (index >= 0 && index < panes.length) {
|
if (index >= 0 && index < panes.length) {
|
||||||
return panes[index];
|
return panes[index];
|
||||||
}
|
}
|
||||||
|
|
@ -76,12 +82,12 @@ QtObject {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getByLabel(label: string): QtObject {
|
function getByLabel(label: string): var {
|
||||||
const index = getIndexByLabel(label);
|
const index = getIndexByLabel(label);
|
||||||
return getByIndex(index);
|
return getByIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getById(id: string): QtObject {
|
function getById(id: string): var {
|
||||||
for (let i = 0; i < panes.length; i++) {
|
for (let i = 0; i < panes.length; i++) {
|
||||||
if (panes[i].id === id) {
|
if (panes[i].id === id) {
|
||||||
return panes[i];
|
return panes[i];
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import "network"
|
||||||
import "audio"
|
import "audio"
|
||||||
import "appearance"
|
import "appearance"
|
||||||
import "taskbar"
|
import "taskbar"
|
||||||
|
import "notifications"
|
||||||
import "launcher"
|
import "launcher"
|
||||||
import "dashboard"
|
import "dashboard"
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,13 @@ DeviceDetails {
|
||||||
inactiveOnColour: Colours.palette.m3onSecondaryContainer
|
inactiveOnColour: Colours.palette.m3onSecondaryContainer
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
const provider = Config.utilities.vpn.provider[root.vpnProvider.index];
|
||||||
editVpnDialog.editIndex = root.vpnProvider.index;
|
editVpnDialog.editIndex = root.vpnProvider.index;
|
||||||
editVpnDialog.providerName = root.vpnProvider.name;
|
editVpnDialog.providerName = root.vpnProvider.name;
|
||||||
editVpnDialog.displayName = root.vpnProvider.displayName;
|
editVpnDialog.displayName = root.vpnProvider.displayName;
|
||||||
editVpnDialog.interfaceName = root.vpnProvider.interface;
|
editVpnDialog.interfaceName = root.vpnProvider.interface;
|
||||||
|
editVpnDialog.connectCmd = (provider && provider.connectCmd) ? provider.connectCmd.join(" ") : "";
|
||||||
|
editVpnDialog.disconnectCmd = (provider && provider.disconnectCmd) ? provider.disconnectCmd.join(" ") : "";
|
||||||
editVpnDialog.open();
|
editVpnDialog.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,6 +139,30 @@ DeviceDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Appearance.spacing.normal
|
||||||
|
visible: root.providerEnabled && VPN.status.state === "needs-auth" && VPN.status.authUrl !== ""
|
||||||
|
text: qsTr("Open Login Page")
|
||||||
|
inactiveColour: Colours.palette.m3tertiaryContainer
|
||||||
|
inactiveOnColour: Colours.palette.m3onTertiaryContainer
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(VPN.status.authUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Appearance.spacing.normal
|
||||||
|
visible: root.providerEnabled && VPN.status.state === "needs-auth" && VPN.status.authUrl === ""
|
||||||
|
text: qsTr("Click 'Connect' to generate authentication URL")
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -176,12 +203,31 @@ DeviceDetails {
|
||||||
return qsTr("Disabled");
|
return qsTr("Disabled");
|
||||||
if (VPN.connecting)
|
if (VPN.connecting)
|
||||||
return qsTr("Connecting...");
|
return qsTr("Connecting...");
|
||||||
if (VPN.connected)
|
|
||||||
|
switch (VPN.status.state) {
|
||||||
|
case "connected":
|
||||||
return qsTr("Connected");
|
return qsTr("Connected");
|
||||||
return qsTr("Enabled (Not connected)");
|
case "disconnected":
|
||||||
|
return qsTr("Disconnected");
|
||||||
|
case "connecting":
|
||||||
|
return qsTr("Connecting...");
|
||||||
|
case "needs-auth":
|
||||||
|
return qsTr("Authentication required");
|
||||||
|
case "error":
|
||||||
|
return qsTr("Error");
|
||||||
|
default:
|
||||||
|
return qsTr("Unknown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PropertyRow {
|
||||||
|
visible: VPN.status.reason !== ""
|
||||||
|
showTopMargin: true
|
||||||
|
label: qsTr("Details")
|
||||||
|
value: VPN.status.reason
|
||||||
|
}
|
||||||
|
|
||||||
PropertyRow {
|
PropertyRow {
|
||||||
showTopMargin: true
|
showTopMargin: true
|
||||||
label: qsTr("Enabled")
|
label: qsTr("Enabled")
|
||||||
|
|
@ -200,6 +246,8 @@ DeviceDetails {
|
||||||
property string providerName: ""
|
property string providerName: ""
|
||||||
property string displayName: ""
|
property string displayName: ""
|
||||||
property string interfaceName: ""
|
property string interfaceName: ""
|
||||||
|
property string connectCmd: ""
|
||||||
|
property string disconnectCmd: ""
|
||||||
|
|
||||||
function closeWithAnimation(): void {
|
function closeWithAnimation(): void {
|
||||||
close();
|
close();
|
||||||
|
|
@ -349,6 +397,82 @@ DeviceDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.smaller / 2
|
||||||
|
visible: editVpnDialog.connectCmd.length > 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Connect Command")
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
color: connectCmdFieldEdit.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
border.width: 1
|
||||||
|
border.color: connectCmdFieldEdit.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: connectCmdFieldEdit
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Appearance.padding.normal
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
text: editVpnDialog.connectCmd
|
||||||
|
onTextChanged: editVpnDialog.connectCmd = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.smaller / 2
|
||||||
|
visible: editVpnDialog.disconnectCmd.length > 0
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Disconnect Command")
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
color: disconnectCmdFieldEdit.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
border.width: 1
|
||||||
|
border.color: disconnectCmdFieldEdit.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: disconnectCmdFieldEdit
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Appearance.padding.normal
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
text: editVpnDialog.disconnectCmd
|
||||||
|
onTextChanged: editVpnDialog.disconnectCmd = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.topMargin: Appearance.spacing.normal
|
Layout.topMargin: Appearance.spacing.normal
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -376,12 +500,23 @@ DeviceDetails {
|
||||||
|
|
||||||
for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
|
for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
|
||||||
if (i === editVpnDialog.editIndex) {
|
if (i === editVpnDialog.editIndex) {
|
||||||
providers.push({
|
const hasCommands = editVpnDialog.connectCmd.length > 0 && editVpnDialog.disconnectCmd.length > 0;
|
||||||
name: editVpnDialog.providerName,
|
const newProvider = {
|
||||||
displayName: editVpnDialog.displayName || editVpnDialog.interfaceName,
|
displayName: editVpnDialog.displayName || editVpnDialog.interfaceName,
|
||||||
interface: editVpnDialog.interfaceName,
|
enabled: wasEnabled,
|
||||||
enabled: wasEnabled
|
iface: editVpnDialog.interfaceName,
|
||||||
});
|
name: editVpnDialog.providerName,
|
||||||
|
connectCmd: hasCommands ? editVpnDialog.connectCmd.split(" ").filter(s => s.length > 0) : undefined,
|
||||||
|
disconnectCmd: hasCommands ? editVpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0) : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove undefined properties
|
||||||
|
if (!hasCommands) {
|
||||||
|
delete newProvider.connectCmd;
|
||||||
|
delete newProvider.disconnectCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
providers.push(newProvider);
|
||||||
} else {
|
} else {
|
||||||
providers.push(Config.utilities.vpn.provider[i]);
|
providers.push(Config.utilities.vpn.provider[i]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,15 +159,38 @@ ColumnLayout {
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: {
|
text: {
|
||||||
if (modelData.enabled && VPN.connected)
|
if (!modelData.enabled)
|
||||||
return qsTr("Connected");
|
return qsTr("Disabled");
|
||||||
if (modelData.enabled && VPN.connecting)
|
|
||||||
|
if (VPN.connecting)
|
||||||
return qsTr("Connecting...");
|
return qsTr("Connecting...");
|
||||||
if (modelData.enabled)
|
|
||||||
|
switch (VPN.status.state) {
|
||||||
|
case "connected":
|
||||||
|
return qsTr("Connected");
|
||||||
|
case "disconnected":
|
||||||
return qsTr("Enabled");
|
return qsTr("Enabled");
|
||||||
return qsTr("Disabled");
|
case "connecting":
|
||||||
|
return qsTr("Connecting...");
|
||||||
|
case "needs-auth":
|
||||||
|
return qsTr("Auth required");
|
||||||
|
case "error":
|
||||||
|
return qsTr("Error");
|
||||||
|
default:
|
||||||
|
return qsTr("Enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color: {
|
||||||
|
if (!modelData.enabled)
|
||||||
|
return Colours.palette.m3outline;
|
||||||
|
if (VPN.status.state === "connected")
|
||||||
|
return Colours.palette.m3primary;
|
||||||
|
if (VPN.status.state === "error")
|
||||||
|
return Colours.palette.m3error;
|
||||||
|
if (VPN.status.state === "needs-auth")
|
||||||
|
return Colours.palette.m3tertiary;
|
||||||
|
return Colours.palette.m3onSurface;
|
||||||
}
|
}
|
||||||
color: modelData.enabled ? (VPN.connected ? Colours.palette.m3primary : Colours.palette.m3onSurface) : Colours.palette.m3outline
|
|
||||||
font.pointSize: Appearance.font.size.small
|
font.pointSize: Appearance.font.size.small
|
||||||
font.weight: modelData.enabled && VPN.connected ? 500 : 400
|
font.weight: modelData.enabled && VPN.connected ? 500 : 400
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|
@ -271,6 +294,8 @@ ColumnLayout {
|
||||||
property string providerName: ""
|
property string providerName: ""
|
||||||
property string displayName: ""
|
property string displayName: ""
|
||||||
property string interfaceName: ""
|
property string interfaceName: ""
|
||||||
|
property string connectCmd: ""
|
||||||
|
property string disconnectCmd: ""
|
||||||
|
|
||||||
function showProviderSelection(): void {
|
function showProviderSelection(): void {
|
||||||
currentState = "selection";
|
currentState = "selection";
|
||||||
|
|
@ -286,6 +311,8 @@ ColumnLayout {
|
||||||
providerName = providerType;
|
providerName = providerType;
|
||||||
displayName = defaultDisplayName;
|
displayName = defaultDisplayName;
|
||||||
interfaceName = "";
|
interfaceName = "";
|
||||||
|
connectCmd = "";
|
||||||
|
disconnectCmd = "";
|
||||||
|
|
||||||
if (currentState === "selection") {
|
if (currentState === "selection") {
|
||||||
transitionToForm.start();
|
transitionToForm.start();
|
||||||
|
|
@ -304,6 +331,8 @@ ColumnLayout {
|
||||||
providerName = isObject ? (provider.name || "custom") : String(provider);
|
providerName = isObject ? (provider.name || "custom") : String(provider);
|
||||||
displayName = isObject ? (provider.displayName || providerName) : providerName;
|
displayName = isObject ? (provider.displayName || providerName) : providerName;
|
||||||
interfaceName = isObject ? (provider.interface || "") : "";
|
interfaceName = isObject ? (provider.interface || "") : "";
|
||||||
|
connectCmd = isObject && provider.connectCmd ? provider.connectCmd.join(" ") : "";
|
||||||
|
disconnectCmd = isObject && provider.disconnectCmd ? provider.disconnectCmd.join(" ") : "";
|
||||||
|
|
||||||
currentState = "form";
|
currentState = "form";
|
||||||
open();
|
open();
|
||||||
|
|
@ -491,7 +520,7 @@ ColumnLayout {
|
||||||
|
|
||||||
TextButton {
|
TextButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: qsTr("WireGuard (Custom)")
|
text: qsTr("WireGuard")
|
||||||
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
|
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
|
||||||
inactiveOnColour: Colours.palette.m3onSurface
|
inactiveOnColour: Colours.palette.m3onSurface
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
@ -499,6 +528,16 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Custom")
|
||||||
|
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
|
||||||
|
inactiveOnColour: Colours.palette.m3onSurface
|
||||||
|
onClicked: {
|
||||||
|
vpnDialog.showAddForm("custom", "Custom VPN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextButton {
|
TextButton {
|
||||||
Layout.topMargin: Appearance.spacing.normal
|
Layout.topMargin: Appearance.spacing.normal
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -604,6 +643,82 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.smaller / 2
|
||||||
|
visible: vpnDialog.editIndex >= 0 ? (vpnDialog.connectCmd.length > 0) : (vpnDialog.providerName === "custom")
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Connect Command (e.g., wg-quick up wg0)")
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
color: connectCmdField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
border.width: 1
|
||||||
|
border.color: connectCmdField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: connectCmdField
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Appearance.padding.normal
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
text: vpnDialog.connectCmd
|
||||||
|
onTextChanged: vpnDialog.connectCmd = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.smaller / 2
|
||||||
|
visible: vpnDialog.editIndex >= 0 ? (vpnDialog.connectCmd.length > 0) : (vpnDialog.providerName === "custom")
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Disconnect Command (e.g., wg-quick down wg0)")
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: 40
|
||||||
|
color: disconnectCmdField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
border.width: 1
|
||||||
|
border.color: disconnectCmdField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
Behavior on border.color {
|
||||||
|
CAnim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledTextField {
|
||||||
|
id: disconnectCmdField
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - Appearance.padding.normal
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
text: vpnDialog.disconnectCmd
|
||||||
|
onTextChanged: vpnDialog.disconnectCmd = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.topMargin: Appearance.spacing.normal
|
Layout.topMargin: Appearance.spacing.normal
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -620,19 +735,37 @@ ColumnLayout {
|
||||||
TextButton {
|
TextButton {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
enabled: vpnDialog.interfaceName.length > 0
|
enabled: {
|
||||||
|
const hasCommands = vpnDialog.connectCmd.length > 0 || vpnDialog.disconnectCmd.length > 0;
|
||||||
|
if (hasCommands) {
|
||||||
|
return vpnDialog.interfaceName.length > 0 && vpnDialog.connectCmd.length > 0 && vpnDialog.disconnectCmd.length > 0;
|
||||||
|
}
|
||||||
|
return vpnDialog.interfaceName.length > 0;
|
||||||
|
}
|
||||||
inactiveColour: Colours.palette.m3primaryContainer
|
inactiveColour: Colours.palette.m3primaryContainer
|
||||||
inactiveOnColour: Colours.palette.m3onPrimaryContainer
|
inactiveOnColour: Colours.palette.m3onPrimaryContainer
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const providers = [];
|
const providers = [];
|
||||||
|
const hasCommands = vpnDialog.connectCmd.length > 0 && vpnDialog.disconnectCmd.length > 0;
|
||||||
const newProvider = {
|
const newProvider = {
|
||||||
name: vpnDialog.providerName,
|
|
||||||
displayName: vpnDialog.displayName || vpnDialog.interfaceName,
|
displayName: vpnDialog.displayName || vpnDialog.interfaceName,
|
||||||
interface: vpnDialog.interfaceName
|
enabled: false,
|
||||||
|
interface: vpnDialog.interfaceName,
|
||||||
|
name: vpnDialog.providerName
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (hasCommands) {
|
||||||
|
newProvider.connectCmd = vpnDialog.connectCmd.split(" ").filter(s => s.length > 0);
|
||||||
|
newProvider.disconnectCmd = vpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (vpnDialog.editIndex >= 0) {
|
if (vpnDialog.editIndex >= 0) {
|
||||||
|
const oldProvider = Config.utilities.vpn.provider[vpnDialog.editIndex];
|
||||||
|
if (typeof oldProvider === "object" && oldProvider.enabled !== undefined) {
|
||||||
|
newProvider.enabled = oldProvider.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
|
for (let i = 0; i < Config.utilities.vpn.provider.length; i++) {
|
||||||
if (i === vpnDialog.editIndex) {
|
if (i === vpnDialog.editIndex) {
|
||||||
providers.push(newProvider);
|
providers.push(newProvider);
|
||||||
|
|
|
||||||
427
modules/controlcenter/notifications/NotificationsPane.qml
Normal file
427
modules/controlcenter/notifications/NotificationsPane.qml
Normal file
|
|
@ -0,0 +1,427 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import ".."
|
||||||
|
import "../components"
|
||||||
|
import qs.components
|
||||||
|
import qs.components.controls
|
||||||
|
import qs.components.effects
|
||||||
|
import qs.components.containers
|
||||||
|
import qs.services
|
||||||
|
import qs.config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property Session session
|
||||||
|
|
||||||
|
property bool notificationsExpire: Config.notifs.expire ?? true
|
||||||
|
property string notificationsFullscreen: Config.notifs.fullscreen ?? "on"
|
||||||
|
property bool notificationsOpenExpanded: Config.notifs.openExpanded ?? false
|
||||||
|
property int notificationsDefaultExpireTimeout: Config.notifs.defaultExpireTimeout ?? 5000
|
||||||
|
property int notificationsGroupPreviewNum: Config.notifs.groupPreviewNum ?? 3
|
||||||
|
|
||||||
|
property int maxToasts: Config.utilities.maxToasts ?? 4
|
||||||
|
property string toastsFullscreen: Config.utilities.toasts.fullscreen ?? "off"
|
||||||
|
property bool chargingChanged: Config.utilities.toasts.chargingChanged ?? true
|
||||||
|
property bool gameModeChanged: Config.utilities.toasts.gameModeChanged ?? true
|
||||||
|
property bool dndChanged: Config.utilities.toasts.dndChanged ?? true
|
||||||
|
property bool audioOutputChanged: Config.utilities.toasts.audioOutputChanged ?? true
|
||||||
|
property bool audioInputChanged: Config.utilities.toasts.audioInputChanged ?? true
|
||||||
|
property bool capsLockChanged: Config.utilities.toasts.capsLockChanged ?? true
|
||||||
|
property bool numLockChanged: Config.utilities.toasts.numLockChanged ?? true
|
||||||
|
property bool kbLayoutChanged: Config.utilities.toasts.kbLayoutChanged ?? true
|
||||||
|
property bool vpnChanged: Config.utilities.toasts.vpnChanged ?? true
|
||||||
|
property bool nowPlaying: Config.utilities.toasts.nowPlaying ?? false
|
||||||
|
|
||||||
|
function saveConfig(): void {
|
||||||
|
Config.notifs.expire = root.notificationsExpire;
|
||||||
|
Config.notifs.fullscreen = root.notificationsFullscreen;
|
||||||
|
Config.notifs.openExpanded = root.notificationsOpenExpanded;
|
||||||
|
Config.notifs.defaultExpireTimeout = root.notificationsDefaultExpireTimeout;
|
||||||
|
Config.notifs.groupPreviewNum = root.notificationsGroupPreviewNum;
|
||||||
|
|
||||||
|
Config.utilities.maxToasts = root.maxToasts;
|
||||||
|
Config.utilities.toasts.fullscreen = root.toastsFullscreen;
|
||||||
|
Config.utilities.toasts.chargingChanged = root.chargingChanged;
|
||||||
|
Config.utilities.toasts.gameModeChanged = root.gameModeChanged;
|
||||||
|
Config.utilities.toasts.dndChanged = root.dndChanged;
|
||||||
|
Config.utilities.toasts.audioOutputChanged = root.audioOutputChanged;
|
||||||
|
Config.utilities.toasts.audioInputChanged = root.audioInputChanged;
|
||||||
|
Config.utilities.toasts.capsLockChanged = root.capsLockChanged;
|
||||||
|
Config.utilities.toasts.numLockChanged = root.numLockChanged;
|
||||||
|
Config.utilities.toasts.kbLayoutChanged = root.kbLayoutChanged;
|
||||||
|
Config.utilities.toasts.vpnChanged = root.vpnChanged;
|
||||||
|
Config.utilities.toasts.nowPlaying = root.nowPlaying;
|
||||||
|
|
||||||
|
Config.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ClippingRectangle {
|
||||||
|
id: notificationsClippingRect
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
anchors.leftMargin: 0
|
||||||
|
anchors.rightMargin: Appearance.padding.normal
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
radius: notificationsBorder.innerRadius
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: notificationsLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.large + Appearance.padding.normal
|
||||||
|
anchors.leftMargin: Appearance.padding.large
|
||||||
|
anchors.rightMargin: Appearance.padding.large
|
||||||
|
|
||||||
|
sourceComponent: notificationsContentComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerBorder {
|
||||||
|
id: notificationsBorder
|
||||||
|
|
||||||
|
leftThickness: 0
|
||||||
|
rightThickness: Appearance.padding.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notificationsContentComponent
|
||||||
|
|
||||||
|
StyledFlickable {
|
||||||
|
id: notificationsFlickable
|
||||||
|
|
||||||
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
contentHeight: notificationsLayout.height
|
||||||
|
|
||||||
|
StyledScrollBar.vertical: StyledScrollBar {
|
||||||
|
flickable: notificationsFlickable
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: notificationsLayout
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: 500
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
SectionContainer {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
alignTop: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Notifications")
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitButtonRow {
|
||||||
|
id: notificationsFullscreenSelector
|
||||||
|
|
||||||
|
function syncActiveItem(): void {
|
||||||
|
active = root.notificationsFullscreen === "off" ? notificationsFullscreenOffItem : notificationsFullscreenOnItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label: qsTr("Show in fullscreen")
|
||||||
|
menuItems: [notificationsFullscreenOffItem, notificationsFullscreenOnItem]
|
||||||
|
|
||||||
|
Component.onCompleted: syncActiveItem()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onNotificationsFullscreenChanged(): void {
|
||||||
|
notificationsFullscreenSelector.syncActiveItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: root
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: notificationsFullscreenOffItem
|
||||||
|
|
||||||
|
text: qsTr("Off")
|
||||||
|
icon: "notifications_off"
|
||||||
|
activeText: qsTr("Off")
|
||||||
|
onClicked: {
|
||||||
|
root.notificationsFullscreen = "off";
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: notificationsFullscreenOnItem
|
||||||
|
|
||||||
|
text: qsTr("On")
|
||||||
|
icon: "notifications"
|
||||||
|
activeText: qsTr("On")
|
||||||
|
onClicked: {
|
||||||
|
root.notificationsFullscreen = "on";
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
label: qsTr("Expire automatically")
|
||||||
|
checked: root.notificationsExpire
|
||||||
|
onToggled: checked => {
|
||||||
|
root.notificationsExpire = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
label: qsTr("Open expanded")
|
||||||
|
checked: root.notificationsOpenExpanded
|
||||||
|
onToggled: checked => {
|
||||||
|
root.notificationsOpenExpanded = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBoxRow {
|
||||||
|
label: qsTr("Default timeout")
|
||||||
|
value: root.notificationsDefaultExpireTimeout
|
||||||
|
min: 1000
|
||||||
|
max: 60000
|
||||||
|
step: 500
|
||||||
|
onValueModified: value => {
|
||||||
|
root.notificationsDefaultExpireTimeout = value;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBoxRow {
|
||||||
|
label: qsTr("Group preview count")
|
||||||
|
value: root.notificationsGroupPreviewNum
|
||||||
|
min: 1
|
||||||
|
max: 10
|
||||||
|
step: 1
|
||||||
|
onValueModified: value => {
|
||||||
|
root.notificationsGroupPreviewNum = value;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
SectionContainer {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
alignTop: true
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: qsTr("Toast settings")
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitButtonRow {
|
||||||
|
id: toastFullscreenSelector
|
||||||
|
|
||||||
|
function syncActiveItem(): void {
|
||||||
|
if (root.toastsFullscreen === "all") {
|
||||||
|
active = toastFullscreenAllItem;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.toastsFullscreen === "important") {
|
||||||
|
active = toastFullscreenImportantItem;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
active = toastFullscreenOffItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
z: expanded ? 100 : 0
|
||||||
|
label: qsTr("Show in fullscreen")
|
||||||
|
menuItems: [toastFullscreenOffItem, toastFullscreenImportantItem, toastFullscreenAllItem]
|
||||||
|
|
||||||
|
Component.onCompleted: syncActiveItem()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onToastsFullscreenChanged(): void {
|
||||||
|
toastFullscreenSelector.syncActiveItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: root
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: toastFullscreenOffItem
|
||||||
|
|
||||||
|
text: qsTr("Off")
|
||||||
|
icon: "notifications_off"
|
||||||
|
activeText: qsTr("Off")
|
||||||
|
onClicked: {
|
||||||
|
root.toastsFullscreen = "off";
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: toastFullscreenImportantItem
|
||||||
|
|
||||||
|
text: qsTr("Important")
|
||||||
|
icon: "priority_high"
|
||||||
|
activeText: qsTr("Important")
|
||||||
|
onClicked: {
|
||||||
|
root.toastsFullscreen = "important";
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
id: toastFullscreenAllItem
|
||||||
|
|
||||||
|
text: qsTr("On")
|
||||||
|
icon: "notifications"
|
||||||
|
activeText: qsTr("On")
|
||||||
|
onClicked: {
|
||||||
|
root.toastsFullscreen = "all";
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBoxRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Visible toasts")
|
||||||
|
value: root.maxToasts
|
||||||
|
min: 1
|
||||||
|
max: 10
|
||||||
|
step: 1
|
||||||
|
onValueModified: value => {
|
||||||
|
root.maxToasts = value;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
columns: 2
|
||||||
|
columnSpacing: Appearance.spacing.normal
|
||||||
|
rowSpacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Charging changes")
|
||||||
|
checked: root.chargingChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.chargingChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Game mode changes")
|
||||||
|
checked: root.gameModeChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.gameModeChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Do not disturb")
|
||||||
|
checked: root.dndChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.dndChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Audio output changes")
|
||||||
|
checked: root.audioOutputChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.audioOutputChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Audio input changes")
|
||||||
|
checked: root.audioInputChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.audioInputChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Caps lock changes")
|
||||||
|
checked: root.capsLockChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.capsLockChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Num lock changes")
|
||||||
|
checked: root.numLockChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.numLockChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Keyboard layout changes")
|
||||||
|
checked: root.kbLayoutChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.kbLayoutChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("VPN changes")
|
||||||
|
checked: root.vpnChanged
|
||||||
|
onToggled: checked => {
|
||||||
|
root.vpnChanged = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchRow {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: qsTr("Now playing")
|
||||||
|
checked: root.nowPlaying
|
||||||
|
onToggled: checked => {
|
||||||
|
root.nowPlaying = checked;
|
||||||
|
root.saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -358,67 +358,60 @@ Item {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
width: parent.width * heroCard.animatedUsage
|
implicitWidth: parent.width * heroCard.animatedUsage
|
||||||
color: Qt.alpha(heroCard.accentColor, 0.15)
|
color: Qt.alpha(heroCard.accentColor, 0.15)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
CardHeader {
|
||||||
anchors.fill: parent
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
anchors.leftMargin: Appearance.padding.large
|
anchors.leftMargin: Appearance.padding.large
|
||||||
anchors.rightMargin: Appearance.padding.large
|
anchors.topMargin: Math.round(Appearance.padding.large * 1.2)
|
||||||
anchors.topMargin: Appearance.padding.normal
|
|
||||||
anchors.bottomMargin: Appearance.padding.normal
|
width: parent.width - anchors.leftMargin - usageColumn.anchors.rightMargin - usageLabel.width - Appearance.spacing.normal
|
||||||
|
icon: heroCard.icon
|
||||||
|
title: heroCard.title
|
||||||
|
accentColor: heroCard.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Math.round(Appearance.padding.large * 1.2)
|
||||||
|
anchors.bottomMargin: Math.round(Appearance.padding.large * 1.3)
|
||||||
|
|
||||||
spacing: Appearance.spacing.small
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
CardHeader {
|
Row {
|
||||||
icon: heroCard.icon
|
spacing: Appearance.spacing.small
|
||||||
title: heroCard.title
|
|
||||||
accentColor: heroCard.accentColor
|
StyledText {
|
||||||
|
text: heroCard.secondaryValue
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: heroCard.secondaryLabel
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
color: Colours.palette.m3onSurfaceVariant
|
||||||
|
anchors.baseline: parent.children[0].baseline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
ProgressBar {
|
||||||
Layout.fillWidth: true
|
implicitWidth: parent.width * 0.5
|
||||||
Layout.fillHeight: true
|
implicitHeight: 6
|
||||||
spacing: Appearance.spacing.normal
|
value: heroCard.tempProgress
|
||||||
|
fgColor: heroCard.accentColor
|
||||||
Column {
|
bgColor: Qt.alpha(heroCard.accentColor, 0.2)
|
||||||
Layout.alignment: Qt.AlignBottom
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
Row {
|
|
||||||
spacing: Appearance.spacing.small
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: heroCard.secondaryValue
|
|
||||||
font.pointSize: Appearance.font.size.normal
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: heroCard.secondaryLabel
|
|
||||||
font.pointSize: Appearance.font.size.small
|
|
||||||
color: Colours.palette.m3onSurfaceVariant
|
|
||||||
anchors.baseline: parent.children[0].baseline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgressBar {
|
|
||||||
width: parent.width * 0.5
|
|
||||||
height: 6
|
|
||||||
value: heroCard.tempProgress
|
|
||||||
fgColor: heroCard.accentColor
|
|
||||||
bgColor: Qt.alpha(heroCard.accentColor, 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: usageColumn
|
||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.margins: Appearance.padding.large
|
anchors.margins: Appearance.padding.large
|
||||||
|
|
@ -426,6 +419,8 @@ Item {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
id: usageLabel
|
||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
text: heroCard.mainLabel
|
text: heroCard.mainLabel
|
||||||
font.pointSize: Appearance.font.size.normal
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,17 @@ Shape {
|
||||||
|
|
||||||
required property Panels panels
|
required property Panels panels
|
||||||
required property Item bar
|
required property Item bar
|
||||||
|
required property real borderThickness
|
||||||
|
required property real borderRounding
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Config.border.thickness
|
anchors.margins: root.borderThickness
|
||||||
anchors.leftMargin: bar.implicitWidth
|
anchors.leftMargin: bar.implicitWidth
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
Osd.Background {
|
Osd.Background {
|
||||||
wrapper: root.panels.osd // qmllint disable incompatible-type
|
wrapper: root.panels.osd // qmllint disable incompatible-type
|
||||||
|
rounding: Config.border.rounding
|
||||||
|
|
||||||
startX: root.width - root.panels.session.width - root.panels.sidebar.width
|
startX: root.width - root.panels.session.width - root.panels.sidebar.width
|
||||||
startY: (root.height - wrapper.height) / 2 - rounding
|
startY: (root.height - wrapper.height) / 2 - rounding
|
||||||
|
|
@ -31,6 +34,7 @@ Shape {
|
||||||
Notifications.Background {
|
Notifications.Background {
|
||||||
wrapper: root.panels.notifications // qmllint disable incompatible-type
|
wrapper: root.panels.notifications // qmllint disable incompatible-type
|
||||||
sidebar: sidebar
|
sidebar: sidebar
|
||||||
|
rounding: Config.border.rounding
|
||||||
|
|
||||||
startX: root.width
|
startX: root.width
|
||||||
startY: 0
|
startY: 0
|
||||||
|
|
@ -68,6 +72,7 @@ Shape {
|
||||||
Utilities.Background {
|
Utilities.Background {
|
||||||
wrapper: root.panels.utilities // qmllint disable incompatible-type
|
wrapper: root.panels.utilities // qmllint disable incompatible-type
|
||||||
sidebar: sidebar
|
sidebar: sidebar
|
||||||
|
rounding: root.borderRounding
|
||||||
|
|
||||||
startX: root.width
|
startX: root.width
|
||||||
startY: root.height
|
startY: root.height
|
||||||
|
|
@ -78,6 +83,7 @@ Shape {
|
||||||
|
|
||||||
wrapper: root.panels.sidebar // qmllint disable incompatible-type
|
wrapper: root.panels.sidebar // qmllint disable incompatible-type
|
||||||
panels: root.panels
|
panels: root.panels
|
||||||
|
rounding: root.borderRounding
|
||||||
|
|
||||||
startX: root.width
|
startX: root.width
|
||||||
startY: root.panels.notifications.height
|
startY: root.panels.notifications.height
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Item bar
|
required property Item bar
|
||||||
|
required property real borderThickness
|
||||||
|
required property real borderRounding
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
|
@ -36,9 +37,9 @@ Item {
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Config.border.thickness
|
anchors.margins: root.borderThickness
|
||||||
anchors.leftMargin: root.bar.implicitWidth
|
anchors.leftMargin: root.bar.implicitWidth
|
||||||
radius: Config.border.rounding
|
radius: root.borderRounding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,34 @@ Variants {
|
||||||
Exclusions {
|
Exclusions {
|
||||||
screen: scope.modelData
|
screen: scope.modelData
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: Config.border.thickness
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledWindow {
|
StyledWindow {
|
||||||
id: win
|
id: win
|
||||||
|
|
||||||
readonly property bool hasFullscreen: Hypr.monitorFor(screen)?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) ?? false
|
readonly property var monitor: Hypr.monitorFor(screen)
|
||||||
|
readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject?.specialWorkspace?.name.length ?? 0) > 0
|
||||||
|
readonly property bool hasFullscreen: {
|
||||||
|
if (hasSpecialWorkspace) {
|
||||||
|
const specialName = monitor?.lastIpcObject?.specialWorkspace?.name;
|
||||||
|
if (!specialName)
|
||||||
|
return false;
|
||||||
|
const specialWs = Hypr.workspaces.values.find(ws => ws.name === specialName);
|
||||||
|
return specialWs?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
|
||||||
|
}
|
||||||
|
return monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false;
|
||||||
|
}
|
||||||
|
property real borderThickness: hasFullscreen ? 0 : Config.border.thickness
|
||||||
|
readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.border.thickness
|
||||||
|
property real borderRounding: hasFullscreen ? 0 : Config.border.rounding
|
||||||
|
property real shadowOpacity: hasFullscreen ? 0 : 0.7
|
||||||
readonly property int dragMaskPadding: {
|
readonly property int dragMaskPadding: {
|
||||||
if (focusGrab.active || panels.popouts.isDetached)
|
if (focusGrab.active || panels.popouts.isDetached)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
const mon = Hypr.monitorFor(screen);
|
const mon = Hypr.monitorFor(screen);
|
||||||
if (mon?.lastIpcObject.specialWorkspace?.name || mon?.activeWorkspace?.lastIpcObject.windows > 0)
|
if (mon?.lastIpcObject.specialWorkspace?.name || mon?.activeWorkspace.lastIpcObject.windows > 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
const thresholds = [];
|
const thresholds = [];
|
||||||
|
|
@ -56,6 +72,7 @@ Variants {
|
||||||
screen: scope.modelData
|
screen: scope.modelData
|
||||||
name: "drawers"
|
name: "drawers"
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.session || panels.dashboard.needsKeyboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: visibilities.launcher || visibilities.session || panels.dashboard.needsKeyboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
|
|
||||||
mask: Regions {
|
mask: Regions {
|
||||||
|
|
@ -69,6 +86,30 @@ Variants {
|
||||||
anchors.left: true
|
anchors.left: true
|
||||||
anchors.right: true
|
anchors.right: true
|
||||||
|
|
||||||
|
Behavior on borderThickness {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on borderRounding {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on shadowOpacity {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
|
|
||||||
|
|
@ -101,7 +142,7 @@ Variants {
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
shadowEnabled: true
|
shadowEnabled: true
|
||||||
blurMax: 15
|
blurMax: 15
|
||||||
shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
|
shadowColor: Qt.alpha(Colours.palette.m3shadow, Math.max(0, win.shadowOpacity))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Border {
|
// Border {
|
||||||
|
|
@ -127,11 +168,11 @@ Variants {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -50 // Make border thicker to smooth out bulge from closed drawers
|
anchors.margins: -50 // Make border thicker to smooth out bulge from closed drawers
|
||||||
group: blobGroup
|
group: blobGroup
|
||||||
radius: Config.border.rounding
|
radius: win.borderRounding
|
||||||
borderLeft: bar.implicitWidth - anchors.margins
|
borderLeft: bar.implicitWidth - anchors.margins
|
||||||
borderRight: Config.border.thickness - anchors.margins
|
borderRight: win.borderThickness - anchors.margins
|
||||||
borderTop: Config.border.thickness - anchors.margins
|
borderTop: win.borderThickness - anchors.margins
|
||||||
borderBottom: Config.border.thickness - anchors.margins
|
borderBottom: win.borderThickness - anchors.margins
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelBg {
|
PanelBg {
|
||||||
|
|
@ -140,6 +181,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.dashboard
|
panel: panels.dashboard
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: 0.1
|
deformAmount: 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,6 +191,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.launcher
|
panel: panels.launcher
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: 0.1
|
deformAmount: 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,6 +201,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.sessionWrapper
|
panel: panels.sessionWrapper
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: 0.25
|
deformAmount: 0.25
|
||||||
x: panels.sessionWrapper.x + panels.session.x + bar.implicitWidth
|
x: panels.sessionWrapper.x + panels.session.x + bar.implicitWidth
|
||||||
implicitWidth: panels.session.width
|
implicitWidth: panels.session.width
|
||||||
|
|
@ -169,6 +213,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.sidebar
|
panel: panels.sidebar
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: 0.05
|
deformAmount: 0.05
|
||||||
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
|
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
|
||||||
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
|
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
|
||||||
|
|
@ -181,6 +226,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.osdWrapper
|
panel: panels.osdWrapper
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: 0.3
|
deformAmount: 0.3
|
||||||
x: panels.osdWrapper.x + panels.osd.x + bar.implicitWidth
|
x: panels.osdWrapper.x + panels.osd.x + bar.implicitWidth
|
||||||
implicitWidth: panels.osd.width
|
implicitWidth: panels.osd.width
|
||||||
|
|
@ -192,6 +238,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.notifications
|
panel: panels.notifications
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelBg {
|
PanelBg {
|
||||||
|
|
@ -200,6 +247,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.utilities
|
panel: panels.utilities
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
deformAmount: panels.sidebar.visible ? 0.1 : 0.15
|
deformAmount: panels.sidebar.visible ? 0.1 : 0.15
|
||||||
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
|
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
|
||||||
topLeftRadius: Math.max(0, Math.min(1, panels.sidebar.offsetScale / 0.3)) * radius
|
topLeftRadius: Math.max(0, Math.min(1, panels.sidebar.offsetScale / 0.3)) * radius
|
||||||
|
|
@ -211,6 +259,7 @@ Variants {
|
||||||
blobGroup: blobGroup
|
blobGroup: blobGroup
|
||||||
panel: panels.popouts
|
panel: panels.popouts
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
|
|
||||||
x: bar.implicitWidth - (panels.popouts.isDetached ? -(win.width - panels.popouts.shownWidth) / 2 : panels.popouts.hasCurrent ? 0 : panels.popouts.shownWidth + 5)
|
x: bar.implicitWidth - (panels.popouts.isDetached ? -(win.width - panels.popouts.shownWidth) / 2 : panels.popouts.hasCurrent ? 0 : panels.popouts.shownWidth + 5)
|
||||||
implicitWidth: panels.popouts.shownWidth
|
implicitWidth: panels.popouts.shownWidth
|
||||||
|
|
@ -243,6 +292,8 @@ Variants {
|
||||||
visibilities: visibilities
|
visibilities: visibilities
|
||||||
panels: panels
|
panels: panels
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderLayoutThickness
|
||||||
|
fullscreen: win.hasFullscreen
|
||||||
|
|
||||||
Panels {
|
Panels {
|
||||||
id: panels
|
id: panels
|
||||||
|
|
@ -250,6 +301,7 @@ Variants {
|
||||||
screen: scope.modelData
|
screen: scope.modelData
|
||||||
visibilities: visibilities
|
visibilities: visibilities
|
||||||
bar: bar
|
bar: bar
|
||||||
|
borderThickness: win.borderThickness
|
||||||
|
|
||||||
utilities.horizontalStretch: (sidebarBg.rawDeformMatrix.m11 - 1) / 2 + 1
|
utilities.horizontalStretch: (sidebarBg.rawDeformMatrix.m11 - 1) / 2 + 1
|
||||||
|
|
||||||
|
|
@ -290,6 +342,7 @@ Variants {
|
||||||
popouts: panels.popouts
|
popouts: panels.popouts
|
||||||
|
|
||||||
disabled: scope.barDisabled
|
disabled: scope.barDisabled
|
||||||
|
fullscreen: win.hasFullscreen
|
||||||
|
|
||||||
Component.onCompleted: Visibilities.bars.set(scope.modelData, this)
|
Component.onCompleted: Visibilities.bars.set(scope.modelData, this)
|
||||||
}
|
}
|
||||||
|
|
@ -301,11 +354,12 @@ Variants {
|
||||||
required property BlobGroup blobGroup
|
required property BlobGroup blobGroup
|
||||||
required property Item panel
|
required property Item panel
|
||||||
required property Item bar
|
required property Item bar
|
||||||
|
required property real borderThickness
|
||||||
property real deformAmount: 0.15
|
property real deformAmount: 0.15
|
||||||
|
|
||||||
group: panel.width > 0 && panel.height > 0 ? blobGroup : null
|
group: panel.width > 0 && panel.height > 0 ? blobGroup : null
|
||||||
x: panel.x + bar.implicitWidth
|
x: panel.x + bar.implicitWidth
|
||||||
y: panel.y + Config.border.thickness
|
y: panel.y + borderThickness
|
||||||
implicitWidth: panel.width
|
implicitWidth: panel.width
|
||||||
implicitHeight: panel.height
|
implicitHeight: panel.height
|
||||||
radius: Config.border.rounding
|
radius: Config.border.rounding
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.components.containers
|
import qs.components.containers
|
||||||
import qs.config
|
|
||||||
import qs.modules.bar as Bar
|
import qs.modules.bar as Bar
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
|
|
@ -11,6 +10,7 @@ Scope {
|
||||||
|
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
required property Bar.BarWrapper bar
|
required property Bar.BarWrapper bar
|
||||||
|
required property real borderThickness
|
||||||
|
|
||||||
ExclusionZone {
|
ExclusionZone {
|
||||||
anchors.left: true
|
anchors.left: true
|
||||||
|
|
@ -32,7 +32,7 @@ Scope {
|
||||||
component ExclusionZone: StyledWindow {
|
component ExclusionZone: StyledWindow {
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
name: "border-exclusion"
|
name: "border-exclusion"
|
||||||
exclusiveZone: Config.border.thickness
|
exclusiveZone: root.borderThickness
|
||||||
mask: Region {}
|
mask: Region {}
|
||||||
implicitWidth: 1
|
implicitWidth: 1
|
||||||
implicitHeight: 1
|
implicitHeight: 1
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ CustomMouseArea {
|
||||||
required property DrawerVisibilities visibilities
|
required property DrawerVisibilities visibilities
|
||||||
required property Panels panels
|
required property Panels panels
|
||||||
required property Bar.BarWrapper bar
|
required property Bar.BarWrapper bar
|
||||||
|
required property real borderThickness
|
||||||
|
required property bool fullscreen
|
||||||
|
|
||||||
property point dragStart
|
property point dragStart
|
||||||
property bool dashboardShortcutActive
|
property bool dashboardShortcutActive
|
||||||
|
|
@ -22,7 +24,7 @@ CustomMouseArea {
|
||||||
property bool utilitiesShortcutActive
|
property bool utilitiesShortcutActive
|
||||||
|
|
||||||
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
||||||
const panelY = Config.border.thickness + panel.y;
|
const panelY = root.borderThickness + panel.y;
|
||||||
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
|
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,13 +51,16 @@ CustomMouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWheel(event: WheelEvent): void {
|
function onWheel(event: WheelEvent): void {
|
||||||
|
if (fullscreen)
|
||||||
|
return;
|
||||||
if (event.x < bar.implicitWidth) {
|
if (event.x < bar.implicitWidth) {
|
||||||
bar.handleWheel(event.y, event.angleDelta);
|
bar.handleWheel(event.y, event.angleDelta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
acceptedButtons: fullscreen ? Qt.NoButton : Qt.AllButtons
|
||||||
|
hoverEnabled: !fullscreen
|
||||||
|
|
||||||
onPressed: event => dragStart = Qt.point(event.x, event.y)
|
onPressed: event => dragStart = Qt.point(event.x, event.y)
|
||||||
onContainsMouseChanged: {
|
onContainsMouseChanged: {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ Item {
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
required property DrawerVisibilities visibilities
|
required property DrawerVisibilities visibilities
|
||||||
required property Bar.BarWrapper bar
|
required property Bar.BarWrapper bar
|
||||||
|
required property real borderThickness
|
||||||
|
|
||||||
readonly property alias osd: osd
|
readonly property alias osd: osd
|
||||||
readonly property alias osdWrapper: osdWrapper
|
readonly property alias osdWrapper: osdWrapper
|
||||||
|
|
@ -33,7 +34,7 @@ Item {
|
||||||
readonly property alias sidebar: sidebar
|
readonly property alias sidebar: sidebar
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Config.border.thickness
|
anchors.margins: root.borderThickness
|
||||||
anchors.leftMargin: bar.implicitWidth
|
anchors.leftMargin: bar.implicitWidth
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
@ -123,7 +124,7 @@ Item {
|
||||||
if (isDetached)
|
if (isDetached)
|
||||||
return (root.height - nonAnimHeight) / 2;
|
return (root.height - nonAnimHeight) / 2;
|
||||||
|
|
||||||
const off = currentCenter - Config.border.thickness - nonAnimHeight / 2;
|
const off = currentCenter - root.borderThickness - nonAnimHeight / 2;
|
||||||
const diff = root.height - Math.floor(off + nonAnimHeight);
|
const diff = root.height - Math.floor(off + nonAnimHeight);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
required property var sidebar
|
required property var sidebar
|
||||||
readonly property real rounding: Config.border.rounding
|
required property real rounding
|
||||||
readonly property bool flatten: wrapper.height < rounding * 2
|
readonly property bool flatten: wrapper.height < rounding * 2
|
||||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||||
|
|
||||||
|
|
@ -31,14 +30,14 @@ ShapePath {
|
||||||
relativeY: root.wrapper.height - root.roundingY * 2
|
relativeY: root.wrapper.height - root.roundingY * 2
|
||||||
}
|
}
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: root.sidebar.notifsRoundingX
|
relativeX: root.rounding
|
||||||
relativeY: root.roundingY
|
relativeY: root.roundingY
|
||||||
radiusX: root.sidebar.notifsRoundingX
|
radiusX: root.rounding
|
||||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||||
direction: PathArc.Counterclockwise
|
direction: PathArc.Counterclockwise
|
||||||
}
|
}
|
||||||
PathLine {
|
PathLine {
|
||||||
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
|
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding * 2 : root.wrapper.width
|
||||||
relativeY: 0
|
relativeY: 0
|
||||||
}
|
}
|
||||||
PathArc {
|
PathArc {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
readonly property real rounding: Config.border.rounding
|
required property real rounding
|
||||||
readonly property bool flatten: wrapper.width < rounding * 2
|
readonly property bool flatten: wrapper.width < rounding * 2
|
||||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,13 @@ import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
required property var panels
|
required property var panels
|
||||||
|
required property real rounding
|
||||||
readonly property real rounding: Config.border.rounding
|
|
||||||
|
|
||||||
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
|
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
|
||||||
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
|
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
import qs.components
|
import qs.components
|
||||||
import qs.services
|
import qs.services
|
||||||
import qs.config
|
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
required property var sidebar
|
required property var sidebar
|
||||||
readonly property real rounding: Config.border.rounding
|
required property real rounding
|
||||||
readonly property bool flatten: wrapper.height < rounding * 2
|
readonly property bool flatten: wrapper.height < rounding * 2
|
||||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,8 +141,10 @@ StyledRect {
|
||||||
roleValue: "vpn"
|
roleValue: "vpn"
|
||||||
delegate: Toggle {
|
delegate: Toggle {
|
||||||
icon: "vpn_key"
|
icon: "vpn_key"
|
||||||
checked: VPN.connected
|
checked: VPN.connected && VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
|
||||||
enabled: !VPN.connecting
|
enabled: !VPN.connecting
|
||||||
|
toggle: VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
|
||||||
|
inactiveOnColour: Colours.palette.m3onSurfaceVariant
|
||||||
onClicked: VPN.toggle()
|
onClicked: VPN.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Caelestia
|
import Caelestia
|
||||||
import qs.components
|
import qs.components
|
||||||
|
import qs.services
|
||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
@ -12,6 +13,16 @@ Item {
|
||||||
readonly property int spacing: Appearance.spacing.small
|
readonly property int spacing: Appearance.spacing.small
|
||||||
property bool flag
|
property bool flag
|
||||||
|
|
||||||
|
function shouldShowToast(toast: Toast): bool {
|
||||||
|
if (!Notifs.hasFullscreen())
|
||||||
|
return true;
|
||||||
|
if (Config.utilities.toasts.fullscreen === "all")
|
||||||
|
return true;
|
||||||
|
if (Config.utilities.toasts.fullscreen === "important")
|
||||||
|
return toast.type === Toast.Warning || toast.type === Toast.Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2
|
implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
let h = -spacing;
|
let h = -spacing;
|
||||||
|
|
@ -31,6 +42,8 @@ Item {
|
||||||
const toasts = [];
|
const toasts = [];
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const toast of Toaster.toasts) {
|
for (const toast of Toaster.toasts) {
|
||||||
|
if (!root.shouldShowToast(toast))
|
||||||
|
continue;
|
||||||
toasts.push(toast);
|
toasts.push(toast);
|
||||||
if (!toast.closed) {
|
if (!toast.closed) {
|
||||||
count++;
|
count++;
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,6 @@ in
|
||||||
prePatch = ''
|
prePatch = ''
|
||||||
substituteInPlace assets/pam.d/fprint \
|
substituteInPlace assets/pam.d/fprint \
|
||||||
--replace-fail pam_fprintd.so /run/current-system/sw/lib/security/pam_fprintd.so
|
--replace-fail pam_fprintd.so /run/current-system/sw/lib/security/pam_fprintd.so
|
||||||
substituteInPlace shell.qml \
|
|
||||||
--replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false'
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ qml_module(caelestia-internal
|
||||||
hyprextras.hpp hyprextras.cpp
|
hyprextras.hpp hyprextras.cpp
|
||||||
logindmanager.hpp logindmanager.cpp
|
logindmanager.hpp logindmanager.cpp
|
||||||
sparklineitem.hpp sparklineitem.cpp
|
sparklineitem.hpp sparklineitem.cpp
|
||||||
|
visualiserbars.hpp visualiserbars.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
|
|
||||||
198
plugin/src/Caelestia/Internal/visualiserbars.cpp
Normal file
198
plugin/src/Caelestia/Internal/visualiserbars.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
#include "visualiserbars.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <qbrush.h>
|
||||||
|
#include <qpainter.h>
|
||||||
|
#include <qpainterpath.h>
|
||||||
|
#include <qpen.h>
|
||||||
|
|
||||||
|
namespace caelestia::internal {
|
||||||
|
|
||||||
|
VisualiserBars::VisualiserBars(QQuickItem* parent)
|
||||||
|
: QQuickPaintedItem(parent) {
|
||||||
|
setAntialiasing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::advance(qreal dt) {
|
||||||
|
if (m_displayValues.isEmpty() || m_settled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// dt is in seconds (from FrameAnimation.frameTime), convert to ms
|
||||||
|
const qreal dtMs = dt * 1000.0;
|
||||||
|
const qreal tau = m_animationDuration / 3.0;
|
||||||
|
const qreal alpha = 1.0 - std::exp(-dtMs / tau);
|
||||||
|
|
||||||
|
bool allSettled = true;
|
||||||
|
|
||||||
|
for (qsizetype i = 0; i < m_displayValues.size(); ++i) {
|
||||||
|
const double diff = m_targetValues[i] - m_displayValues[i];
|
||||||
|
|
||||||
|
if (std::abs(diff) > 0.001) {
|
||||||
|
m_displayValues[i] += diff * alpha;
|
||||||
|
allSettled = false;
|
||||||
|
} else {
|
||||||
|
m_displayValues[i] = m_targetValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
if (allSettled && !m_settled) {
|
||||||
|
m_settled = true;
|
||||||
|
emit settledChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::paint(QPainter* painter) {
|
||||||
|
if (m_displayValues.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
|
||||||
|
const qreal h = height();
|
||||||
|
const qreal maxBarHeight = h * 0.4;
|
||||||
|
|
||||||
|
QLinearGradient gradient(0, h - maxBarHeight, 0, h);
|
||||||
|
gradient.setColorAt(0, m_primaryColor);
|
||||||
|
gradient.setColorAt(1, m_secondaryColor);
|
||||||
|
painter->setBrush(gradient);
|
||||||
|
|
||||||
|
drawSide(painter, false);
|
||||||
|
drawSide(painter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::drawSide(QPainter* painter, bool rightSide) {
|
||||||
|
const qreal w = width();
|
||||||
|
const qreal h = height();
|
||||||
|
const auto count = m_displayValues.size();
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal sideWidth = w * 0.4;
|
||||||
|
const qreal slotWidth = sideWidth / static_cast<qreal>(count);
|
||||||
|
const qreal barWidth = slotWidth - m_spacing;
|
||||||
|
|
||||||
|
if (barWidth <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal sideOffset = rightSide ? w * 0.6 : 0;
|
||||||
|
const qreal maxBarHeight = h * 0.4;
|
||||||
|
|
||||||
|
for (qsizetype i = 0; i < count; ++i) {
|
||||||
|
const qsizetype valueIndex = rightSide ? i : (count - i - 1);
|
||||||
|
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
|
||||||
|
const qreal barHeight = value * maxBarHeight;
|
||||||
|
|
||||||
|
if (barHeight <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
|
||||||
|
const qreal y = h - barHeight;
|
||||||
|
const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight });
|
||||||
|
|
||||||
|
QPainterPath path;
|
||||||
|
path.moveTo(x, h);
|
||||||
|
path.lineTo(x, y + r);
|
||||||
|
|
||||||
|
if (r > 0) {
|
||||||
|
path.arcTo(x, y, r * 2, r * 2, 180, -90);
|
||||||
|
path.lineTo(x + barWidth - r, y);
|
||||||
|
path.arcTo(x + barWidth - r * 2, y, r * 2, r * 2, 90, -90);
|
||||||
|
} else {
|
||||||
|
path.lineTo(x, y);
|
||||||
|
path.lineTo(x + barWidth, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.lineTo(x + barWidth, h);
|
||||||
|
path.closeSubpath();
|
||||||
|
|
||||||
|
painter->drawPath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<double> VisualiserBars::values() const {
|
||||||
|
return m_targetValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setValues(const QVector<double>& values) {
|
||||||
|
m_targetValues = values;
|
||||||
|
|
||||||
|
if (m_displayValues.size() != values.size()) {
|
||||||
|
m_displayValues.resize(values.size(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settled) {
|
||||||
|
m_settled = false;
|
||||||
|
emit settledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit valuesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisualiserBars::settled() const {
|
||||||
|
return m_settled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor VisualiserBars::primaryColor() const {
|
||||||
|
return m_primaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setPrimaryColor(const QColor& color) {
|
||||||
|
if (m_primaryColor == color)
|
||||||
|
return;
|
||||||
|
m_primaryColor = color;
|
||||||
|
emit primaryColorChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor VisualiserBars::secondaryColor() const {
|
||||||
|
return m_secondaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setSecondaryColor(const QColor& color) {
|
||||||
|
if (m_secondaryColor == color)
|
||||||
|
return;
|
||||||
|
m_secondaryColor = color;
|
||||||
|
emit secondaryColorChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal VisualiserBars::rounding() const {
|
||||||
|
return m_rounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setRounding(qreal rounding) {
|
||||||
|
if (qFuzzyCompare(m_rounding, rounding))
|
||||||
|
return;
|
||||||
|
m_rounding = rounding;
|
||||||
|
emit roundingChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal VisualiserBars::spacing() const {
|
||||||
|
return m_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setSpacing(qreal spacing) {
|
||||||
|
if (qFuzzyCompare(m_spacing, spacing))
|
||||||
|
return;
|
||||||
|
m_spacing = spacing;
|
||||||
|
emit spacingChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
int VisualiserBars::animationDuration() const {
|
||||||
|
return m_animationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualiserBars::setAnimationDuration(int duration) {
|
||||||
|
if (m_animationDuration == duration)
|
||||||
|
return;
|
||||||
|
m_animationDuration = duration;
|
||||||
|
emit animationDurationChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace caelestia::internal
|
||||||
72
plugin/src/Caelestia/Internal/visualiserbars.hpp
Normal file
72
plugin/src/Caelestia/Internal/visualiserbars.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qquickpainteditem.h>
|
||||||
|
#include <qvector.h>
|
||||||
|
|
||||||
|
namespace caelestia::internal {
|
||||||
|
|
||||||
|
class VisualiserBars : public QQuickPaintedItem {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
|
||||||
|
Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged)
|
||||||
|
Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged)
|
||||||
|
Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
|
||||||
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
|
Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
|
||||||
|
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VisualiserBars(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void advance(qreal dt);
|
||||||
|
|
||||||
|
[[nodiscard]] QVector<double> values() const;
|
||||||
|
void setValues(const QVector<double>& values);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor primaryColor() const;
|
||||||
|
void setPrimaryColor(const QColor& color);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor secondaryColor() const;
|
||||||
|
void setSecondaryColor(const QColor& color);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal rounding() const;
|
||||||
|
void setRounding(qreal rounding);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal spacing() const;
|
||||||
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
|
[[nodiscard]] int animationDuration() const;
|
||||||
|
void setAnimationDuration(int duration);
|
||||||
|
|
||||||
|
[[nodiscard]] bool settled() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valuesChanged();
|
||||||
|
void primaryColorChanged();
|
||||||
|
void secondaryColorChanged();
|
||||||
|
void roundingChanged();
|
||||||
|
void spacingChanged();
|
||||||
|
void animationDurationChanged();
|
||||||
|
void settledChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawSide(QPainter* painter, bool rightSide);
|
||||||
|
|
||||||
|
QVector<double> m_targetValues;
|
||||||
|
QVector<double> m_displayValues;
|
||||||
|
QColor m_primaryColor;
|
||||||
|
QColor m_secondaryColor;
|
||||||
|
qreal m_rounding = 0.0;
|
||||||
|
qreal m_spacing = 0.0;
|
||||||
|
int m_animationDuration = 200;
|
||||||
|
bool m_settled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace caelestia::internal
|
||||||
|
|
@ -21,6 +21,22 @@ Singleton {
|
||||||
|
|
||||||
property bool loaded
|
property bool loaded
|
||||||
|
|
||||||
|
function hasFullscreen(): bool {
|
||||||
|
for (const monitor of Hypr.monitors.values) {
|
||||||
|
if (monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowPopup(): bool {
|
||||||
|
if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar))
|
||||||
|
return false;
|
||||||
|
if (Config.notifs.fullscreen === "off" && hasFullscreen())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
onDndChanged: {
|
onDndChanged: {
|
||||||
if (!Config.utilities.toasts.dndChanged)
|
if (!Config.utilities.toasts.dndChanged)
|
||||||
return;
|
return;
|
||||||
|
|
@ -79,7 +95,7 @@ Singleton {
|
||||||
notif.tracked = true;
|
notif.tracked = true;
|
||||||
|
|
||||||
const comp = notifComp.createObject(root, {
|
const comp = notifComp.createObject(root, {
|
||||||
popup: !props.dnd && ![...Visibilities.screens.values()].some(v => v.sidebar),
|
popup: root.shouldShowPopup(),
|
||||||
notification: notif
|
notification: notif
|
||||||
});
|
});
|
||||||
root.list = [comp, ...root.list];
|
root.list = [comp, ...root.list];
|
||||||
|
|
|
||||||
334
services/VPN.qml
334
services/VPN.qml
|
|
@ -10,6 +10,12 @@ Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool connected: false
|
property bool connected: false
|
||||||
|
property var status: ({
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
})
|
||||||
|
|
||||||
readonly property bool connecting: connectProc.running || disconnectProc.running
|
readonly property bool connecting: connectProc.running || disconnectProc.running
|
||||||
readonly property bool enabled: Config.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false)
|
readonly property bool enabled: Config.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false)
|
||||||
|
|
@ -19,7 +25,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
readonly property bool isCustomProvider: typeof providerInput === "object"
|
readonly property bool isCustomProvider: typeof providerInput === "object"
|
||||||
readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput)
|
readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput)
|
||||||
readonly property string interfaceName: isCustomProvider ? (providerInput.interface || "") : ""
|
readonly property string interfaceName: isCustomProvider ? (providerInput.iface || "") : ""
|
||||||
readonly property var currentConfig: {
|
readonly property var currentConfig: {
|
||||||
const name = providerName;
|
const name = providerName;
|
||||||
const iface = interfaceName;
|
const iface = interfaceName;
|
||||||
|
|
@ -30,7 +36,7 @@ Singleton {
|
||||||
return {
|
return {
|
||||||
connectCmd: custom.connectCmd || defaults.connectCmd,
|
connectCmd: custom.connectCmd || defaults.connectCmd,
|
||||||
disconnectCmd: custom.disconnectCmd || defaults.disconnectCmd,
|
disconnectCmd: custom.disconnectCmd || defaults.disconnectCmd,
|
||||||
interface: custom.interface || defaults.interface,
|
interface: custom.iface || defaults.interface,
|
||||||
displayName: custom.displayName || defaults.displayName
|
displayName: custom.displayName || defaults.displayName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +59,7 @@ Singleton {
|
||||||
displayName: "Warp"
|
displayName: "Warp"
|
||||||
},
|
},
|
||||||
"netbird": {
|
"netbird": {
|
||||||
connectCmd: ["netbird", "up"],
|
connectCmd: ["netbird", "up", "--no-browser"],
|
||||||
disconnectCmd: ["netbird", "down"],
|
disconnectCmd: ["netbird", "down"],
|
||||||
interface: "wt0",
|
interface: "wt0",
|
||||||
displayName: "NetBird"
|
displayName: "NetBird"
|
||||||
|
|
@ -75,6 +81,10 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect(): void {
|
function connect(): void {
|
||||||
|
if (status.state === "needs-auth" && status.authUrl) {
|
||||||
|
emitStatusToast(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!connected && !connecting && root.currentConfig && root.currentConfig.connectCmd) {
|
if (!connected && !connecting && root.currentConfig && root.currentConfig.connectCmd) {
|
||||||
connectProc.exec(root.currentConfig.connectCmd);
|
connectProc.exec(root.currentConfig.connectCmd);
|
||||||
}
|
}
|
||||||
|
|
@ -87,11 +97,7 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle(): void {
|
function toggle(): void {
|
||||||
if (connected) {
|
connected ? disconnect() : connect();
|
||||||
disconnect();
|
|
||||||
} else {
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkStatus(): void {
|
function checkStatus(): void {
|
||||||
|
|
@ -100,18 +106,223 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnectedChanged: {
|
function getStatusCommand(): var {
|
||||||
|
switch (providerName) {
|
||||||
|
case "tailscale":
|
||||||
|
return ["tailscale", "status", "--json"];
|
||||||
|
case "netbird":
|
||||||
|
return ["netbird", "status", "--json"];
|
||||||
|
case "warp":
|
||||||
|
return ["warp-cli", "status"];
|
||||||
|
case "wireguard":
|
||||||
|
return ["ip", "link", "show"];
|
||||||
|
default:
|
||||||
|
return ["ip", "link", "show"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTailscaleStatus(output: string): var {
|
||||||
|
const status = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle empty or whitespace-only output
|
||||||
|
if (!output || output.trim().length === 0) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for common non-JSON states first
|
||||||
|
if (output.includes("Logged out") || output.includes("Stopped") || output.includes("not running") || output.includes("Tailscale is not running")) {
|
||||||
|
status.state = "disconnected";
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as JSON
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(output);
|
||||||
|
const backendState = data.BackendState || "";
|
||||||
|
|
||||||
|
if (backendState === "Running") {
|
||||||
|
status.connected = true;
|
||||||
|
status.state = "connected";
|
||||||
|
} else if (backendState === "Starting") {
|
||||||
|
status.state = "connecting";
|
||||||
|
} else if (backendState === "NeedsLogin" || backendState === "NeedsMachineAuth") {
|
||||||
|
status.state = "needs-auth";
|
||||||
|
status.reason = backendState === "NeedsLogin" ? "Login required" : "Machine authorization required";
|
||||||
|
status.authUrl = data.AuthURL || "";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// JSON parsing failed - treat as disconnected unless it looks like an error
|
||||||
|
if (output.includes("error") || output.includes("Error") || output.includes("failed")) {
|
||||||
|
status.state = "disconnected";
|
||||||
|
status.reason = "Tailscale may not be running";
|
||||||
|
} else {
|
||||||
|
status.state = "disconnected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNetBirdStatus(output: string): var {
|
||||||
|
const status = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(output);
|
||||||
|
const mgmtConnected = data.management?.connected;
|
||||||
|
const signalConnected = data.signal?.connected;
|
||||||
|
|
||||||
|
if (mgmtConnected && signalConnected) {
|
||||||
|
status.connected = true;
|
||||||
|
status.state = "connected";
|
||||||
|
} else if (data.management?.error) {
|
||||||
|
const error = data.management.error;
|
||||||
|
if (error.includes("auth") || error.includes("login")) {
|
||||||
|
status.state = "needs-auth";
|
||||||
|
status.reason = "Authentication required";
|
||||||
|
} else {
|
||||||
|
status.reason = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
status.state = "error";
|
||||||
|
status.reason = "Failed to parse status";
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWarpStatus(output: string): var {
|
||||||
|
const status = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
if (output.includes("Connected")) {
|
||||||
|
status.connected = true;
|
||||||
|
status.state = "connected";
|
||||||
|
} else if (output.includes("Connecting")) {
|
||||||
|
status.state = "connecting";
|
||||||
|
} else if (output.includes("Unable") || output.includes("Registration Missing") || output.includes("registration") || output.includes("register")) {
|
||||||
|
status.state = "needs-auth";
|
||||||
|
status.reason = "WARP registration required";
|
||||||
|
} else if (!output.includes("Disconnected")) {
|
||||||
|
status.state = "error";
|
||||||
|
status.reason = "Unknown WARP status";
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWireGuardStatus(output: string): var {
|
||||||
|
const status = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
const iface = root.currentConfig?.interface || "";
|
||||||
|
|
||||||
|
if (iface && output.includes(iface + ":")) {
|
||||||
|
status.connected = true;
|
||||||
|
status.state = "connected";
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStatusOutput(output: string): var {
|
||||||
|
switch (providerName) {
|
||||||
|
case "tailscale":
|
||||||
|
return parseTailscaleStatus(output);
|
||||||
|
case "netbird":
|
||||||
|
return parseNetBirdStatus(output);
|
||||||
|
case "warp":
|
||||||
|
return parseWarpStatus(output);
|
||||||
|
case "wireguard":
|
||||||
|
default:
|
||||||
|
return parseWireGuardStatus(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAuthUrl(text: string): string {
|
||||||
|
const urlMatch = text.match(/(https?:\/\/[^\s]+)/);
|
||||||
|
return urlMatch ? urlMatch[1] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAuthStatus(authUrl: string): var {
|
||||||
|
return {
|
||||||
|
connected: false,
|
||||||
|
state: "needs-auth",
|
||||||
|
reason: "Authentication required",
|
||||||
|
authUrl: authUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(newStatus: var): void {
|
||||||
|
const oldState = status.state;
|
||||||
|
if (newStatus.state === "needs-auth" && !newStatus.authUrl && status.authUrl) {
|
||||||
|
newStatus.authUrl = status.authUrl;
|
||||||
|
}
|
||||||
|
status = newStatus;
|
||||||
|
root.connected = newStatus.connected;
|
||||||
|
|
||||||
|
if (oldState !== newStatus.state) {
|
||||||
|
emitStatusToast(newStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitStatusToast(statusObj: var): void {
|
||||||
if (!Config.utilities.toasts.vpnChanged)
|
if (!Config.utilities.toasts.vpnChanged)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const displayName = root.currentConfig ? (root.currentConfig.displayName || "VPN") : "VPN";
|
const displayName = root.currentConfig ? (root.currentConfig.displayName || "VPN") : "VPN";
|
||||||
if (connected) {
|
|
||||||
|
switch (statusObj.state) {
|
||||||
|
case "connected":
|
||||||
Toaster.toast(qsTr("VPN connected"), qsTr("Connected to %1").arg(displayName), "vpn_key");
|
Toaster.toast(qsTr("VPN connected"), qsTr("Connected to %1").arg(displayName), "vpn_key");
|
||||||
} else {
|
break;
|
||||||
Toaster.toast(qsTr("VPN disconnected"), qsTr("Disconnected from %1").arg(displayName), "vpn_key_off");
|
case "disconnected":
|
||||||
|
if (status.connected) {
|
||||||
|
Toaster.toast(qsTr("VPN disconnected"), qsTr("Disconnected from %1").arg(displayName), "vpn_key_off");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "needs-auth":
|
||||||
|
const authMsg = statusObj.reason || "Authentication required";
|
||||||
|
Toaster.toast(qsTr("VPN authentication required"), qsTr("%1: %2").arg(displayName).arg(authMsg), "vpn_lock");
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
if (status.state === "connected" || status.state === "connecting" || status.state === "needs-auth") {
|
||||||
|
const errMsg = statusObj.reason || "Unknown error";
|
||||||
|
Toaster.toast(qsTr("VPN error"), qsTr("%1: %2").arg(displayName).arg(errMsg), "error");
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStatusChanged: {
|
||||||
|
if (providerName === "warp" && status.state === "needs-auth" && status.reason.includes("registration")) {
|
||||||
|
warpRegisterProc.exec(["warp-cli", "registration", "new"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onProviderNameChanged: {
|
||||||
|
status = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
root.connected = false;
|
||||||
|
statusCheckTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: root.enabled && statusCheckTimer.start()
|
Component.onCompleted: root.enabled && statusCheckTimer.start()
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
|
|
@ -127,7 +338,7 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: statusProc
|
id: statusProc
|
||||||
|
|
||||||
command: ["ip", "link", "show"]
|
command: root.getStatusCommand()
|
||||||
// qmllint disable incompatible-type
|
// qmllint disable incompatible-type
|
||||||
environment: ({
|
environment: ({
|
||||||
// qmllint enable incompatible-type
|
// qmllint enable incompatible-type
|
||||||
|
|
@ -136,8 +347,38 @@ Singleton {
|
||||||
})
|
})
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const iface = root.currentConfig ? root.currentConfig.interface : "";
|
const newStatus = root.parseStatusOutput(text);
|
||||||
root.connected = iface && text.includes(iface + ":");
|
root.updateStatus(newStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
if (text.trim().length > 0) {
|
||||||
|
if (text.includes("doesn't appear to be running") || text.includes("failed to connect to local tailscaled") || text.includes("daemon is not running") || text.includes("not running") && (text.includes("netbird") || text.includes("warp"))) {
|
||||||
|
let cmd = "sudo systemctl start ";
|
||||||
|
switch (root.providerName) {
|
||||||
|
case "tailscale":
|
||||||
|
cmd += "tailscaled";
|
||||||
|
break;
|
||||||
|
case "netbird":
|
||||||
|
cmd += "netbird";
|
||||||
|
break;
|
||||||
|
case "warp":
|
||||||
|
cmd += "warp-svc";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cmd += root.providerName + "d";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const errorStatus = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: `Service not running (run: ${cmd})`,
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
root.updateStatus(errorStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,12 +386,59 @@ Singleton {
|
||||||
Process {
|
Process {
|
||||||
id: connectProc
|
id: connectProc
|
||||||
|
|
||||||
onExited: statusCheckTimer.start() // qmllint disable signal-handler-parameters
|
onExited: exitCode => { // qmllint disable signal-handler-parameters
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.providerName === "tailscale") {
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (root.status.state !== "needs-auth") {
|
||||||
|
statusCheckTimer.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (root.status.state !== "needs-auth") {
|
||||||
|
statusCheckTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
const authUrl = root.extractAuthUrl(data);
|
||||||
|
if (authUrl) {
|
||||||
|
root.updateStatus(root.createAuthStatus(authUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
stderr: StdioCollector {
|
stderr: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
const error = text.trim();
|
const error = text.trim();
|
||||||
if (error && !error.includes("[#]") && !error.includes("already exists")) {
|
|
||||||
console.warn("VPN connection error:", error);
|
if (error.includes("Access denied") || error.includes("checkprefs access denied")) {
|
||||||
|
const errorStatus = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "Permission denied. Run in terminal: sudo tailscale set --operator=$USER",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
root.updateStatus(errorStatus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.includes("Unknown device type") || error.includes("Protocol not supported")) {
|
||||||
|
const errorStatus = {
|
||||||
|
connected: false,
|
||||||
|
state: "disconnected",
|
||||||
|
reason: "WireGuard module not loaded. Run: sudo modprobe wireguard",
|
||||||
|
authUrl: ""
|
||||||
|
};
|
||||||
|
root.updateStatus(errorStatus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authUrl = root.extractAuthUrl(error);
|
||||||
|
|
||||||
|
if (authUrl) {
|
||||||
|
root.updateStatus(root.createAuthStatus(authUrl));
|
||||||
} else if (error.includes("already exists")) {
|
} else if (error.includes("already exists")) {
|
||||||
root.connected = true;
|
root.connected = true;
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +460,16 @@ Singleton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: warpRegisterProc
|
||||||
|
|
||||||
|
onExited: exitCode => { // qmllint disable signal-handler-parameters
|
||||||
|
if (exitCode === 0) {
|
||||||
|
statusCheckTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: statusCheckTimer
|
id: statusCheckTimer
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,8 @@ Singleton {
|
||||||
humidity: json.current.relative_humidity_2m,
|
humidity: json.current.relative_humidity_2m,
|
||||||
windSpeed: json.current.wind_speed_10m,
|
windSpeed: json.current.wind_speed_10m,
|
||||||
isDay: json.current.is_day,
|
isDay: json.current.is_day,
|
||||||
sunrise: json.daily.sunrise[0],
|
sunrise: json.daily.sunrise[0].replace("T", " "),
|
||||||
sunset: json.daily.sunset[0]
|
sunset: json.daily.sunset[0].replace("T", " ")
|
||||||
};
|
};
|
||||||
|
|
||||||
const forecastList = [];
|
const forecastList = [];
|
||||||
|
|
@ -218,7 +218,6 @@ Singleton {
|
||||||
target: Config.services
|
target: Config.services
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh current location hourly
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 3600000 // 1 hour
|
interval: 3600000 // 1 hour
|
||||||
running: true
|
running: true
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue