Settings rework...
This commit is contained in:
parent
74b233798d
commit
fb68300746
63 changed files with 7139 additions and 1026 deletions
|
|
@ -12,7 +12,7 @@ Item {
|
|||
id: root
|
||||
property alias panel: bluetoothPanelModal
|
||||
|
||||
// For showing error/status messages
|
||||
|
||||
property string statusMessage: ""
|
||||
property bool statusPopupVisible: false
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ Item {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 24
|
||||
radius: 20
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -145,7 +145,7 @@ Item {
|
|||
opacity: 0.12
|
||||
}
|
||||
|
||||
// Content area (centered, in a card)
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 640
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import QtQuick
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
|
@ -53,24 +53,108 @@ Rectangle {
|
|||
spacing: 12
|
||||
visible: !!MusicManager.currentPlayer
|
||||
|
||||
// Album art and spectrum
|
||||
// Player selector
|
||||
ComboBox {
|
||||
id: playerSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 40
|
||||
visible: MusicManager.getAvailablePlayers().length > 1
|
||||
model: MusicManager.getAvailablePlayers()
|
||||
textRole: "identity"
|
||||
currentIndex: MusicManager.selectedPlayerIndex
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 120
|
||||
implicitHeight: 40
|
||||
color: Theme.surfaceVariant
|
||||
border.color: playerSelector.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
leftPadding: 12
|
||||
rightPadding: playerSelector.indicator.width + playerSelector.spacing
|
||||
text: playerSelector.displayText
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: playerSelector.width - width - 12
|
||||
y: playerSelector.topPadding + (playerSelector.availableHeight - height) / 2
|
||||
text: "arrow_drop_down"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: playerSelector.height
|
||||
width: playerSelector.width
|
||||
implicitHeight: contentItem.implicitHeight
|
||||
padding: 1
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: playerSelector.popup.visible ? playerSelector.delegateModel : null
|
||||
currentIndex: playerSelector.highlightedIndex
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceVariant
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 16
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: playerSelector.width
|
||||
contentItem: Text {
|
||||
text: modelData.identity
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
highlighted: playerSelector.highlightedIndex === index
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: {
|
||||
MusicManager.selectedPlayerIndex = index;
|
||||
MusicManager.updateCurrentPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Album art with spectrum visualizer
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Album art with spectrum
|
||||
// Album art container with circular spectrum overlay
|
||||
Item {
|
||||
id: albumArtContainer
|
||||
width: 96; height: 96 // enough for spectrum and art (will adjust if needed)
|
||||
width: 96
|
||||
height: 96 // enough for spectrum and art (will adjust if needed)
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
// Spectrum visualizer
|
||||
// Circular spectrum visualizer around album art
|
||||
CircularSpectrum {
|
||||
id: spectrum
|
||||
values: MusicManager.cavaValues
|
||||
anchors.centerIn: parent
|
||||
innerRadius: 30 // just outside 60x60 album art
|
||||
outerRadius: 48 // how far bars extend
|
||||
innerRadius: 30 // Position just outside 60x60 album art
|
||||
outerRadius: 48 // Extend bars outward from album art
|
||||
fillColor: Theme.accentPrimary
|
||||
strokeColor: Theme.accentPrimary
|
||||
strokeWidth: 0
|
||||
|
|
@ -80,7 +164,8 @@ Rectangle {
|
|||
// Album art image
|
||||
Rectangle {
|
||||
id: albumArtwork
|
||||
width: 60; height: 60
|
||||
width: 60
|
||||
height: 60
|
||||
anchors.centerIn: parent
|
||||
radius: 30 // circle
|
||||
color: Qt.darker(Theme.surface, 1.1)
|
||||
|
|
@ -93,6 +178,7 @@ Rectangle {
|
|||
anchors.margins: 2
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
cache: false
|
||||
asynchronous: true
|
||||
sourceSize.width: 60
|
||||
|
|
@ -100,20 +186,29 @@ Rectangle {
|
|||
source: MusicManager.trackArtUrl
|
||||
visible: source.toString() !== ""
|
||||
|
||||
// Rounded corners using layer
|
||||
// Apply circular mask for rounded corners
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
cached: true
|
||||
maskSource: Rectangle {
|
||||
width: albumArt.width
|
||||
height: albumArt.height
|
||||
radius: albumArt.width / 2 // circle
|
||||
visible: false
|
||||
}
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskSource: mask
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: albumArt
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
width: albumArt.width
|
||||
height: albumArt.height
|
||||
radius: albumArt.width / 2 // circle
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon when no album art available
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "album"
|
||||
|
|
@ -171,8 +266,12 @@ Rectangle {
|
|||
color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.15)
|
||||
Layout.fillWidth: true
|
||||
|
||||
property real progressRatio: Math.min(1, MusicManager.trackLength > 0 ?
|
||||
(MusicManager.currentPosition / MusicManager.trackLength) : 0)
|
||||
property real progressRatio: {
|
||||
if (!MusicManager.currentPlayer || !MusicManager.isPlaying || MusicManager.trackLength <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(1, MusicManager.currentPosition / MusicManager.trackLength);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: progressFill
|
||||
|
|
@ -182,7 +281,9 @@ Rectangle {
|
|||
color: Theme.accentPrimary
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 200 }
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,14 +297,16 @@ Rectangle {
|
|||
border.color: Qt.lighter(Theme.accentPrimary, 1.3)
|
||||
border.width: 1
|
||||
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
|
||||
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width / 2))
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: MusicManager.trackLength > 0
|
||||
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150 }
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,15 +318,15 @@ Rectangle {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: MusicManager.trackLength > 0 && MusicManager.canSeek
|
||||
|
||||
onClicked: function(mouse) {
|
||||
let ratio = mouse.x / width
|
||||
MusicManager.seekByRatio(ratio)
|
||||
onClicked: function (mouse) {
|
||||
let ratio = mouse.x / width;
|
||||
MusicManager.seekByRatio(ratio);
|
||||
}
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
onPositionChanged: function (mouse) {
|
||||
if (pressed) {
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width))
|
||||
MusicManager.seekByRatio(ratio)
|
||||
let ratio = Math.max(0, Math.min(1, mouse.x / width));
|
||||
MusicManager.seekByRatio(ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -318,4 +421,4 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ PanelWithOverlay {
|
|||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
// Animation properties
|
||||
|
||||
property real slideOffset: width
|
||||
property bool isAnimating: false
|
||||
|
||||
|
|
@ -59,15 +59,15 @@ PanelWithOverlay {
|
|||
if (sidebarPopupRect.settingsModal && sidebarPopupRect.settingsModal.visible) {
|
||||
sidebarPopupRect.settingsModal.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.wallpaperPanelModal && sidebarPopupRect.wallpaperPanelModal.visible) {
|
||||
sidebarPopupRect.wallpaperPanelModal.visible = false;
|
||||
if (wallpaperPanel && wallpaperPanel.visible) {
|
||||
wallpaperPanel.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.wifiPanelModal && sidebarPopupRect.wifiPanelModal.visible) {
|
||||
sidebarPopupRect.wifiPanelModal.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.bluetoothPanelModal && sidebarPopupRect.bluetoothPanelModal.visible) {
|
||||
sidebarPopupRect.bluetoothPanelModal.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.wifiPanelModal && sidebarPopupRect.wifiPanelModal.visible) {
|
||||
sidebarPopupRect.wifiPanelModal.visible = false;
|
||||
}
|
||||
if (sidebarPopupRect.bluetoothPanelModal && sidebarPopupRect.bluetoothPanelModal.visible) {
|
||||
sidebarPopupRect.bluetoothPanelModal.visible = false;
|
||||
}
|
||||
if (sidebarPopup.visible) {
|
||||
slideAnim.from = 0;
|
||||
slideAnim.to = width;
|
||||
|
|
@ -85,7 +85,7 @@ PanelWithOverlay {
|
|||
onStopped: {
|
||||
if (sidebarPopupRect.slideOffset === sidebarPopupRect.width) {
|
||||
sidebarPopup.visible = false;
|
||||
// Stop monitoring and background tasks when hidden
|
||||
|
||||
if (weather)
|
||||
weather.stopWeatherFetch();
|
||||
if (systemWidget)
|
||||
|
|
@ -125,7 +125,6 @@ PanelWithOverlay {
|
|||
}
|
||||
|
||||
property alias settingsModal: settingsModal
|
||||
property alias wallpaperPanelModal: wallpaperPanelModal
|
||||
property alias wifiPanelModal: wifiPanel.panel
|
||||
property alias bluetoothPanelModal: bluetoothPanel.panel
|
||||
SettingsModal {
|
||||
|
|
@ -314,7 +313,7 @@ PanelWithOverlay {
|
|||
settingsModal.visible = true;
|
||||
}
|
||||
onWallpaperRequested: {
|
||||
wallpaperPanelModal.visible = true;
|
||||
wallpaperPanel.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -339,7 +338,15 @@ PanelWithOverlay {
|
|||
videoPath += "/";
|
||||
}
|
||||
var outputPath = videoPath + filename;
|
||||
var command = "gpu-screen-recorder -w portal -f 60 -a default_output -o " + outputPath;
|
||||
var command = "gpu-screen-recorder -w portal" +
|
||||
" -f " + Settings.settings.recordingFrameRate +
|
||||
" -a default_output" +
|
||||
" -k " + Settings.settings.recordingCodec +
|
||||
" -ac " + Settings.settings.audioCodec +
|
||||
" -q " + Settings.settings.recordingQuality +
|
||||
" -cursor " + (Settings.settings.showCursor ? "yes" : "no") +
|
||||
" -cr " + Settings.settings.colorRange +
|
||||
" -o " + outputPath;
|
||||
Quickshell.execDetached(["sh", "-c", command]);
|
||||
isRecording = true;
|
||||
quickAccessWidget.isRecording = true;
|
||||
|
|
@ -403,15 +410,13 @@ PanelWithOverlay {
|
|||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanelModal
|
||||
visible: false
|
||||
id: wallpaperPanel
|
||||
Component.onCompleted: {
|
||||
if (parent) {
|
||||
wallpaperPanelModal.anchors.top = parent.top;
|
||||
wallpaperPanelModal.anchors.right = parent.right;
|
||||
anchors.top = parent.top;
|
||||
anchors.right = parent.right;
|
||||
}
|
||||
}
|
||||
// Add a close button inside WallpaperPanel.qml for user to close the modal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Rectangle {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 20
|
||||
|
||||
// Performance
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
|
|
@ -63,7 +63,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Balanced
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
|
|
@ -109,7 +109,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Power Saver
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36
|
||||
radius: 18
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Settings
|
||||
|
|
@ -32,7 +32,7 @@ Rectangle {
|
|||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
// Settings Button
|
||||
|
||||
Rectangle {
|
||||
id: settingsButton
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -75,7 +75,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Screen Recorder Button
|
||||
|
||||
Rectangle {
|
||||
id: recorderButton
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -123,7 +123,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Wallpaper Button
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperButton
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -168,10 +168,10 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
property bool panelVisible: false
|
||||
|
||||
// Timer to check if recording is active
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
repeat: true
|
||||
|
|
@ -185,7 +185,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Process to check if gpu-screen-recorder is running
|
||||
|
||||
Process {
|
||||
id: checkRecordingProcess
|
||||
command: ["pgrep", "-f", "gpu-screen-recorder.*portal"]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Settings
|
||||
import qs.Widgets
|
||||
import qs.Widgets.LockScreen
|
||||
|
|
@ -29,19 +30,19 @@ Rectangle {
|
|||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
// User info row
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
// Profile image
|
||||
|
||||
Rectangle {
|
||||
width: 48
|
||||
height: 48
|
||||
radius: 24
|
||||
color: Theme.accentPrimary
|
||||
|
||||
// Border
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
|
|
@ -51,41 +52,10 @@ Rectangle {
|
|||
z: 2
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
source: Image {
|
||||
id: avatarImage
|
||||
anchors.fill: parent
|
||||
source: Settings.settings.profileImage !== undefined ? Settings.settings.profileImage : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: false
|
||||
sourceSize.width: 44
|
||||
sourceSize.height: 44
|
||||
}
|
||||
maskSource: Rectangle {
|
||||
width: 44
|
||||
height: 44
|
||||
radius: 22
|
||||
visible: false
|
||||
}
|
||||
visible: Settings.settings.profileImage !== undefined && Settings.settings.profileImage !== ""
|
||||
z: 1
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "person"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 24
|
||||
color: Theme.onAccent
|
||||
visible: Settings.settings.profileImage === undefined || Settings.settings.profileImage === ""
|
||||
z: 0
|
||||
}
|
||||
Avatar {}
|
||||
}
|
||||
|
||||
// User info text
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -106,12 +76,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// System menu button
|
||||
|
||||
Rectangle {
|
||||
id: systemButton
|
||||
width: 32
|
||||
|
|
@ -153,7 +123,7 @@ Rectangle {
|
|||
id: systemMenu
|
||||
anchors.top: systemButton.bottom
|
||||
anchors.right: systemButton.right
|
||||
// System menu popup
|
||||
|
||||
Rectangle {
|
||||
|
||||
width: 160
|
||||
|
|
@ -167,7 +137,7 @@ Rectangle {
|
|||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
// Position below system button
|
||||
|
||||
anchors.rightMargin: 32
|
||||
anchors.topMargin: systemButton.y + systemButton.height + 48
|
||||
|
||||
|
|
@ -176,7 +146,7 @@ Rectangle {
|
|||
anchors.margins: 8
|
||||
spacing: 4
|
||||
|
||||
// Lock button
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -216,7 +186,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Suspend button
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -255,7 +225,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Reboot button
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -295,7 +265,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Logout button
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -334,7 +304,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Shutdown button
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
|
|
@ -376,10 +346,10 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
property string uptimeText: "--:--"
|
||||
|
||||
// Process to get uptime
|
||||
|
||||
Process {
|
||||
id: uptimeProcess
|
||||
command: ["sh", "-c", "uptime | awk -F 'up ' '{print $2}' | awk -F ',' '{print $1}' | xargs"]
|
||||
|
|
@ -410,7 +380,7 @@ Rectangle {
|
|||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
Process {
|
||||
id: logoutProcessNiri
|
||||
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
|
||||
running: false
|
||||
|
|
@ -422,13 +392,19 @@ Rectangle {
|
|||
running: false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: logoutProcess
|
||||
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
|
||||
running: false
|
||||
}
|
||||
|
||||
function logout() {
|
||||
if (WorkspaceManager.isNiri) {
|
||||
logoutProcessNiri.running = true;
|
||||
} else if (WorkspaceManager.isHyprland) {
|
||||
logoutProcessHyprland.running = true;
|
||||
} else {
|
||||
// fallback or error
|
||||
|
||||
console.warn("No supported compositor detected for logout");
|
||||
}
|
||||
}
|
||||
|
|
@ -445,19 +421,18 @@ Rectangle {
|
|||
rebootProcess.running = true;
|
||||
}
|
||||
|
||||
|
||||
property bool panelVisible: false
|
||||
|
||||
// Trigger initial update when panel becomes visible
|
||||
|
||||
onPanelVisibleChanged: {
|
||||
if (panelVisible) {
|
||||
updateSystemInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// Timer to update uptime - only runs when panel is visible
|
||||
|
||||
Timer {
|
||||
interval: 60000 // Update every minute
|
||||
interval: 60000
|
||||
repeat: true
|
||||
running: panelVisible
|
||||
onTriggered: updateSystemInfo()
|
||||
|
|
@ -471,8 +446,8 @@ Rectangle {
|
|||
uptimeProcess.running = true;
|
||||
}
|
||||
|
||||
// Add lockscreen instance (hidden by default)
|
||||
|
||||
LockScreen {
|
||||
id: lockScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Rectangle {
|
|||
height: 250
|
||||
color: "transparent"
|
||||
|
||||
// Track visibility state for panel integration
|
||||
property bool isVisible: false
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -26,7 +27,8 @@ Rectangle {
|
|||
spacing: 12
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
// CPU Usage
|
||||
|
||||
// CPU usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
|
|
@ -55,7 +57,8 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Cpu Temp
|
||||
|
||||
// CPU temperature indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
|
|
@ -85,7 +88,8 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
|
||||
// Memory usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
|
|
@ -114,7 +118,8 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Disk Usage
|
||||
|
||||
// Disk usage indicator with circular progress bar
|
||||
Item {
|
||||
width: 50; height: 50
|
||||
CircularProgressBar {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ PanelWindow {
|
|||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (wallpaperPanelModal.visible) {
|
||||
if (wallpaperPanel.visible) {
|
||||
wallpapers = WallpaperManager.wallpaperList
|
||||
} else {
|
||||
wallpapers = []
|
||||
|
|
@ -40,7 +40,7 @@ PanelWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 24
|
||||
radius: 20
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
|
|
@ -81,7 +81,9 @@ PanelWindow {
|
|||
id: closeButtonArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: wallpaperPanelModal.visible = false
|
||||
onClicked: {
|
||||
wallpaperPanel.visible = false;
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +94,7 @@ PanelWindow {
|
|||
color: Theme.outline
|
||||
opacity: 0.12
|
||||
}
|
||||
// Wallpaper grid area
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
|
@ -114,7 +116,7 @@ PanelWindow {
|
|||
cellWidth: Math.max(120, (scrollView.width / 3) - 12)
|
||||
cellHeight: cellWidth * 0.6
|
||||
model: wallpapers
|
||||
cacheBuffer: 32
|
||||
cacheBuffer: 64
|
||||
leftMargin: 8
|
||||
rightMargin: 8
|
||||
topMargin: 8
|
||||
|
|
@ -129,7 +131,7 @@ PanelWindow {
|
|||
color: Qt.darker(Theme.backgroundPrimary, 1.1)
|
||||
radius: 12
|
||||
border.color: Settings.settings.currentWallpaper === modelData ? Theme.accentPrimary : Theme.outline
|
||||
border.width: Settings.settings.currentWallpaper === modelData ? 3 : 1
|
||||
border.width: 2
|
||||
Image {
|
||||
id: wallpaperImage
|
||||
anchors.fill: parent
|
||||
|
|
@ -137,8 +139,19 @@ PanelWindow {
|
|||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
sourceSize.width: Math.min(width, 150)
|
||||
sourceSize.height: Math.min(height, 90)
|
||||
smooth: true
|
||||
mipmap: true
|
||||
|
||||
sourceSize.width: Math.min(width, 480)
|
||||
sourceSize.height: Math.min(height, 270)
|
||||
|
||||
opacity: (wallpaperImage.status == Image.Ready) ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
|
|
@ -54,17 +54,17 @@ Rectangle {
|
|||
anchors.margins: 18
|
||||
spacing: 12
|
||||
|
||||
// Current weather row
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Weather icon and basic info section
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.preferredWidth: 140
|
||||
|
||||
// Weather icon
|
||||
|
||||
Text {
|
||||
id: weatherIcon
|
||||
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
|
||||
|
|
@ -103,13 +103,13 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Spacer to push content to the right
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
// Separator line
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
|
|
@ -119,7 +119,7 @@ Rectangle {
|
|||
Layout.bottomMargin: 2
|
||||
}
|
||||
|
||||
// 5-day forecast row
|
||||
|
||||
RowLayout {
|
||||
spacing: 12
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -132,7 +132,7 @@ Rectangle {
|
|||
spacing: 2
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Text {
|
||||
// Day of the week (e.g., Mon)
|
||||
|
||||
text: Qt.formatDateTime(new Date(weatherData.daily.time[index]), "ddd")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
|
|
@ -141,7 +141,7 @@ Rectangle {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
// Material Symbol icon
|
||||
|
||||
text: materialSymbolForCode(weatherData.daily.weathercode[index])
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
|
|
@ -150,7 +150,7 @@ Rectangle {
|
|||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
// High/low temp
|
||||
|
||||
text: weatherData && weatherData.daily ? ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? `${Math.round(weatherData.daily.temperature_2m_max[index] * 9/5 + 32)}° / ${Math.round(weatherData.daily.temperature_2m_min[index] * 9/5 + 32)}°` : `${Math.round(weatherData.daily.temperature_2m_max[index])}° / ${Math.round(weatherData.daily.temperature_2m_min[index])}°`) : ((Settings.settings.useFahrenheit !== undefined ? Settings.settings.useFahrenheit : false) ? "--° / --°" : "--° / --°")
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 12
|
||||
|
|
@ -162,7 +162,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Error message
|
||||
|
||||
Text {
|
||||
text: errorString
|
||||
color: Theme.error
|
||||
|
|
@ -175,16 +175,16 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Weather code to Material Symbol ligature mapping
|
||||
|
||||
function materialSymbolForCode(code) {
|
||||
if (code === 0) return "sunny"; // Clear
|
||||
if (code === 1 || code === 2) return "partly_cloudy_day"; // Mainly clear/partly cloudy
|
||||
if (code === 3) return "cloud"; // Overcast
|
||||
if (code >= 45 && code <= 48) return "foggy"; // Fog
|
||||
if (code >= 51 && code <= 67) return "rainy"; // Drizzle
|
||||
if (code >= 71 && code <= 77) return "weather_snowy"; // Snow
|
||||
if (code >= 80 && code <= 82) return "rainy"; // Rain showers
|
||||
if (code >= 95 && code <= 99) return "thunderstorm"; // Thunderstorm
|
||||
if (code === 0) return "sunny";
|
||||
if (code === 1 || code === 2) return "partly_cloudy_day";
|
||||
if (code === 3) return "cloud";
|
||||
if (code >= 45 && code <= 48) return "foggy";
|
||||
if (code >= 51 && code <= 67) return "rainy";
|
||||
if (code >= 71 && code <= 77) return "weather_snowy";
|
||||
if (code >= 80 && code <= 82) return "rainy";
|
||||
if (code >= 95 && code <= 99) return "thunderstorm";
|
||||
return "cloud";
|
||||
}
|
||||
function weatherDescriptionForCode(code) {
|
||||
|
|
|
|||
|
|
@ -11,72 +11,128 @@ import qs.Helpers
|
|||
|
||||
Item {
|
||||
property alias panel: wifiPanelModal
|
||||
|
||||
|
||||
function showAt() {
|
||||
wifiPanelModal.visible = true;
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
existingNetwork.running = true;
|
||||
}
|
||||
|
||||
function signalIcon(signal) {
|
||||
if (signal >= 80) return "network_wifi";
|
||||
if (signal >= 60) return "network_wifi_3_bar";
|
||||
if (signal >= 40) return "network_wifi_2_bar";
|
||||
if (signal >= 20) return "network_wifi_1_bar";
|
||||
if (signal >= 80)
|
||||
return "network_wifi";
|
||||
if (signal >= 60)
|
||||
return "network_wifi_3_bar";
|
||||
if (signal >= 40)
|
||||
return "network_wifi_2_bar";
|
||||
if (signal >= 20)
|
||||
return "network_wifi_1_bar";
|
||||
return "wifi_0_bar";
|
||||
}
|
||||
|
||||
Process {
|
||||
id: existingNetwork
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.split("\n");
|
||||
const networksMap = {};
|
||||
|
||||
refreshIndicator.running = true;
|
||||
refreshIndicator.visible = true;
|
||||
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim();
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
const parts = line.split(":");
|
||||
if (parts.length < 2) {
|
||||
console.warn("Malformed nmcli output line:", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ssid = wifiLogic.replaceQuickshell(parts[0]);
|
||||
const type = parts[1];
|
||||
|
||||
if (ssid) {
|
||||
networksMap[ssid] = {
|
||||
ssid: ssid,
|
||||
type: type
|
||||
};
|
||||
}
|
||||
}
|
||||
scanProcess.existingNetwork = networksMap;
|
||||
scanProcess.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: scanProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"]
|
||||
onRunningChanged: {
|
||||
// Removed debug log
|
||||
}
|
||||
|
||||
property var existingNetwork
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var lines = text.split("\n");
|
||||
var nets = [];
|
||||
var seen = {};
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
var line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
var parts = line.split(":");
|
||||
var ssid = parts[0];
|
||||
var security = parts[1];
|
||||
var signal = parseInt(parts[2]);
|
||||
var inUse = parts[3] === "*";
|
||||
const lines = text.split("\n");
|
||||
const networksMap = {};
|
||||
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim();
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
const parts = line.split(":");
|
||||
if (parts.length < 4) {
|
||||
console.warn("Malformed nmcli output line:", line);
|
||||
continue;
|
||||
}
|
||||
const ssid = parts[0];
|
||||
const security = parts[1];
|
||||
const signal = parseInt(parts[2]);
|
||||
const inUse = parts[3] === "*";
|
||||
|
||||
if (ssid) {
|
||||
if (!seen[ssid]) {
|
||||
// First time seeing this SSID
|
||||
nets.push({ ssid: ssid, security: security, signal: signal, connected: inUse });
|
||||
seen[ssid] = true;
|
||||
if (!networksMap[ssid]) {
|
||||
networksMap[ssid] = {
|
||||
ssid: ssid,
|
||||
security: security,
|
||||
signal: signal,
|
||||
connected: inUse,
|
||||
existing: ssid in scanProcess.existingNetwork
|
||||
};
|
||||
} else {
|
||||
// SSID already exists, update if this entry has better signal or is connected
|
||||
for (var j = 0; j < nets.length; ++j) {
|
||||
if (nets[j].ssid === ssid) {
|
||||
// Update connection status if this entry is connected
|
||||
if (inUse) {
|
||||
nets[j].connected = true;
|
||||
}
|
||||
// Update signal if this entry has better signal
|
||||
if (signal > nets[j].signal) {
|
||||
nets[j].signal = signal;
|
||||
nets[j].security = security;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const existingNet = networksMap[ssid];
|
||||
if (inUse) {
|
||||
existingNet.connected = true;
|
||||
}
|
||||
if (signal > existingNet.signal) {
|
||||
existingNet.signal = signal;
|
||||
existingNet.security = security;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wifiLogic.networks = nets;
|
||||
|
||||
|
||||
wifiLogic.networks = networksMap;
|
||||
scanProcess.existingNetwork = {};
|
||||
refreshIndicator.running = false;
|
||||
refreshIndicator.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: wifiLogic
|
||||
property var networks: []
|
||||
property var networks: {}
|
||||
property var anchorItem: null
|
||||
property real anchorX
|
||||
property real anchorY
|
||||
|
|
@ -90,54 +146,98 @@ Item {
|
|||
property string connectSecurity: ""
|
||||
property var pendingConnect: null
|
||||
property string detectedInterface: ""
|
||||
property string actionPanelSsid: ""
|
||||
|
||||
function profileNameForSsid(ssid) {
|
||||
return "quickshell-" + ssid.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
function replaceQuickshell(ssid: string): string {
|
||||
const newName = ssid.replace("quickshell-", "");
|
||||
|
||||
if (!ssid.startsWith("quickshell-")) {
|
||||
return newName;
|
||||
}
|
||||
|
||||
if (wifiLogic.networks && newName in wifiLogic.networks) {
|
||||
console.log(`Quickshell ${newName} already exists, deleting old profile`)
|
||||
deleteProfileProcess.connName = ssid;
|
||||
deleteProfileProcess.running = true;
|
||||
}
|
||||
|
||||
console.log(`Changing from ${ssid} to ${newName}`)
|
||||
renameConnectionProcess.oldName = ssid;
|
||||
renameConnectionProcess.newName = newName;
|
||||
renameConnectionProcess.running = true;
|
||||
|
||||
return newName;
|
||||
}
|
||||
|
||||
function disconnectNetwork(ssid) {
|
||||
var profileName = wifiLogic.profileNameForSsid(ssid);
|
||||
const profileName = ssid;
|
||||
disconnectProfileProcess.connectionName = profileName;
|
||||
disconnectProfileProcess.running = true;
|
||||
}
|
||||
function refreshNetworks() {
|
||||
scanProcess.running = true;
|
||||
existingNetwork.running = true;
|
||||
}
|
||||
function showAt() {
|
||||
wifiPanelModal.visible = true;
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
function connectNetwork(ssid, security) {
|
||||
wifiLogic.pendingConnect = {ssid: ssid, security: security, password: ""};
|
||||
listConnectionsProcess.running = true;
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: ssid,
|
||||
security: security,
|
||||
password: ""
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
function submitPassword() {
|
||||
wifiLogic.pendingConnect = {ssid: wifiLogic.passwordPromptSsid, security: wifiLogic.connectSecurity, password: wifiLogic.passwordInput};
|
||||
listConnectionsProcess.running = true;
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: wifiLogic.passwordPromptSsid,
|
||||
security: wifiLogic.connectSecurity,
|
||||
password: wifiLogic.passwordInput
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
function doConnect() {
|
||||
var params = wifiLogic.pendingConnect;
|
||||
const params = wifiLogic.pendingConnect;
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
wifiLogic.connectingSsid = params.ssid;
|
||||
|
||||
|
||||
const targetNetwork = wifiLogic.networks[params.ssid];
|
||||
|
||||
|
||||
if (targetNetwork && targetNetwork.existing) {
|
||||
|
||||
upConnectionProcess.profileName = params.ssid;
|
||||
upConnectionProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (params.security && params.security !== "--") {
|
||||
getInterfaceProcess.running = true;
|
||||
} else {
|
||||
connectProcess.security = params.security;
|
||||
connectProcess.ssid = params.ssid;
|
||||
connectProcess.password = params.password;
|
||||
connectProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
return;
|
||||
}
|
||||
connectProcess.security = params.security;
|
||||
connectProcess.ssid = params.ssid;
|
||||
connectProcess.password = params.password;
|
||||
connectProcess.running = true;
|
||||
wifiLogic.pendingConnect = null;
|
||||
}
|
||||
function isSecured(security) {
|
||||
return security && security.trim() !== "" && security.trim() !== "--";
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect, delete profile, refresh
|
||||
|
||||
Process {
|
||||
id: disconnectProfileProcess
|
||||
property string connectionName: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "down", "id", connectionName]
|
||||
command: ["nmcli", "connection", "down", connectionName]
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
wifiLogic.refreshNetworks();
|
||||
|
|
@ -145,63 +245,70 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Process to rename a connection
|
||||
Process {
|
||||
id: listConnectionsProcess
|
||||
id: renameConnectionProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "NAME", "connection", "show"]
|
||||
property string oldName: ""
|
||||
property string newName: ""
|
||||
command: ["nmcli", "connection", "modify", oldName, "connection.id", newName]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var params = wifiLogic.pendingConnect;
|
||||
var lines = text.split("\n");
|
||||
var expectedProfile = wifiLogic.profileNameForSsid(params.ssid);
|
||||
var foundProfile = null;
|
||||
for (var i = 0; i < lines.length; ++i) {
|
||||
if (lines[i] === expectedProfile) {
|
||||
foundProfile = lines[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundProfile) {
|
||||
// Profile exists, just bring it up (no password prompt)
|
||||
upConnectionProcess.profileName = foundProfile;
|
||||
upConnectionProcess.running = true;
|
||||
} else {
|
||||
// No profile: check if secured
|
||||
if (wifiLogic.isSecured(params.security)) {
|
||||
if (params.password && params.password.length > 0) {
|
||||
// Password provided, proceed to connect
|
||||
wifiLogic.doConnect();
|
||||
} else {
|
||||
// No password yet, prompt for it
|
||||
wifiLogic.passwordPromptSsid = params.ssid;
|
||||
wifiLogic.passwordInput = "";
|
||||
wifiLogic.showPasswordPrompt = true;
|
||||
wifiLogic.connectStatus = "";
|
||||
wifiLogic.connectStatusSsid = "";
|
||||
wifiLogic.connectError = "";
|
||||
wifiLogic.connectSecurity = params.security;
|
||||
}
|
||||
} else {
|
||||
// Open, connect directly
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
console.log("Successfully renamed connection '" +
|
||||
renameConnectionProcess.oldName + "' to '" +
|
||||
renameConnectionProcess.newName + "'");
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim() !== "" && !text.toLowerCase().includes("warning")) {
|
||||
console.error("Error renaming connection:", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles connecting to a Wi-Fi network, with or without password
|
||||
|
||||
|
||||
// Process to rename a connection
|
||||
Process {
|
||||
id: deleteProfileProcess
|
||||
running: false
|
||||
property string connName: ""
|
||||
command: ["nmcli", "connection", "delete", `'${connName}'`]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.log("Deleted connection '" + deleteProfileProcess.connName + "'");
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.error("Error deleting connection '" + deleteProfileProcess.connName + "':", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Process {
|
||||
id: connectProcess
|
||||
property string ssid: ""
|
||||
property string password: ""
|
||||
property string security: ""
|
||||
running: false
|
||||
onStarted: {
|
||||
refreshIndicator.running = true;
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
refreshIndicator.running = false;
|
||||
}
|
||||
command: {
|
||||
if (password) {
|
||||
return ["nmcli", "device", "wifi", "connect", ssid, "password", password]
|
||||
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`, "password", password];
|
||||
} else {
|
||||
return ["nmcli", "device", "wifi", "connect", ssid]
|
||||
return ["nmcli", "device", "wifi", "connect", `'${ssid}'`];
|
||||
}
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
|
|
@ -229,7 +336,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Finds the correct Wi-Fi interface for connection
|
||||
|
||||
Process {
|
||||
id: getInterfaceProcess
|
||||
running: false
|
||||
|
|
@ -249,7 +356,7 @@ Item {
|
|||
addConnectionProcess.ifname = wifiLogic.detectedInterface;
|
||||
addConnectionProcess.ssid = params.ssid;
|
||||
addConnectionProcess.password = params.password;
|
||||
addConnectionProcess.profileName = wifiLogic.profileNameForSsid(params.ssid);
|
||||
addConnectionProcess.profileName = params.ssid;
|
||||
addConnectionProcess.security = params.security;
|
||||
addConnectionProcess.running = true;
|
||||
} else {
|
||||
|
|
@ -263,7 +370,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Adds a new Wi-Fi connection profile
|
||||
|
||||
Process {
|
||||
id: addConnectionProcess
|
||||
property string ifname: ""
|
||||
|
|
@ -296,7 +403,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Brings up the new connection profile and finalizes connection state
|
||||
|
||||
Process {
|
||||
id: upConnectionProcess
|
||||
property string profileName: ""
|
||||
|
|
@ -329,10 +436,11 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Wifi button (no background card)
|
||||
|
||||
Rectangle {
|
||||
id: wifiButton
|
||||
width: 36; height: 36
|
||||
width: 36
|
||||
height: 36
|
||||
radius: 18
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
|
@ -343,9 +451,7 @@ Item {
|
|||
text: "wifi"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 22
|
||||
color: wifiButtonArea.containsMouse
|
||||
? Theme.backgroundPrimary
|
||||
: Theme.accentPrimary
|
||||
color: wifiButtonArea.containsMouse ? Theme.backgroundPrimary : Theme.accentPrimary
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
|
@ -371,12 +477,12 @@ Item {
|
|||
margins.top: 0
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
Component.onCompleted: {
|
||||
wifiLogic.refreshNetworks()
|
||||
wifiLogic.refreshNetworks();
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.backgroundPrimary
|
||||
radius: 24
|
||||
radius: 20
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
|
|
@ -400,8 +506,29 @@ Item {
|
|||
color: Theme.textPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Spinner {
|
||||
id: refreshIndicator
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: false
|
||||
running: false
|
||||
color: Theme.accentPrimary
|
||||
size: 22
|
||||
}
|
||||
IconButton {
|
||||
id: refreshButton
|
||||
icon: "refresh"
|
||||
onClicked: wifiLogic.refreshNetworks()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 36; height: 36; radius: 18
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
radius: 18
|
||||
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 1
|
||||
|
|
@ -463,11 +590,15 @@ Item {
|
|||
anchors.fill: parent
|
||||
spacing: 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
model: wifiLogic.networks
|
||||
model: wifiLogic.networks ? Object.values(wifiLogic.networks) : null
|
||||
delegate: Item {
|
||||
id: networkEntry
|
||||
|
||||
required property var modelData
|
||||
property var signalIcon: wifiPanel.signalIcon
|
||||
|
||||
width: parent.width
|
||||
height: modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42
|
||||
height: (modelData.ssid === wifiLogic.passwordPromptSsid && wifiLogic.showPasswordPrompt ? 102 : 42) + (modelData.ssid === wifiLogic.actionPanelSsid ? 60 : 0)
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
|
@ -504,7 +635,8 @@ Item {
|
|||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Item {
|
||||
width: 22; height: 22
|
||||
width: 22
|
||||
height: 22
|
||||
visible: wifiLogic.connectStatusSsid === modelData.ssid && wifiLogic.connectStatus !== ""
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -554,28 +686,29 @@ Item {
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
Spinner {
|
||||
visible: wifiLogic.connectingSsid === modelData.ssid
|
||||
running: wifiLogic.connectingSsid === modelData.ssid
|
||||
color: Theme.accentPrimary
|
||||
anchors.centerIn: parent
|
||||
size: 22
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredHeight: 22
|
||||
Layout.preferredWidth: 22
|
||||
Spinner {
|
||||
visible: wifiLogic.connectingSsid === modelData.ssid
|
||||
running: wifiLogic.connectingSsid === modelData.ssid
|
||||
color: Theme.accentPrimary
|
||||
anchors.centerIn: parent
|
||||
size: 22
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: networkMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
wifiLogic.disconnectNetwork(modelData.ssid);
|
||||
|
||||
if (wifiLogic.actionPanelSsid === modelData.ssid) {
|
||||
wifiLogic.actionPanelSsid = ""; // Close if already open
|
||||
} else {
|
||||
wifiLogic.connectNetwork(modelData.ssid, modelData.security);
|
||||
wifiLogic.actionPanelSsid = modelData.ssid; // Open for this network
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -586,8 +719,9 @@ Item {
|
|||
Layout.preferredHeight: 60
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
anchors.leftMargin: 32
|
||||
anchors.rightMargin: 32
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
z: 2
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -627,14 +761,18 @@ Item {
|
|||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 36
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: Theme.accentPrimary
|
||||
border.color: Theme.accentPrimary
|
||||
border.width: 0
|
||||
opacity: 1.0
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: wifiLogic.submitPassword()
|
||||
|
|
@ -653,6 +791,113 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.ssid === wifiLogic.actionPanelSsid
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 60
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.leftMargin: 32
|
||||
Layout.rightMargin: 32
|
||||
z: 2
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
visible: wifiLogic.isSecured(modelData.security) && !modelData.connected && !modelData.existing
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: "transparent"
|
||||
border.color: actionPanelPasswordField.activeFocus ? Theme.accentPrimary : Theme.outline
|
||||
border.width: 1
|
||||
TextInput {
|
||||
id: actionPanelPasswordField
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
font.pixelSize: 13
|
||||
color: Theme.textPrimary
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
activeFocusOnTab: true
|
||||
inputMethodHints: Qt.ImhNone
|
||||
echoMode: TextInput.Password
|
||||
onAccepted: {
|
||||
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
security: modelData.security,
|
||||
password: text
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
|
||||
wifiLogic.actionPanelSsid = ""; // Close the panel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 36
|
||||
radius: 18
|
||||
color: modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
border.color: modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
border.width: 0
|
||||
opacity: 1.0
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
|
||||
wifiLogic.disconnectNetwork(modelData.ssid);
|
||||
} else {
|
||||
|
||||
if (wifiLogic.isSecured(modelData.security) && !modelData.existing) {
|
||||
|
||||
if (actionPanelPasswordField.text.length > 0) {
|
||||
wifiLogic.pendingConnect = {
|
||||
ssid: modelData.ssid,
|
||||
security: modelData.security,
|
||||
password: actionPanelPasswordField.text
|
||||
};
|
||||
wifiLogic.doConnect();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
wifiLogic.connectNetwork(modelData.ssid, modelData.security);
|
||||
}
|
||||
}
|
||||
wifiLogic.actionPanelSsid = ""; // Close the panel
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onEntered: parent.color = modelData.connected ? Qt.darker(Theme.error, 1.1) : Qt.darker(Theme.accentPrimary, 1.1)
|
||||
onExited: parent.color = modelData.connected ? Theme.error : Theme.accentPrimary
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.connected ? "wifi_off" : "check"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 20
|
||||
color: Theme.backgroundPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue