forked from Shinonome/dots-hyprland
wifi menu
This commit is contained in:
@@ -1,29 +1,35 @@
|
|||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
|
* Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
|
||||||
*/
|
*/
|
||||||
RippleButton {
|
RippleButton {
|
||||||
id: button
|
id: root
|
||||||
|
|
||||||
property string buttonText
|
property string buttonText
|
||||||
implicitHeight: 30
|
padding: 14
|
||||||
implicitWidth: buttonTextWidget.implicitWidth + 15 * 2
|
implicitHeight: 36
|
||||||
|
implicitWidth: buttonTextWidget.implicitWidth + padding * 2
|
||||||
buttonRadius: Appearance?.rounding.full ?? 9999
|
buttonRadius: Appearance?.rounding.full ?? 9999
|
||||||
|
|
||||||
property color colEnabled: Appearance?.colors.colPrimary ?? "#65558F"
|
property color colEnabled: Appearance?.colors.colPrimary ?? "#65558F"
|
||||||
property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96"
|
property color colDisabled: Appearance?.m3colors.m3outline ?? "#8D8C96"
|
||||||
|
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
||||||
|
colBackgroundHover: Appearance.colors.colLayer3Hover
|
||||||
|
colRipple: Appearance.colors.colLayer3Active
|
||||||
|
|
||||||
contentItem: StyledText {
|
contentItem: StyledText {
|
||||||
id: buttonTextWidget
|
id: buttonTextWidget
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: 15
|
anchors.leftMargin: root.padding
|
||||||
anchors.rightMargin: 15
|
anchors.rightMargin: root.padding
|
||||||
text: buttonText
|
text: buttonText
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
font.pixelSize: Appearance?.font.pixelSize.small ?? 12
|
font.pixelSize: Appearance?.font.pixelSize.small ?? 12
|
||||||
color: button.enabled ? button.colEnabled : button.colDisabled
|
color: root.enabled ? root.colEnabled : root.colDisabled
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import qs.modules.common
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls.Material
|
||||||
|
import QtQuick.Controls
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material 3 styled TextArea (filled style)
|
||||||
|
* https://m3.material.io/components/text-fields/overview
|
||||||
|
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
|
||||||
|
*/
|
||||||
|
TextArea {
|
||||||
|
id: root
|
||||||
|
Material.theme: Material.System
|
||||||
|
Material.accent: Appearance.m3colors.m3primary
|
||||||
|
Material.primary: Appearance.m3colors.m3primary
|
||||||
|
Material.background: Appearance.m3colors.m3surface
|
||||||
|
Material.foreground: Appearance.m3colors.m3onSurface
|
||||||
|
Material.containerStyle: Material.Filled
|
||||||
|
renderType: Text.QtRendering
|
||||||
|
|
||||||
|
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
selectionColor: Appearance.colors.colSecondaryContainer
|
||||||
|
placeholderTextColor: Appearance.m3colors.m3outline
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitHeight: 56
|
||||||
|
color: Appearance.m3colors.m3surface
|
||||||
|
topLeftRadius: 4
|
||||||
|
topRightRadius: 4
|
||||||
|
Rectangle {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
height: 1
|
||||||
|
color: root.focus ? Appearance.m3colors.m3primary :
|
||||||
|
root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
font {
|
||||||
|
family: Appearance?.font.family.main ?? "sans-serif"
|
||||||
|
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||||
|
hintingPreference: Font.PreferFullHinting
|
||||||
|
}
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
}
|
||||||
@@ -4,44 +4,24 @@ import QtQuick.Controls.Material
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Material 3 styled TextArea (filled style)
|
* Material 3 styled TextField (filled style)
|
||||||
* https://m3.material.io/components/text-fields/overview
|
* https://m3.material.io/components/text-fields/overview
|
||||||
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
|
* Note: We don't use NativeRendering because it makes the small placeholder text look weird
|
||||||
*/
|
*/
|
||||||
TextArea {
|
TextField {
|
||||||
id: root
|
id: root
|
||||||
Material.theme: Material.System
|
Material.theme: Material.System
|
||||||
Material.accent: Appearance.m3colors.m3primary
|
Material.accent: Appearance.m3colors.m3primary
|
||||||
Material.primary: Appearance.m3colors.m3primary
|
Material.primary: Appearance.m3colors.m3primary
|
||||||
Material.background: Appearance.m3colors.m3surface
|
Material.background: Appearance.m3colors.m3surface
|
||||||
Material.foreground: Appearance.m3colors.m3onSurface
|
Material.foreground: Appearance.m3colors.m3onSurface
|
||||||
Material.containerStyle: Material.Filled
|
Material.containerStyle: Material.Outlined
|
||||||
renderType: Text.QtRendering
|
renderType: Text.QtRendering
|
||||||
|
|
||||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||||
selectionColor: Appearance.colors.colSecondaryContainer
|
selectionColor: Appearance.colors.colSecondaryContainer
|
||||||
placeholderTextColor: Appearance.m3colors.m3outline
|
placeholderTextColor: Appearance.m3colors.m3outline
|
||||||
|
clip: true
|
||||||
background: Rectangle {
|
|
||||||
implicitHeight: 56
|
|
||||||
color: Appearance.m3colors.m3surface
|
|
||||||
topLeftRadius: 4
|
|
||||||
topRightRadius: 4
|
|
||||||
Rectangle {
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
height: 1
|
|
||||||
color: root.focus ? Appearance.m3colors.m3primary :
|
|
||||||
root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
font {
|
font {
|
||||||
family: Appearance?.font.family.main ?? "sans-serif"
|
family: Appearance?.font.family.main ?? "sans-serif"
|
||||||
@@ -49,4 +29,11 @@ TextArea {
|
|||||||
hintingPreference: Font.PreferFullHinting
|
hintingPreference: Font.PreferFullHinting
|
||||||
}
|
}
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ ListView {
|
|||||||
property int dragIndex: -1
|
property int dragIndex: -1
|
||||||
property real dragDistance: 0
|
property real dragDistance: 0
|
||||||
property bool popin: true
|
property bool popin: true
|
||||||
|
property bool animateAppearance: true
|
||||||
|
property bool animateMovement: false
|
||||||
// Accumulated scroll destination so wheel deltas stack while animating
|
// Accumulated scroll destination so wheel deltas stack while animating
|
||||||
property real scrollTargetY: 0
|
property real scrollTargetY: 0
|
||||||
|
|
||||||
@@ -66,17 +68,17 @@ ListView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add: Transition {
|
add: Transition {
|
||||||
animations: [
|
animations: animateAppearance ? [
|
||||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
properties: popin ? "opacity,scale" : "opacity",
|
properties: popin ? "opacity,scale" : "opacity",
|
||||||
from: 0,
|
from: 0,
|
||||||
to: 1,
|
to: 1,
|
||||||
}),
|
}),
|
||||||
]
|
] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
addDisplaced: Transition {
|
addDisplaced: Transition {
|
||||||
animations: [
|
animations: animateAppearance ? [
|
||||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
property: "y",
|
property: "y",
|
||||||
}),
|
}),
|
||||||
@@ -84,46 +86,46 @@ ListView {
|
|||||||
properties: popin ? "opacity,scale" : "opacity",
|
properties: popin ? "opacity,scale" : "opacity",
|
||||||
to: 1,
|
to: 1,
|
||||||
}),
|
}),
|
||||||
]
|
] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
// displaced: Transition {
|
displaced: Transition {
|
||||||
// animations: [
|
animations: root.animateMovement ? [
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// property: "y",
|
property: "y",
|
||||||
// }),
|
}),
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// properties: "opacity,scale",
|
properties: "opacity,scale",
|
||||||
// to: 1,
|
to: 1,
|
||||||
// }),
|
}),
|
||||||
// ]
|
] : []
|
||||||
// }
|
}
|
||||||
|
|
||||||
// move: Transition {
|
move: Transition {
|
||||||
// animations: [
|
animations: root.animateMovement ? [
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// property: "y",
|
property: "y",
|
||||||
// }),
|
}),
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// properties: "opacity,scale",
|
properties: "opacity,scale",
|
||||||
// to: 1,
|
to: 1,
|
||||||
// }),
|
}),
|
||||||
// ]
|
] : []
|
||||||
// }
|
}
|
||||||
// moveDisplaced: Transition {
|
moveDisplaced: Transition {
|
||||||
// animations: [
|
animations: root.animateMovement ? [
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// property: "y",
|
property: "y",
|
||||||
// }),
|
}),
|
||||||
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
// properties: "opacity,scale",
|
properties: "opacity,scale",
|
||||||
// to: 1,
|
to: 1,
|
||||||
// }),
|
}),
|
||||||
// ]
|
] : []
|
||||||
// }
|
}
|
||||||
|
|
||||||
remove: Transition {
|
remove: Transition {
|
||||||
animations: [
|
animations: animateAppearance ? [
|
||||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
property: "x",
|
property: "x",
|
||||||
to: root.width + root.removeOvershoot,
|
to: root.width + root.removeOvershoot,
|
||||||
@@ -132,12 +134,12 @@ ListView {
|
|||||||
property: "opacity",
|
property: "opacity",
|
||||||
to: 0,
|
to: 0,
|
||||||
})
|
})
|
||||||
]
|
] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is movement when something is removed, not removing animation!
|
// This is movement when something is removed, not removing animation!
|
||||||
removeDisplaced: Transition {
|
removeDisplaced: Transition {
|
||||||
animations: [
|
animations: animateAppearance ? [
|
||||||
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
|
||||||
property: "y",
|
property: "y",
|
||||||
}),
|
}),
|
||||||
@@ -145,6 +147,6 @@ ListView {
|
|||||||
properties: "opacity,scale",
|
properties: "opacity,scale",
|
||||||
to: 1,
|
to: 1,
|
||||||
}),
|
}),
|
||||||
]
|
] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool show: false
|
||||||
|
default property alias data: contentColumn.data
|
||||||
|
property real backgroundHeight: 600
|
||||||
|
property real backgroundAnimationMovementDistance: 60
|
||||||
|
signal dismiss()
|
||||||
|
|
||||||
|
color: root.show ? Appearance.colors.colScrim : ColorUtils.transparentize(Appearance.colors.colScrim)
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
visible: dialogBackground.implicitHeight > 0
|
||||||
|
|
||||||
|
onShowChanged: {
|
||||||
|
dialogBackgroundHeightAnimation.easing.bezierCurve = (show ? Appearance.animationCurves.emphasizedDecel : Appearance.animationCurves.emphasizedAccel)
|
||||||
|
dialogBackground.implicitHeight = show ? backgroundHeight : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
||||||
|
|
||||||
|
MouseArea { // Clicking outside the dialog should dismiss
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
hoverEnabled: true
|
||||||
|
onPressed: root.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: dialogBackground
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
radius: Appearance.rounding.large
|
||||||
|
color: Appearance.colors.colLayer3
|
||||||
|
|
||||||
|
property real targetY: root.height / 2 - root.backgroundHeight / 2
|
||||||
|
y: root.show ? targetY : (targetY - root.backgroundAnimationMovementDistance)
|
||||||
|
implicitWidth: 350
|
||||||
|
implicitHeight: 0
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation {
|
||||||
|
id: dialogBackgroundHeightAnimation
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.animationCurves.emphasizedDecel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: dialogBackgroundHeightAnimation.duration
|
||||||
|
easing.type: dialogBackgroundHeightAnimation.easing.type
|
||||||
|
easing.bezierCurve: dialogBackgroundHeightAnimation.easing.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea { // So clicking inside the dialog won't dismiss
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentColumn
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: dialogBackground.radius
|
||||||
|
}
|
||||||
|
spacing: 16
|
||||||
|
opacity: root.show ? 1 : 0
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
// These shouldn't be needed but it would be a terrible waste of space to follow the spec
|
||||||
|
Layout.margins: -8
|
||||||
|
Layout.topMargin: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
implicitHeight: 1
|
||||||
|
color: Appearance.colors.colOutline
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: -Appearance.rounding.large
|
||||||
|
Layout.rightMargin: -Appearance.rounding.large
|
||||||
|
Layout.topMargin: -8
|
||||||
|
Layout.bottomMargin: -8
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Dialog Title"
|
||||||
|
font {
|
||||||
|
pixelSize: Appearance.font.pixelSize.title
|
||||||
|
family: Appearance.font.family.title
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ ContentPage {
|
|||||||
}
|
}
|
||||||
ContentSection {
|
ContentSection {
|
||||||
title: Translation.tr("AI")
|
title: Translation.tr("AI")
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("System prompt")
|
placeholderText: Translation.tr("System prompt")
|
||||||
text: Config.options.ai.systemPrompt
|
text: Config.options.ai.systemPrompt
|
||||||
@@ -115,7 +115,7 @@ ContentPage {
|
|||||||
|
|
||||||
ContentSection {
|
ContentSection {
|
||||||
title: Translation.tr("Networking")
|
title: Translation.tr("Networking")
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("User agent (for services that require it)")
|
placeholderText: Translation.tr("User agent (for services that require it)")
|
||||||
text: Config.options.networking.userAgent
|
text: Config.options.networking.userAgent
|
||||||
@@ -159,7 +159,7 @@ ContentPage {
|
|||||||
ConfigRow {
|
ConfigRow {
|
||||||
uniform: true
|
uniform: true
|
||||||
|
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("Action")
|
placeholderText: Translation.tr("Action")
|
||||||
text: Config.options.search.prefix.action
|
text: Config.options.search.prefix.action
|
||||||
@@ -168,7 +168,7 @@ ContentPage {
|
|||||||
Config.options.search.prefix.action = text;
|
Config.options.search.prefix.action = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("Clipboard")
|
placeholderText: Translation.tr("Clipboard")
|
||||||
text: Config.options.search.prefix.clipboard
|
text: Config.options.search.prefix.clipboard
|
||||||
@@ -177,7 +177,7 @@ ContentPage {
|
|||||||
Config.options.search.prefix.clipboard = text;
|
Config.options.search.prefix.clipboard = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("Emojis")
|
placeholderText: Translation.tr("Emojis")
|
||||||
text: Config.options.search.prefix.emojis
|
text: Config.options.search.prefix.emojis
|
||||||
@@ -190,7 +190,7 @@ ContentPage {
|
|||||||
}
|
}
|
||||||
ContentSubsection {
|
ContentSubsection {
|
||||||
title: Translation.tr("Web search")
|
title: Translation.tr("Web search")
|
||||||
MaterialTextField {
|
MaterialTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
placeholderText: Translation.tr("Base URL")
|
placeholderText: Translation.tr("Base URL")
|
||||||
text: Config.options.search.engineBaseUrl
|
text: Config.options.search.engineBaseUrl
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import qs.services
|
|||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
import qs.modules.common.widgets
|
import qs.modules.common.widgets
|
||||||
import qs.modules.common.functions
|
import qs.modules.common.functions
|
||||||
import "./quickToggles/"
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -16,8 +15,6 @@ import Quickshell.Hyprland
|
|||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
||||||
property int sidebarPadding: 12
|
|
||||||
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: sidebarRoot
|
id: sidebarRoot
|
||||||
@@ -67,124 +64,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: SidebarRightContent {}
|
||||||
implicitHeight: sidebarRightBackground.implicitHeight
|
|
||||||
implicitWidth: sidebarRightBackground.implicitWidth
|
|
||||||
|
|
||||||
StyledRectangularShadow {
|
|
||||||
target: sidebarRightBackground
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: sidebarRightBackground
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
|
||||||
implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
|
|
||||||
color: Appearance.colors.colLayer0
|
|
||||||
border.width: 1
|
|
||||||
border.color: Appearance.colors.colLayer0Border
|
|
||||||
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: sidebarPadding
|
|
||||||
spacing: sidebarPadding
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillHeight: false
|
|
||||||
spacing: 10
|
|
||||||
Layout.margins: 10
|
|
||||||
Layout.topMargin: 5
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
|
|
||||||
CustomIcon {
|
|
||||||
id: distroIcon
|
|
||||||
width: 25
|
|
||||||
height: 25
|
|
||||||
source: SystemInfo.distroIcon
|
|
||||||
colorize: true
|
|
||||||
color: Appearance.colors.colOnLayer0
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
font.pixelSize: Appearance.font.pixelSize.normal
|
|
||||||
color: Appearance.colors.colOnLayer0
|
|
||||||
text: Translation.tr("Up %1").arg(DateTime.uptime)
|
|
||||||
textFormat: Text.MarkdownText
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonGroup {
|
|
||||||
QuickToggleButton {
|
|
||||||
toggled: false
|
|
||||||
buttonIcon: "restart_alt"
|
|
||||||
onClicked: {
|
|
||||||
Hyprland.dispatch("reload")
|
|
||||||
Quickshell.reload(true)
|
|
||||||
}
|
|
||||||
StyledToolTip {
|
|
||||||
content: Translation.tr("Reload Hyprland & Quickshell")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QuickToggleButton {
|
|
||||||
toggled: false
|
|
||||||
buttonIcon: "settings"
|
|
||||||
onClicked: {
|
|
||||||
GlobalStates.sidebarRightOpen = false
|
|
||||||
Quickshell.execDetached(["qs", "-p", root.settingsQmlPath])
|
|
||||||
}
|
|
||||||
StyledToolTip {
|
|
||||||
content: Translation.tr("Settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QuickToggleButton {
|
|
||||||
toggled: false
|
|
||||||
buttonIcon: "power_settings_new"
|
|
||||||
onClicked: {
|
|
||||||
GlobalStates.sessionOpen = true
|
|
||||||
}
|
|
||||||
StyledToolTip {
|
|
||||||
content: Translation.tr("Session")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonGroup {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: 5
|
|
||||||
padding: 5
|
|
||||||
color: Appearance.colors.colLayer1
|
|
||||||
|
|
||||||
NetworkToggle {}
|
|
||||||
BluetoothToggle {}
|
|
||||||
NightLight {}
|
|
||||||
GameMode {}
|
|
||||||
IdleInhibitor {}
|
|
||||||
EasyEffectsToggle {}
|
|
||||||
CloudflareWarp {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center widget group
|
|
||||||
CenterWidgetGroup {
|
|
||||||
focus: sidebarRoot.visible
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
BottomWidgetGroup {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillHeight: false
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: implicitHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.services.network
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import "./quickToggles/"
|
||||||
|
import "./wifiNetworks/"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
||||||
|
property int sidebarPadding: 12
|
||||||
|
property string settingsQmlPath: Quickshell.shellPath("settings.qml")
|
||||||
|
property bool showDialog: false
|
||||||
|
property bool dialogIsWifi: true
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GlobalStates
|
||||||
|
function onSidebarRightOpenChanged() {
|
||||||
|
if (!GlobalStates.sidebarRightOpen) {
|
||||||
|
root.showDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: sidebarRightBackground.implicitHeight
|
||||||
|
implicitWidth: sidebarRightBackground.implicitWidth
|
||||||
|
|
||||||
|
StyledRectangularShadow {
|
||||||
|
target: sidebarRightBackground
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: sidebarRightBackground
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2
|
||||||
|
implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
|
||||||
|
color: Appearance.colors.colLayer0
|
||||||
|
border.width: 1
|
||||||
|
border.color: Appearance.colors.colLayer0Border
|
||||||
|
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: sidebarPadding
|
||||||
|
spacing: sidebarPadding
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillHeight: false
|
||||||
|
spacing: 10
|
||||||
|
Layout.margins: 10
|
||||||
|
Layout.topMargin: 5
|
||||||
|
Layout.bottomMargin: 0
|
||||||
|
|
||||||
|
CustomIcon {
|
||||||
|
id: distroIcon
|
||||||
|
width: 25
|
||||||
|
height: 25
|
||||||
|
source: SystemInfo.distroIcon
|
||||||
|
colorize: true
|
||||||
|
color: Appearance.colors.colOnLayer0
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.normal
|
||||||
|
color: Appearance.colors.colOnLayer0
|
||||||
|
text: Translation.tr("Up %1").arg(DateTime.uptime)
|
||||||
|
textFormat: Text.MarkdownText
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonGroup {
|
||||||
|
QuickToggleButton {
|
||||||
|
toggled: false
|
||||||
|
buttonIcon: "restart_alt"
|
||||||
|
onClicked: {
|
||||||
|
Hyprland.dispatch("reload")
|
||||||
|
Quickshell.reload(true)
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Reload Hyprland & Quickshell")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QuickToggleButton {
|
||||||
|
toggled: false
|
||||||
|
buttonIcon: "settings"
|
||||||
|
onClicked: {
|
||||||
|
GlobalStates.sidebarRightOpen = false
|
||||||
|
Quickshell.execDetached(["qs", "-p", root.settingsQmlPath])
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QuickToggleButton {
|
||||||
|
toggled: false
|
||||||
|
buttonIcon: "power_settings_new"
|
||||||
|
onClicked: {
|
||||||
|
GlobalStates.sessionOpen = true
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonGroup {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
spacing: 5
|
||||||
|
padding: 5
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
|
||||||
|
NetworkToggle {
|
||||||
|
altAction: () => {
|
||||||
|
Network.enableWifi()
|
||||||
|
Network.rescanWifi()
|
||||||
|
root.dialogIsWifi = true
|
||||||
|
root.showDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BluetoothToggle {}
|
||||||
|
NightLight {}
|
||||||
|
GameMode {}
|
||||||
|
IdleInhibitor {}
|
||||||
|
EasyEffectsToggle {}
|
||||||
|
CloudflareWarp {}
|
||||||
|
}
|
||||||
|
|
||||||
|
CenterWidgetGroup {
|
||||||
|
focus: sidebarRoot.visible
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
BottomWidgetGroup {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillHeight: false
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: implicitHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialog {
|
||||||
|
show: root.showDialog
|
||||||
|
onDismiss: root.showDialog = false
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowDialogTitle {
|
||||||
|
text: Translation.tr("Connect to Wi-Fi")
|
||||||
|
}
|
||||||
|
WindowDialogSeparator {
|
||||||
|
// TODO: add indeterminate progress bar when scanning
|
||||||
|
}
|
||||||
|
StyledListView {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: -15
|
||||||
|
Layout.bottomMargin: -16
|
||||||
|
Layout.leftMargin: -Appearance.rounding.large
|
||||||
|
Layout.rightMargin: -Appearance.rounding.large
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
spacing: 0
|
||||||
|
animateAppearance: false
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: [...Network.wifiNetworks].sort((a, b) => {
|
||||||
|
if (a.active && !b.active) return -1;
|
||||||
|
if (!a.active && b.active) return 1;
|
||||||
|
return b.strength - a.strength;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// model: Network.wifiNetworks
|
||||||
|
delegate: WifiNetworkItem {
|
||||||
|
required property WifiAccessPoint modelData
|
||||||
|
wifiNetwork: modelData
|
||||||
|
anchors {
|
||||||
|
left: parent?.left
|
||||||
|
right: parent?.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowDialogSeparator {}
|
||||||
|
WindowDialogButtonRow {
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Details")
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
|
||||||
|
GlobalStates.sidebarRightOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Done")
|
||||||
|
onClicked: root.showDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import qs
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.services
|
||||||
|
import qs.services.network
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: root
|
||||||
|
required property WifiAccessPoint wifiNetwork
|
||||||
|
|
||||||
|
horizontalPadding: Appearance.rounding.large
|
||||||
|
verticalPadding: 12
|
||||||
|
implicitWidth: mainLayout.implicitWidth + horizontalPadding * 2
|
||||||
|
implicitHeight: mainLayout.implicitHeight + verticalPadding * 2
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
buttonRadius: 0
|
||||||
|
colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)
|
||||||
|
colBackgroundHover: wifiNetwork?.askingPassword ? colBackground : Appearance.colors.colLayer3Hover
|
||||||
|
colRipple: Appearance.colors.colLayer3Active
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Network.connectToWifiNetwork(wifiNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
id: mainLayout
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: root.verticalPadding
|
||||||
|
bottomMargin: root.verticalPadding
|
||||||
|
leftMargin: root.horizontalPadding
|
||||||
|
rightMargin: root.horizontalPadding
|
||||||
|
}
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 10
|
||||||
|
MaterialSymbol {
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
text: root.wifiNetwork?.strength > 80 ? "signal_wifi_4_bar" :
|
||||||
|
root.wifiNetwork?.strength > 60 ? "network_wifi_3_bar" :
|
||||||
|
root.wifiNetwork?.strength > 40 ? "network_wifi_2_bar" :
|
||||||
|
root.wifiNetwork?.strength > 20 ? "network_wifi_1_bar" :
|
||||||
|
"signal_wifi_0_bar"
|
||||||
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.wifiNetwork?.ssid
|
||||||
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
}
|
||||||
|
MaterialSymbol {
|
||||||
|
visible: root.wifiNetwork?.isSecure || root.wifiNetwork?.active
|
||||||
|
text: root.wifiNetwork?.active ? "check" : Network.wifiConnectTarget === root.wifiNetwork ? "settings_ethernet" : "lock"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
color: Appearance.colors.colOnSurfaceVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: passwordPrompt
|
||||||
|
visible: root.wifiNetwork?.askingPassword
|
||||||
|
Layout.topMargin: 12
|
||||||
|
|
||||||
|
MaterialTextField {
|
||||||
|
id: passwordField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: Translation.tr("Password")
|
||||||
|
|
||||||
|
// Password
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
inputMethodHints: Qt.ImhSensitiveData
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
Network.changePassword(root.wifiNetwork, passwordField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Cancel")
|
||||||
|
onClicked: {
|
||||||
|
root.wifiNetwork.askingPassword = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: Translation.tr("Connect")
|
||||||
|
onClicked: {
|
||||||
|
Network.changePassword(root.wifiNetwork, passwordField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
// Took many bits from https://github.com/caelestia-dots/shell (GPLv3)
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import "./network"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple polled network state service.
|
* Network service with nmcli.
|
||||||
*/
|
*/
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -15,6 +18,12 @@ Singleton {
|
|||||||
property bool ethernet: false
|
property bool ethernet: false
|
||||||
|
|
||||||
property bool wifiEnabled: false
|
property bool wifiEnabled: false
|
||||||
|
property bool wifiScanning: false
|
||||||
|
property bool wifiConnecting: connectProc.running
|
||||||
|
property WifiAccessPoint wifiConnectTarget
|
||||||
|
readonly property list<WifiAccessPoint> wifiNetworks: []
|
||||||
|
readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null
|
||||||
|
|
||||||
property string networkName: ""
|
property string networkName: ""
|
||||||
property int networkStrength
|
property int networkStrength
|
||||||
property string materialSymbol: ethernet ? "lan" :
|
property string materialSymbol: ethernet ? "lan" :
|
||||||
@@ -27,15 +36,99 @@ Singleton {
|
|||||||
) : "signal_wifi_off"
|
) : "signal_wifi_off"
|
||||||
|
|
||||||
// Control
|
// Control
|
||||||
function toggleWifi(): void {
|
function enableWifi(enabled = true): void {
|
||||||
const cmd = wifiEnabled ? "off" : "on";
|
const cmd = enabled ? "on" : "off";
|
||||||
enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]);
|
enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleWifi(): void {
|
||||||
|
enableWifi(!wifiEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rescanWifi(): void {
|
||||||
|
wifiScanning = true;
|
||||||
|
rescanProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectToWifiNetwork(accessPoint: WifiAccessPoint): void {
|
||||||
|
accessPoint.askingPassword = false;
|
||||||
|
root.wifiConnectTarget = accessPoint;
|
||||||
|
// We use this instead of `nmcli connection up SSID` because this also creates a connection profile
|
||||||
|
connectProc.exec(["nmcli", "dev", "wifi", "connect", accessPoint.ssid])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnectWifiNetwork(): void {
|
||||||
|
if (active) disconnectProc.exec(["nmcli", "connection", "down", active.ssid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePassword(network: WifiAccessPoint, password: string, username = ""): void {
|
||||||
|
// TODO: enterprise wifi with username
|
||||||
|
network.askingPassword = false;
|
||||||
|
changePasswordProc.exec({
|
||||||
|
"environment": {
|
||||||
|
"PASSWORD": password
|
||||||
|
},
|
||||||
|
"command": ["bash", "-c", `nmcli connection modify ${network.ssid} wifi-sec.psk "$PASSWORD"`]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: enableWifiProc
|
id: enableWifiProc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: connectProc
|
||||||
|
environment: ({
|
||||||
|
LANG: "C",
|
||||||
|
LC_ALL: "C"
|
||||||
|
})
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: line => {
|
||||||
|
// print(line)
|
||||||
|
getNetworks.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr: SplitParser {
|
||||||
|
onRead: line => {
|
||||||
|
// print("err:", line)
|
||||||
|
if (line.includes("Secrets were required")) {
|
||||||
|
root.wifiConnectTarget.askingPassword = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.wifiConnectTarget.askingPassword = (exitCode !== 0)
|
||||||
|
root.wifiConnectTarget = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: disconnectProc
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: getNetworks.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: changePasswordProc
|
||||||
|
onExited: { // Re-attempt connection after changing password
|
||||||
|
connectProc.running = false
|
||||||
|
connectProc.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: rescanProcess
|
||||||
|
command: ["nmcli", "dev", "wifi", "list", "--rescan", "yes"]
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: {
|
||||||
|
wifiScanning = false;
|
||||||
|
getNetworks.running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Status update
|
// Status update
|
||||||
function update() {
|
function update() {
|
||||||
updateConnectionType.startCheck();
|
updateConnectionType.startCheck();
|
||||||
@@ -118,4 +211,78 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getNetworks
|
||||||
|
running: true
|
||||||
|
command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID,SECURITY", "d", "w"]
|
||||||
|
environment: ({
|
||||||
|
LANG: "C",
|
||||||
|
LC_ALL: "C"
|
||||||
|
})
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED";
|
||||||
|
const rep = new RegExp("\\\\:", "g");
|
||||||
|
const rep2 = new RegExp(PLACEHOLDER, "g");
|
||||||
|
|
||||||
|
const allNetworks = text.trim().split("\n").map(n => {
|
||||||
|
const net = n.replace(rep, PLACEHOLDER).split(":");
|
||||||
|
return {
|
||||||
|
active: net[0] === "yes",
|
||||||
|
strength: parseInt(net[1]),
|
||||||
|
frequency: parseInt(net[2]),
|
||||||
|
ssid: net[3],
|
||||||
|
bssid: net[4]?.replace(rep2, ":") ?? "",
|
||||||
|
security: net[5] || ""
|
||||||
|
};
|
||||||
|
}).filter(n => n.ssid && n.ssid.length > 0);
|
||||||
|
|
||||||
|
// Group networks by SSID and prioritize connected ones
|
||||||
|
const networkMap = new Map();
|
||||||
|
for (const network of allNetworks) {
|
||||||
|
const existing = networkMap.get(network.ssid);
|
||||||
|
if (!existing) {
|
||||||
|
networkMap.set(network.ssid, network);
|
||||||
|
} else {
|
||||||
|
// Prioritize active/connected networks
|
||||||
|
if (network.active && !existing.active) {
|
||||||
|
networkMap.set(network.ssid, network);
|
||||||
|
} else if (!network.active && !existing.active) {
|
||||||
|
// If both are inactive, keep the one with better signal
|
||||||
|
if (network.strength > existing.strength) {
|
||||||
|
networkMap.set(network.ssid, network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If existing is active and new is not, keep existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wifiNetworks = Array.from(networkMap.values());
|
||||||
|
|
||||||
|
const rNetworks = root.wifiNetworks;
|
||||||
|
|
||||||
|
const destroyed = rNetworks.filter(rn => !wifiNetworks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid));
|
||||||
|
for (const network of destroyed)
|
||||||
|
rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());
|
||||||
|
|
||||||
|
for (const network of wifiNetworks) {
|
||||||
|
const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid);
|
||||||
|
if (match) {
|
||||||
|
match.lastIpcObject = network;
|
||||||
|
} else {
|
||||||
|
rNetworks.push(apComp.createObject(root, {
|
||||||
|
lastIpcObject: network
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: apComp
|
||||||
|
|
||||||
|
WifiAccessPoint {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
required property var lastIpcObject
|
||||||
|
readonly property string ssid: lastIpcObject.ssid
|
||||||
|
readonly property string bssid: lastIpcObject.bssid
|
||||||
|
readonly property int strength: lastIpcObject.strength
|
||||||
|
readonly property int frequency: lastIpcObject.frequency
|
||||||
|
readonly property bool active: lastIpcObject.active
|
||||||
|
readonly property string security: lastIpcObject.security
|
||||||
|
readonly property bool isSecure: security.length > 0
|
||||||
|
|
||||||
|
property bool askingPassword: false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user