aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/system/quickshell/AnchoredPopup.qml77
-rw-r--r--modules/system/quickshell/Background.qml2
-rw-r--r--modules/system/quickshell/Bar.qml53
-rw-r--r--modules/system/quickshell/Bluetooth.qml116
-rw-r--r--modules/system/quickshell/BrightnessService.qml21
-rw-r--r--modules/system/quickshell/Clock.qml205
-rw-r--r--modules/system/quickshell/ConnectivityBox.qml34
-rw-r--r--modules/system/quickshell/ControlCenter.qml57
-rw-r--r--modules/system/quickshell/ControlTile.qml7
-rw-r--r--modules/system/quickshell/CustomCheckBox.qml4
-rw-r--r--modules/system/quickshell/GlobalState.qml27
-rw-r--r--modules/system/quickshell/IconCircle.qml8
-rw-r--r--modules/system/quickshell/Launcher.qml309
-rw-r--r--modules/system/quickshell/LockContext.qml7
-rw-r--r--modules/system/quickshell/LockSurface.qml99
-rw-r--r--modules/system/quickshell/Media.qml20
-rw-r--r--modules/system/quickshell/MediaCard.qml71
-rw-r--r--modules/system/quickshell/MicInput.qml204
-rw-r--r--modules/system/quickshell/MusicVisualizer.qml9
-rw-r--r--modules/system/quickshell/NotificationCard.qml82
-rw-r--r--modules/system/quickshell/NotificationPopupList.qml47
-rw-r--r--modules/system/quickshell/Notifications.qml96
-rw-r--r--modules/system/quickshell/PillSlider.qml12
-rw-r--r--modules/system/quickshell/Polkit.qml36
-rw-r--r--modules/system/quickshell/PopupCard.qml11
-rw-r--r--modules/system/quickshell/ScreenRecordIndicator.qml38
-rw-r--r--modules/system/quickshell/SliderBox.qml10
-rw-r--r--modules/system/quickshell/Theme.qml21
-rw-r--r--modules/system/quickshell/Toggle.qml19
-rw-r--r--modules/system/quickshell/TrayMenu.qml18
-rw-r--r--modules/system/quickshell/Volume.qml59
-rw-r--r--modules/system/quickshell/VolumeOSD.qml36
-rw-r--r--modules/system/quickshell/Wifi.qml159
-rw-r--r--modules/system/quickshell/WifiPasswordPrompt.qml78
-rw-r--r--modules/system/quickshell/Workspaces.qml27
-rw-r--r--modules/system/quickshell/qmldir4
-rw-r--r--modules/system/quickshell/shell.qml13
-rw-r--r--modules/system/quickshell/squircle.frag2
38 files changed, 1344 insertions, 754 deletions
diff --git a/modules/system/quickshell/AnchoredPopup.qml b/modules/system/quickshell/AnchoredPopup.qml
new file mode 100644
index 0000000..76d4ccd
--- /dev/null
+++ b/modules/system/quickshell/AnchoredPopup.qml
@@ -0,0 +1,77 @@
+import QtQuick
+import Quickshell
+import Quickshell.Wayland
+
+Scope {
+ id: root
+
+ property string popupName: ""
+ property var anchorWindow
+ property var anchorItem
+ property int popupGap: Theme.popupGap
+ readonly property bool open: GlobalState.activePopup === popupName
+ default property alias content: contentRoot.data
+
+ signal opened
+ signal closed
+
+ PanelWindow {
+ visible: root.open
+ anchors {
+ top: true
+ bottom: true
+ left: true
+ right: true
+ }
+ WlrLayershell.layer: WlrLayer.Top
+ WlrLayershell.margins.top: Theme.barHeight
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
+ exclusionMode: ExclusionMode.Ignore
+ color: Theme.transparent
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+ hoverEnabled: true
+ onClicked: GlobalState.close()
+ onWheel: wheel => wheel.accepted = true
+ }
+ }
+
+ PopupWindow {
+ id: popup
+ visible: root.open
+ grabFocus: true
+ implicitWidth: contentRoot.childrenRect.width
+ implicitHeight: contentRoot.childrenRect.height
+
+ anchor {
+ window: root.anchorWindow
+ item: root.anchorItem
+ edges: Edges.Bottom
+ gravity: Edges.Bottom
+ margins.top: root.popupGap
+ }
+
+ color: Theme.transparent
+
+ onVisibleChanged: {
+ if (visible) {
+ anchor.updateAnchor();
+ contentRoot.forceActiveFocus();
+ root.opened();
+ } else {
+ root.closed();
+ }
+ }
+
+ Item {
+ id: contentRoot
+ focus: true
+ width: childrenRect.width
+ height: childrenRect.height
+
+ Keys.onEscapePressed: GlobalState.close()
+ }
+ }
+}
diff --git a/modules/system/quickshell/Background.qml b/modules/system/quickshell/Background.qml
index 2bf9ae6..f15022d 100644
--- a/modules/system/quickshell/Background.qml
+++ b/modules/system/quickshell/Background.qml
@@ -5,7 +5,7 @@ import QtQuick
PanelWindow {
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.exclusiveZone: -1
-
+
anchors {
top: true
bottom: true
diff --git a/modules/system/quickshell/Bar.qml b/modules/system/quickshell/Bar.qml
index 77cfd1e..2356670 100644
--- a/modules/system/quickshell/Bar.qml
+++ b/modules/system/quickshell/Bar.qml
@@ -61,7 +61,7 @@ PanelWindow {
trayMenu.open(trayIcon);
}
} else {
- modelData.activate()
+ modelData.activate();
}
}
}
@@ -73,36 +73,37 @@ PanelWindow {
parentWindow: barWindow
}
- Bluetooth { anchors.verticalCenter: parent.verticalCenter }
-
- Wifi { anchors.verticalCenter: parent.verticalCenter }
-
- Volume { anchors.verticalCenter: parent.verticalCenter }
-
- Media { anchors.verticalCenter: parent.verticalCenter }
-
- ControlCenter { anchors.verticalCenter: parent.verticalCenter }
+ Bluetooth {
+ anchors.verticalCenter: parent.verticalCenter
+ }
- Text {
- id: clock
+ Wifi {
anchors.verticalCenter: parent.verticalCenter
- color: Theme.text
- font {
- family: Theme.mainFont
- pixelSize: 13
- weight: Font.Medium
- }
+ }
- Timer {
- interval: 1000
- running: true
- repeat: true
- onTriggered: parent.text = Qt.formatDateTime(new Date(), "ddd d MMM HH:mm:ss")
- }
+ Volume {
+ anchors.verticalCenter: parent.verticalCenter
+ }
- Component.onCompleted: text = Qt.formatDateTime(new Date(), "ddd d MMM HH:mm:ss")
+ ScreenRecordIndicator {
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ MicInput {
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Media {
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ ControlCenter {
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Clock {
+ anchors.verticalCenter: parent.verticalCenter
}
}
}
}
-
diff --git a/modules/system/quickshell/Bluetooth.qml b/modules/system/quickshell/Bluetooth.qml
index 17091ff..2e9232b 100644
--- a/modules/system/quickshell/Bluetooth.qml
+++ b/modules/system/quickshell/Bluetooth.qml
@@ -3,7 +3,7 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Widgets
-import QtQuick.Effects // Required for icon tinting
+import QtQuick.Effects
Item {
id: root
@@ -13,21 +13,23 @@ Item {
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
readonly property var allDevices: adapter ? adapter.devices.values : []
+ readonly property bool hasPaired: hasPairedDevice()
+ readonly property bool hasNewVisible: hasNewVisibleDevice()
- property bool hasPaired: false
- property bool hasNewVisible: false
-
- function updateState() {
- let paired = false
- let newVisible = false
-
+ function hasPairedDevice() {
for (const d of internal.allDevices) {
- if (d?.paired) paired = true
- if (d && !d.paired && d.name) newVisible = true
+ if (d?.paired)
+ return true;
}
+ return false;
+ }
- internal.hasPaired = paired
- internal.hasNewVisible = newVisible
+ function hasNewVisibleDevice() {
+ for (const d of internal.allDevices) {
+ if (d && !d.paired && d.name)
+ return true;
+ }
+ return false;
}
}
@@ -54,48 +56,25 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
- GlobalState.toggle("Bluetooth")
+ GlobalState.toggle("Bluetooth");
} else if (mouse.button === Qt.RightButton && internal.adapter) {
- internal.adapter.enabled = !internal.adapter.enabled
+ internal.adapter.enabled = !internal.adapter.enabled;
}
}
}
- PopupWindow {
- id: popup
- visible: GlobalState.activePopup === "Bluetooth"
- grabFocus: true
- implicitWidth: card.width
- implicitHeight: card.height
-
- anchor {
- window: barWindow
- item: root
- edges: Edges.Bottom
- gravity: Edges.Bottom
- margins.top: Theme.popupGap
- }
-
- color: Theme.transparent
-
- onVisibleChanged: {
- if (visible) {
- anchor.updateAnchor()
- if (internal.adapter) internal.adapter.discovering = true
- } else if (internal.adapter?.discovering) {
- internal.adapter.discovering = false
- }
- }
-
- Connections {
- target: internal.adapter ? internal.adapter.devices : null
- function onValuesChanged() { internal.updateState() }
- }
+ AnchoredPopup {
+ popupName: "Bluetooth"
+ anchorWindow: barWindow
+ anchorItem: root
+ onOpened: if (internal.adapter)
+ internal.adapter.discovering = true
+ onClosed: if (internal.adapter?.discovering)
+ internal.adapter.discovering = false
PopupCard {
id: card
- // Main Toggle Header
RowLayout {
Layout.fillWidth: true
spacing: 8
@@ -110,7 +89,9 @@ Item {
}
}
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
Toggle {
checked: internal.adapter?.enabled ?? false
@@ -125,10 +106,9 @@ Item {
color: Theme.border
}
- // Paired Devices Header
Text {
visible: internal.hasPaired
- text: "My Devices" // macOS typically labels this "My Devices"
+ text: "My Devices"
color: Theme.textMuted
font {
family: Theme.mainFont
@@ -137,7 +117,6 @@ Item {
}
}
- // Paired Devices List
ColumnLayout {
Layout.fillWidth: true
spacing: 2
@@ -156,23 +135,16 @@ Item {
fillColor: hovered ? Theme.surface : Theme.transparent
cornerRadius: 6
- Connections {
- target: pairedItem.modelData
- function onPairedChanged() { internal.updateState() }
- function onConnectedChanged() { internal.updateState() }
- function onNameChanged() { internal.updateState() }
- }
- Component.onCompleted: internal.updateState()
- Component.onDestruction: internal.updateState()
-
MouseArea {
id: pairedArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
- const d = pairedItem.modelData
- if (d.connected) d.disconnect()
- else d.connect()
+ const d = pairedItem.modelData;
+ if (d.connected)
+ d.disconnect();
+ else
+ d.connect();
}
}
@@ -183,19 +155,18 @@ Item {
}
spacing: 10
- // macOS uses bare icons in the list, tinted accent color when connected
Item {
Layout.preferredWidth: 20
Layout.preferredHeight: 20
-
+
Image {
id: deviceIcon
anchors.fill: parent
source: Quickshell.iconPath("bluetooth-active-symbolic")
sourceSize: Qt.size(20, 20)
- visible: false
+ visible: false
}
-
+
MultiEffect {
anchors.fill: deviceIcon
source: deviceIcon
@@ -215,7 +186,6 @@ Item {
elide: Text.ElideRight
}
- // macOS displays connection status text instead of a toggle/button
Text {
text: (pairedItem.modelData?.connected ?? false) ? "Connected" : "Not Connected"
color: Theme.textMuted
@@ -236,7 +206,6 @@ Item {
visible: internal.hasPaired
}
- // Other Devices Header
RowLayout {
Layout.fillWidth: true
@@ -251,7 +220,6 @@ Item {
Layout.fillWidth: true
}
- // Passive scanning indicator instead of interactive refresh button
Image {
width: 14
height: 14
@@ -272,7 +240,6 @@ Item {
}
}
- // Unpaired Devices List
ColumnLayout {
Layout.fillWidth: true
spacing: 2
@@ -290,14 +257,6 @@ Item {
fillColor: newArea.containsMouse ? Theme.surface : Theme.transparent
cornerRadius: 6
- Connections {
- target: newItem.modelData
- function onPairedChanged() { internal.updateState() }
- function onNameChanged() { internal.updateState() }
- }
- Component.onCompleted: internal.updateState()
- Component.onDestruction: internal.updateState()
-
MouseArea {
id: newArea
anchors.fill: parent
@@ -332,8 +291,7 @@ Item {
}
elide: Text.ElideRight
}
-
- // Replace + icon with "Connecting..." text when pairing
+
Text {
visible: newItem.modelData?.pairing ?? false
text: "Connecting..."
diff --git a/modules/system/quickshell/BrightnessService.qml b/modules/system/quickshell/BrightnessService.qml
index 991a6ec..6142188 100644
--- a/modules/system/quickshell/BrightnessService.qml
+++ b/modules/system/quickshell/BrightnessService.qml
@@ -2,31 +2,30 @@ import QtQuick
import Quickshell
import Quickshell.Io
-// Simplified brightness service using brightnessctl.
QtObject {
id: root
property real brightness: 0
property int maxBrightness: 1
function update() {
- updateProc.running = true
+ updateProc.running = true;
}
function setBrightness(value) {
- const raw = Math.round(value * maxBrightness)
- Quickshell.execDetached(["brightnessctl", "s", raw.toString()])
- brightness = value
+ const raw = Math.round(value * maxBrightness);
+ Quickshell.execDetached(["brightnessctl", "s", raw.toString()]);
+ brightness = value;
}
readonly property Process updateProc: Process {
- command: ["sh", "-c", "brightnessctl g && brightnessctl m"]
+ command: ["sh", "-c", "printf '%s %s\\n' \"$(brightnessctl g)\" \"$(brightnessctl m)\""]
stdout: SplitParser {
onRead: data => {
- const lines = data.trim().split("\n")
- if (lines.length >= 2) {
- const current = parseInt(lines[0])
- root.maxBrightness = parseInt(lines[1])
- root.brightness = current / root.maxBrightness
+ const parts = data.trim().split(/\s+/);
+ if (parts.length >= 2) {
+ const current = parseInt(parts[0]);
+ root.maxBrightness = parseInt(parts[1]);
+ root.brightness = current / root.maxBrightness;
}
}
}
diff --git a/modules/system/quickshell/Clock.qml b/modules/system/quickshell/Clock.qml
new file mode 100644
index 0000000..0ec3933
--- /dev/null
+++ b/modules/system/quickshell/Clock.qml
@@ -0,0 +1,205 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+
+Item {
+ id: root
+ width: clock.implicitWidth
+ height: parent.height
+
+ Text {
+ id: clock
+ anchors.verticalCenter: parent.verticalCenter
+ color: Theme.text
+ text: Qt.formatDateTime(new Date(), "ddd d MMM HH:mm:ss")
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ weight: Font.Medium
+ }
+
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: clock.text = Qt.formatDateTime(new Date(), "ddd d MMM HH:mm:ss")
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: GlobalState.toggle("NotificationHistory")
+ }
+
+ AnchoredPopup {
+ popupName: "NotificationHistory"
+ anchorWindow: barWindow
+ anchorItem: root
+
+ PopupCard {
+ width: 360
+ maxHeight: 520
+ margins: 14
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 4
+ Layout.rightMargin: 4
+ spacing: 8
+
+ Text {
+ text: "Notifications"
+ color: Theme.text
+ font {
+ family: Theme.mainFont
+ pixelSize: 14
+ weight: Font.DemiBold
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Text {
+ visible: GlobalState.notificationHistory.length > 0
+ text: "Clear"
+ color: clearArea.containsMouse ? Theme.text : Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 12
+ weight: Font.Medium
+ }
+
+ MouseArea {
+ id: clearArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: GlobalState.clearNotificationHistory()
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: Theme.border
+ }
+
+ Text {
+ visible: GlobalState.notificationHistory.length === 0
+ Layout.fillWidth: true
+ Layout.topMargin: 20
+ Layout.bottomMargin: 20
+ horizontalAlignment: Text.AlignHCenter
+ text: "No notifications"
+ color: Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ weight: Font.Medium
+ }
+ }
+
+ Repeater {
+ model: GlobalState.notificationHistory
+
+ delegate: Squircle {
+ id: historyItem
+ required property var modelData
+
+ Layout.fillWidth: true
+ Layout.preferredHeight: Math.max(64, historyLayout.implicitHeight + 20)
+ cornerRadius: 12
+ fillColor: historyHover.containsMouse ? Theme.surfaceHover : Theme.surface
+ strokeColor: Theme.border
+ strokeWidth: 1
+
+ MouseArea {
+ id: historyHover
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.NoButton
+ }
+
+ RowLayout {
+ id: historyLayout
+ anchors {
+ fill: parent
+ margins: 10
+ }
+ spacing: 10
+
+ IconCircle {
+ size: 32
+ source: "notifications"
+ active: false
+ Layout.alignment: Qt.AlignTop
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 2
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Text {
+ Layout.fillWidth: true
+ text: historyItem.modelData.nAppName || "Notification"
+ color: Theme.textMuted
+ elide: Text.ElideRight
+ font {
+ family: Theme.mainFont
+ pixelSize: 11
+ weight: Font.Medium
+ }
+ }
+
+ Text {
+ text: Qt.formatTime(historyItem.modelData.nTimestamp, "HH:mm")
+ color: Theme.textPlaceholder
+ font {
+ family: Theme.mainFont
+ pixelSize: 10
+ }
+ }
+ }
+
+ Text {
+ visible: text !== ""
+ Layout.fillWidth: true
+ text: historyItem.modelData.nSummary || ""
+ color: Theme.text
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ weight: Font.DemiBold
+ }
+ }
+
+ Text {
+ visible: text !== ""
+ Layout.fillWidth: true
+ text: historyItem.modelData.nBody || ""
+ color: Theme.textMuted
+ wrapMode: Text.WordWrap
+ maximumLineCount: 2
+ elide: Text.ElideRight
+ textFormat: Text.StyledText
+ font {
+ family: Theme.mainFont
+ pixelSize: 12
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/system/quickshell/ConnectivityBox.qml b/modules/system/quickshell/ConnectivityBox.qml
index e1fb03f..384aff2 100644
--- a/modules/system/quickshell/ConnectivityBox.qml
+++ b/modules/system/quickshell/ConnectivityBox.qml
@@ -14,19 +14,23 @@ Squircle {
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
- spacing: 0 // Using spacers for better control
+ spacing: 0
- // WiFi Row
Item {
Layout.fillWidth: true
height: 50
scale: wifiArea.pressed ? 0.96 : 1.0
- Behavior on scale { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
+ Behavior on scale {
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
+ }
RowLayout {
anchors.fill: parent
spacing: 12
-
+
IconCircle {
source: "network-wireless"
active: Networking.wifiEnabled
@@ -46,14 +50,16 @@ Squircle {
}
Text {
text: {
- const vs = Networking.devices?.values || []
+ const vs = Networking.devices?.values || [];
for (const device of vs) {
if (device.scannerEnabled !== undefined) {
- const nets = device.networks?.values || []
- for (const n of nets) if (n.connected) return n.name
+ const nets = device.networks?.values || [];
+ for (const n of nets)
+ if (n.connected)
+ return n.name;
}
}
- return Networking.wifiEnabled ? "On" : "Off"
+ return Networking.wifiEnabled ? "On" : "Off";
}
color: Theme.textMuted
font.pixelSize: 12
@@ -71,19 +77,23 @@ Squircle {
}
}
- Item { Layout.fillHeight: true } // Spacer
+ //Item { Layout.fillHeight: true }
- // Bluetooth Row
Item {
Layout.fillWidth: true
height: 50
scale: btArea.pressed ? 0.96 : 1.0
- Behavior on scale { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
+ Behavior on scale {
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
+ }
RowLayout {
anchors.fill: parent
spacing: 12
-
+
IconCircle {
source: "bluetooth-active"
active: Bluetooth.defaultAdapter?.enabled ?? false
diff --git a/modules/system/quickshell/ControlCenter.qml b/modules/system/quickshell/ControlCenter.qml
index 25aae5e..af13638 100644
--- a/modules/system/quickshell/ControlCenter.qml
+++ b/modules/system/quickshell/ControlCenter.qml
@@ -11,19 +11,21 @@ Item {
width: childrenRect.width
height: parent.height
- BrightnessService { id: brightnessService }
+ BrightnessService {
+ id: brightnessService
+ }
MouseArea {
anchors.fill: parent
onClicked: GlobalState.toggle("ControlCenter")
}
- // Indicator in bar
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Image {
- width: 20; height: 20
+ width: 20
+ height: 20
source: Quickshell.iconPath("emblem-system-symbolic")
sourceSize: Qt.size(width, height)
smooth: true
@@ -31,33 +33,16 @@ Item {
}
}
- PopupWindow {
- id: popup
- visible: GlobalState.activePopup === "ControlCenter"
- grabFocus: true
- implicitWidth: card.width
- implicitHeight: card.height
-
- anchor {
- window: barWindow
- item: root
- edges: Edges.Bottom
- gravity: Edges.Bottom
- margins.top: Theme.popupGap
- }
-
- color: "transparent"
-
- onVisibleChanged: {
- if (visible) anchor.updateAnchor()
- }
+ AnchoredPopup {
+ popupName: "ControlCenter"
+ anchorWindow: barWindow
+ anchorItem: root
PopupCard {
id: card
width: 320
margins: 16
- // TOP SECTION: Connectivity & Quick Actions
RowLayout {
Layout.fillWidth: true
spacing: 12
@@ -78,7 +63,6 @@ Item {
}
}
- // MIDDLE SECTION: Sliders
SliderBox {
label: "Display"
icon: "display-brightness"
@@ -90,16 +74,17 @@ Item {
label: "Sound"
icon: "audio-volume-high"
value: Pipewire.defaultAudioSink?.audio?.volume ?? 0
+ clickable: true
+ onClicked: Qt.callLater(() => GlobalState.open("Volume"))
onMoved: val => {
- const sink = Pipewire.defaultAudioSink
+ const sink = Pipewire.defaultAudioSink;
if (sink?.audio) {
- sink.audio.muted = false
- sink.audio.volume = val
+ sink.audio.muted = false;
+ sink.audio.volume = val;
}
}
}
- // NOW PLAYING BOX
Squircle {
Layout.fillWidth: true
height: 64
@@ -120,22 +105,22 @@ Item {
anchors.right: parent.right
anchors.leftMargin: 12
anchors.rightMargin: 12
-
+
isExpanded: false
readonly property var activePlayer: {
- const ps = Mpris.players?.values || []
- for (const p of ps) if (p.playbackState === MprisPlaybackState.Playing) return p
- return ps[0]
+ const ps = Mpris.players?.values || [];
+ for (const p of ps)
+ if (p.playbackState === MprisPlaybackState.Playing)
+ return p;
+ return ps[0] ?? null;
}
player: activePlayer
-
+
onClicked: Qt.callLater(() => GlobalState.open("Media"))
}
}
}
}
}
-
-
diff --git a/modules/system/quickshell/ControlTile.qml b/modules/system/quickshell/ControlTile.qml
index 2c8e0a6..d944e89 100644
--- a/modules/system/quickshell/ControlTile.qml
+++ b/modules/system/quickshell/ControlTile.qml
@@ -16,12 +16,13 @@ Item {
anchors.fill: parent
cornerRadius: 16
fillColor: root.active ? Theme.accent : Theme.surface
-
+
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
- if (root.clickHandler) root.clickHandler()
+ if (root.clickHandler)
+ root.clickHandler();
}
}
@@ -47,4 +48,4 @@ Item {
}
}
}
-} \ No newline at end of file
+}
diff --git a/modules/system/quickshell/CustomCheckBox.qml b/modules/system/quickshell/CustomCheckBox.qml
index 9b7014d..2ed3212 100644
--- a/modules/system/quickshell/CustomCheckBox.qml
+++ b/modules/system/quickshell/CustomCheckBox.qml
@@ -3,7 +3,7 @@ import QtQuick.Controls
CheckBox {
id: control
-
+
contentItem: Text {
text: control.text
color: Theme.text
@@ -12,7 +12,7 @@ CheckBox {
verticalAlignment: Text.AlignVCenter
leftPadding: control.indicator.width + control.spacing
}
-
+
indicator: Rectangle {
implicitWidth: 14
implicitHeight: 14
diff --git a/modules/system/quickshell/GlobalState.qml b/modules/system/quickshell/GlobalState.qml
index c212967..c657b31 100644
--- a/modules/system/quickshell/GlobalState.qml
+++ b/modules/system/quickshell/GlobalState.qml
@@ -1,23 +1,32 @@
-import QtQuick
-
pragma Singleton
+import QtQuick
-// Shared state to coordinate which popup is currently open.
-// Ensures only one menu is visible at a time.
QtObject {
property string activePopup: ""
+ property var notificationHistory: []
function open(name) {
- activePopup = ""
- activePopup = name
+ activePopup = "";
+ activePopup = name;
}
function close() {
- activePopup = ""
+ activePopup = "";
}
function toggle(name) {
- if (activePopup === name) activePopup = ""
- else activePopup = name
+ if (activePopup === name)
+ activePopup = "";
+ else
+ activePopup = name;
+ }
+
+ function addNotification(data) {
+ const entry = Object.assign({}, data);
+ notificationHistory = [entry].concat(notificationHistory).slice(0, 50);
+ }
+
+ function clearNotificationHistory() {
+ notificationHistory = [];
}
}
diff --git a/modules/system/quickshell/IconCircle.qml b/modules/system/quickshell/IconCircle.qml
index 50d5dd6..35a399c 100644
--- a/modules/system/quickshell/IconCircle.qml
+++ b/modules/system/quickshell/IconCircle.qml
@@ -4,7 +4,7 @@ import Quickshell
Rectangle {
id: root
-
+
property string source: ""
property bool active: false
property real size: 24
@@ -23,8 +23,8 @@ Rectangle {
sourceSize: Qt.size(width, height)
smooth: true
mipmap: true
-
- visible: false
+
+ visible: false
}
MultiEffect {
@@ -32,7 +32,7 @@ Rectangle {
source: iconImage
colorizationColor: "#FFFFFF"
colorization: 1.0
-
+
opacity: root.active ? 1.0 : 0.6
}
}
diff --git a/modules/system/quickshell/Launcher.qml b/modules/system/quickshell/Launcher.qml
index fc0dc3d..184271f 100644
--- a/modules/system/quickshell/Launcher.qml
+++ b/modules/system/quickshell/Launcher.qml
@@ -11,25 +11,26 @@ PanelWindow {
WlrLayershell.namespace: "launcher"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.exclusiveZone: -1
-
+
exclusionMode: ExclusionMode.Ignore
-
- // Centering logic
+
anchors {
top: true
+ bottom: true
+ left: true
+ right: true
}
- margins.top: 200
implicitWidth: 600
implicitHeight: 400
-
+
color: "transparent"
visible: GlobalState.activePopup === "Launcher"
-
+
onVisibleChanged: {
if (visible) {
- searchInput.forceActiveFocus()
- searchInput.text = ""
+ searchInput.forceActiveFocus();
+ searchInput.text = "";
}
}
@@ -37,21 +38,35 @@ PanelWindow {
id: internal
function filterApps(searchText) {
- const search = searchText.toLowerCase()
- const apps = DesktopEntries.applications.values
-
+ const search = searchText.toLowerCase();
+ const apps = DesktopEntries.applications.values;
+
return apps.filter(app => {
- if (app.noDisplay) return false
- if (!search) return true
-
- return app.name.toLowerCase().includes(search) ||
- (app.comment && app.comment.toLowerCase().includes(search))
- })
+ if (app.noDisplay)
+ return false;
+ if (!search)
+ return true;
+
+ return app.name.toLowerCase().includes(search) || (app.comment && app.comment.toLowerCase().includes(search));
+ });
}
}
- FocusScope {
+ MouseArea {
anchors.fill: parent
+ acceptedButtons: Qt.AllButtons
+ onClicked: GlobalState.close()
+ onWheel: wheel => wheel.accepted = true
+ }
+
+ FocusScope {
+ width: 600
+ height: 400
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ topMargin: 200
+ }
focus: true
Squircle {
@@ -62,7 +77,6 @@ PanelWindow {
strokeColor: Theme.border
strokeWidth: 1
- // Capture Escape to close
Keys.onEscapePressed: GlobalState.close()
ColumnLayout {
@@ -70,159 +84,160 @@ PanelWindow {
anchors.margins: 12
spacing: 8
- // Search Header
- RowLayout {
- Layout.fillWidth: true
- spacing: 12
-
- Image {
- width: 24; height: 24
- source: Quickshell.iconPath("system-search-symbolic")
- sourceSize: Qt.size(24, 24)
- smooth: true
- mipmap: true
- opacity: 0.7
- }
-
- TextInput {
- id: searchInput
+ RowLayout {
Layout.fillWidth: true
- font {
- family: Theme.mainFont
- pixelSize: 20
+ spacing: 12
+
+ Image {
+ width: 24
+ height: 24
+ source: Quickshell.iconPath("system-search-symbolic")
+ sourceSize: Qt.size(24, 24)
+ smooth: true
+ mipmap: true
+ opacity: 0.7
}
- color: Theme.text
- clip: true
-
- focus: true
- cursorVisible: true
- selectByMouse: true
- inputMethodHints: Qt.ImhNoPredictiveText
-
- Text {
- visible: searchInput.text === ""
- text: "Search Applications..."
+
+ TextInput {
+ id: searchInput
+ Layout.fillWidth: true
+ font {
+ family: Theme.mainFont
+ pixelSize: 20
+ }
color: Theme.text
- opacity: 0.3
- font: searchInput.font
- }
+ clip: true
+
+ focus: true
+ cursorVisible: true
+ selectByMouse: true
+ inputMethodHints: Qt.ImhNoPredictiveText
+
+ Text {
+ visible: searchInput.text === ""
+ text: "Search Applications..."
+ color: Theme.text
+ opacity: 0.3
+ font: searchInput.font
+ }
- onTextChanged: resultsList.currentIndex = 0
-
- Keys.onPressed: event => {
- if (event.key === Qt.Key_Down) {
- resultsList.incrementCurrentIndex()
- event.accepted = true
- } else if (event.key === Qt.Key_Up) {
- resultsList.decrementCurrentIndex()
- event.accepted = true
- } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
- if (resultsList.count > 0 && resultsList.currentIndex >= 0) {
- if (resultsList.currentItem) {
- resultsList.currentItem.launch()
+ onTextChanged: resultsList.currentIndex = 0
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Down) {
+ resultsList.incrementCurrentIndex();
+ event.accepted = true;
+ } else if (event.key === Qt.Key_Up) {
+ resultsList.decrementCurrentIndex();
+ event.accepted = true;
+ } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
+ if (resultsList.count > 0 && resultsList.currentIndex >= 0) {
+ if (resultsList.currentItem) {
+ resultsList.currentItem.launch();
+ }
}
+ event.accepted = true;
}
- event.accepted = true
}
}
}
- }
-
- Rectangle {
- Layout.fillWidth: true
- height: 1
- color: Theme.border
- }
- ListView {
- id: resultsList
- Layout.fillWidth: true
- Layout.fillHeight: true
- clip: true
- boundsBehavior: Flickable.StopAtBounds
- highlightFollowsCurrentItem: true
- highlightMoveDuration: 150
- highlightResizeDuration: 0
-
- highlight: Squircle {
- width: resultsList.width
- height: 44
- cornerRadius: 8
- fillColor: Theme.surface
- z: 1
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: Theme.border
}
-
- model: internal.filterApps(searchInput.text)
-
- delegate: Squircle {
- id: delegateRoot
- required property var modelData
- required property int index
-
- height: 44
- width: resultsList.width
-
- cornerRadius: 8
- fillColor: "transparent"
- z: 5
- function launch() {
- if (modelData && modelData.execute) {
- modelData.execute()
- GlobalState.close()
- }
+ ListView {
+ id: resultsList
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ boundsBehavior: Flickable.StopAtBounds
+ highlightFollowsCurrentItem: true
+ highlightMoveDuration: 150
+ highlightResizeDuration: 0
+
+ highlight: Squircle {
+ width: resultsList.width
+ height: 44
+ cornerRadius: 8
+ fillColor: Theme.surface
+ z: 1
}
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 12
- anchors.rightMargin: 12
- spacing: 12
+ model: internal.filterApps(searchInput.text)
+
+ delegate: Squircle {
+ id: delegateRoot
+ required property var modelData
+ required property int index
+
+ height: 44
+ width: resultsList.width
- Image {
- width: 24; height: 24
- source: Quickshell.iconPath(modelData.icon)
- sourceSize: Qt.size(32, 32)
- smooth: true
- mipmap: true
+ cornerRadius: 8
+ fillColor: "transparent"
+ z: 5
+
+ function launch() {
+ if (modelData && modelData.execute) {
+ modelData.execute();
+ GlobalState.close();
+ }
}
- Column {
- Layout.fillWidth: true
- Text {
- text: delegateRoot.modelData.name
- color: Theme.text
- font {
- family: Theme.mainFont
- pixelSize: 14
- weight: Font.Medium
- }
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 12
+ anchors.rightMargin: 12
+ spacing: 12
+
+ Image {
+ width: 24
+ height: 24
+ source: Quickshell.iconPath(modelData.icon)
+ sourceSize: Qt.size(32, 32)
+ smooth: true
+ mipmap: true
}
- Text {
- visible: delegateRoot.modelData.comment !== ""
- text: delegateRoot.modelData.comment
- color: Theme.textMuted
- font {
- family: Theme.mainFont
- pixelSize: 11
+
+ Column {
+ Layout.fillWidth: true
+ Text {
+ text: delegateRoot.modelData.name
+ color: Theme.text
+ font {
+ family: Theme.mainFont
+ pixelSize: 14
+ weight: Font.Medium
+ }
+ }
+ Text {
+ visible: delegateRoot.modelData.comment !== ""
+ text: delegateRoot.modelData.comment
+ color: Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 11
+ }
+ elide: Text.ElideRight
+ width: parent.width
}
- elide: Text.ElideRight
- width: parent.width
}
}
- }
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onEntered: resultsList.currentIndex = index
- onClicked: delegateRoot.launch()
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: resultsList.currentIndex = index
+ onClicked: delegateRoot.launch()
+ }
}
+
+ ScrollBar.vertical: ScrollBar {}
}
-
- ScrollBar.vertical: ScrollBar {}
}
}
}
}
-}
diff --git a/modules/system/quickshell/LockContext.qml b/modules/system/quickshell/LockContext.qml
index 05fd1de..ea32a57 100644
--- a/modules/system/quickshell/LockContext.qml
+++ b/modules/system/quickshell/LockContext.qml
@@ -4,8 +4,8 @@ import Quickshell.Services.Pam
Scope {
id: root
- signal unlocked()
- signal failed()
+ signal unlocked
+ signal failed
property string currentText: ""
property bool unlockInProgress: false
@@ -18,7 +18,8 @@ Scope {
}
function tryUnlock() {
- if (currentText === "" || unlockInProgress) return;
+ if (currentText === "" || unlockInProgress)
+ return;
unlockInProgress = true;
pam.start();
}
diff --git a/modules/system/quickshell/LockSurface.qml b/modules/system/quickshell/LockSurface.qml
index eacd98f..1a4667a 100644
--- a/modules/system/quickshell/LockSurface.qml
+++ b/modules/system/quickshell/LockSurface.qml
@@ -19,19 +19,18 @@ Item {
running: true
stdout: SplitParser {
onRead: line => {
- root.realName = line.trim()
+ root.realName = line.trim();
}
}
}
- // Capture keyboard input to reveal the text field
- Keys.onPressed: (event) => {
+ Keys.onPressed: event => {
if (!showInput && event.key !== Qt.Key_Escape) {
- showInput = true
- passwordInput.forceActiveFocus()
+ showInput = true;
+ passwordInput.forceActiveFocus();
if (event.text !== "") {
- passwordInput.text = event.text
- root.context.currentText = event.text
+ passwordInput.text = event.text;
+ root.context.currentText = event.text;
}
}
}
@@ -39,8 +38,8 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: {
- root.showInput = true
- passwordInput.forceActiveFocus()
+ root.showInput = true;
+ passwordInput.forceActiveFocus();
}
}
@@ -50,7 +49,6 @@ Item {
fillMode: Image.PreserveAspectCrop
}
- // Main Content (Clock)
Column {
anchors {
top: parent.top
@@ -79,9 +77,9 @@ Item {
weight: Font.DemiBold
letterSpacing: -8
}
- } }
+ }
+ }
- // Profile and Password Area (Bottom Center)
ColumnLayout {
id: passwordContainer
anchors {
@@ -93,13 +91,55 @@ Item {
SequentialAnimation {
id: shakeAnimation
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: -6; duration: 30; easing.type: Easing.OutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: 6; duration: 60; easing.type: Easing.InOutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: -6; duration: 60; easing.type: Easing.InOutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: 6; duration: 60; easing.type: Easing.InOutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: -6; duration: 60; easing.type: Easing.InOutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: 6; duration: 60; easing.type: Easing.InOutQuad }
- NumberAnimation { target: passwordContainer; property: "anchors.horizontalCenterOffset"; to: 0; duration: 30; easing.type: Easing.InQuad }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: -6
+ duration: 30
+ easing.type: Easing.OutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: 6
+ duration: 60
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: -6
+ duration: 60
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: 6
+ duration: 60
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: -6
+ duration: 60
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: 6
+ duration: 60
+ easing.type: Easing.InOutQuad
+ }
+ NumberAnimation {
+ target: passwordContainer
+ property: "anchors.horizontalCenterOffset"
+ to: 0
+ duration: 30
+ easing.type: Easing.InQuad
+ }
}
Connections {
@@ -109,7 +149,6 @@ Item {
}
}
- // Avatar Placeholder
Rectangle {
Layout.alignment: Qt.AlignHCenter
width: 60
@@ -118,7 +157,6 @@ Item {
color: "#b0b0b0"
}
- // User Name
Text {
Layout.alignment: Qt.AlignHCenter
text: root.realName || "User"
@@ -131,7 +169,6 @@ Item {
visible: !root.showInput
}
- // Prompt Text
Text {
Layout.alignment: Qt.AlignHCenter
text: "Enter Password"
@@ -144,24 +181,20 @@ Item {
visible: !root.showInput
}
- // Password Input Field
TextField {
id: passwordInput
Layout.preferredWidth: 220
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignHCenter
visible: root.showInput
-
+
placeholderText: root.context.pamMessage || "Enter Password"
placeholderTextColor: Theme.textPlaceholder
echoMode: TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
enabled: !root.context.unlockInProgress
-
- // Update context when text is changed directly in this field
onTextChanged: root.context.currentText = this.text
-
- // Sync text from context to support multi-monitor mirroring securely
+
Connections {
target: root.context
function onCurrentTextChanged() {
@@ -170,7 +203,7 @@ Item {
}
}
}
-
+
background: Rectangle {
radius: height / 2
color: Theme.surface
@@ -184,13 +217,13 @@ Item {
verticalAlignment: TextInput.AlignVCenter
onAccepted: {
- root.context.tryUnlock()
+ root.context.tryUnlock();
}
Keys.onEscapePressed: {
- root.showInput = false
- root.context.currentText = ""
- root.forceActiveFocus()
+ root.showInput = false;
+ root.context.currentText = "";
+ root.forceActiveFocus();
}
}
}
diff --git a/modules/system/quickshell/Media.qml b/modules/system/quickshell/Media.qml
index ee5bf42..f16207c 100644
--- a/modules/system/quickshell/Media.qml
+++ b/modules/system/quickshell/Media.qml
@@ -42,22 +42,10 @@ Item {
onClicked: GlobalState.toggle("Media")
}
- PopupWindow {
- id: popup
- visible: GlobalState.activePopup === "Media"
- grabFocus: true
- implicitWidth: card.width
- implicitHeight: card.height
-
- anchor {
- window: barWindow
- item: root
- edges: Edges.Bottom
- gravity: Edges.Bottom
- margins.top: Theme.popupGap
- }
-
- color: Theme.transparent
+ AnchoredPopup {
+ popupName: "Media"
+ anchorWindow: barWindow
+ anchorItem: root
PopupCard {
id: card
diff --git a/modules/system/quickshell/MediaCard.qml b/modules/system/quickshell/MediaCard.qml
index 5aba34c..3156734 100644
--- a/modules/system/quickshell/MediaCard.qml
+++ b/modules/system/quickshell/MediaCard.qml
@@ -8,13 +8,16 @@ Item {
property QtObject player: null
property bool isExpanded: false
- signal clicked()
+ signal clicked
Layout.fillWidth: true
implicitHeight: layout.implicitHeight
Behavior on implicitHeight {
- NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
+ NumberAnimation {
+ duration: 200
+ easing.type: Easing.OutCubic
+ }
}
MouseArea {
@@ -24,9 +27,9 @@ Item {
}
function formatTime(s) {
- const mins = Math.floor(s / 60)
- const secs = Math.floor(s % 60)
- return `${mins}:${secs.toString().padStart(2, '0')}`
+ const mins = Math.floor(s / 60);
+ const secs = Math.floor(s % 60);
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
}
ColumnLayout {
@@ -35,7 +38,6 @@ Item {
anchors.right: parent.right
spacing: 16
- // Top Row: Minimal view and header for expanded view
RowLayout {
Layout.fillWidth: true
spacing: 12
@@ -47,9 +49,21 @@ Item {
fillColor: root.player ? Theme.transparent : Theme.surface
clip: true
- Behavior on width { NumberAnimation { duration: 200 } }
- Behavior on height { NumberAnimation { duration: 200 } }
- Behavior on cornerRadius { NumberAnimation { duration: 200 } }
+ Behavior on width {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ Behavior on height {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ Behavior on cornerRadius {
+ NumberAnimation {
+ duration: 200
+ }
+ }
Image {
anchors.fill: parent
@@ -101,14 +115,14 @@ Item {
}
}
- // Minimal Controls: Only visible when not expanded
Row {
visible: !root.isExpanded
spacing: 12
Layout.alignment: Qt.AlignVCenter
Item {
- width: 24; height: 24
+ width: 24
+ height: 24
Image {
anchors.centerIn: parent
source: Quickshell.iconPath(root.player?.playbackState === MprisPlaybackState.Playing ? "media-playback-pause-symbolic" : "media-playback-start-symbolic")
@@ -119,12 +133,14 @@ Item {
id: minPlayMouse
anchors.fill: parent
enabled: root.player?.canTogglePlaying ?? false
- onClicked: if (root.player) root.player.togglePlaying()
+ onClicked: if (root.player)
+ root.player.togglePlaying()
}
}
Item {
- width: 24; height: 24
+ width: 24
+ height: 24
Image {
anchors.centerIn: parent
source: Quickshell.iconPath("media-skip-forward-symbolic")
@@ -135,13 +151,13 @@ Item {
id: minNextMouse
anchors.fill: parent
enabled: root.player?.canGoNext ?? false
- onClicked: if (root.player) root.player.next()
+ onClicked: if (root.player)
+ root.player.next()
}
}
}
}
- // Expanded Controls: Only visible when expanded and a player exists
ColumnLayout {
visible: root.isExpanded && root.player !== null
Layout.fillWidth: true
@@ -159,7 +175,8 @@ Item {
value: root.player?.position || 0
enabled: root.player?.canSeek ?? false
- onMoved: if (root.player) root.player.position = value
+ onMoved: if (root.player)
+ root.player.position = value
Timer {
running: root.player?.playbackState === MprisPlaybackState.Playing && root.isExpanded
@@ -177,7 +194,9 @@ Item {
font.pixelSize: 9
font.weight: Font.Medium
}
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
Text {
text: root.formatTime(root.player?.length || 0)
color: Theme.textPlaceholder
@@ -192,7 +211,8 @@ Item {
spacing: 32
Item {
- width: 24; height: 24
+ width: 24
+ height: 24
Image {
anchors.centerIn: parent
source: Quickshell.iconPath("media-skip-backward-symbolic")
@@ -203,12 +223,14 @@ Item {
id: prevMouse
anchors.fill: parent
enabled: root.player?.canGoPrevious ?? false
- onClicked: if (root.player) root.player.previous()
+ onClicked: if (root.player)
+ root.player.previous()
}
}
Item {
- width: 32; height: 32
+ width: 32
+ height: 32
Image {
anchors.centerIn: parent
source: Quickshell.iconPath(root.player?.playbackState === MprisPlaybackState.Playing ? "media-playback-pause-symbolic" : "media-playback-start-symbolic")
@@ -219,12 +241,14 @@ Item {
id: maxPlayMouse
anchors.fill: parent
enabled: root.player?.canTogglePlaying ?? false
- onClicked: if (root.player) root.player.togglePlaying()
+ onClicked: if (root.player)
+ root.player.togglePlaying()
}
}
Item {
- width: 24; height: 24
+ width: 24
+ height: 24
Image {
anchors.centerIn: parent
source: Quickshell.iconPath("media-skip-forward-symbolic")
@@ -235,7 +259,8 @@ Item {
id: maxNextMouse
anchors.fill: parent
enabled: root.player?.canGoNext ?? false
- onClicked: if (root.player) root.player.next()
+ onClicked: if (root.player)
+ root.player.next()
}
}
}
diff --git a/modules/system/quickshell/MicInput.qml b/modules/system/quickshell/MicInput.qml
new file mode 100644
index 0000000..2a1f653
--- /dev/null
+++ b/modules/system/quickshell/MicInput.qml
@@ -0,0 +1,204 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.Pipewire
+
+Item {
+ id: root
+ width: indicator.visible ? indicator.width : 0
+ height: parent.height
+ visible: root.micInUse
+
+ readonly property PwNode source: Pipewire.defaultAudioSource
+ readonly property bool muted: source && source.audio ? source.audio.muted : true
+ readonly property real volume: source && source.audio ? source.audio.volume : 0
+ readonly property bool micInUse: {
+ const nodes = Pipewire.nodes?.values || [];
+ for (const node of nodes) {
+ if (node && (node.type & PwNodeType.AudioInStream))
+ return true;
+ }
+ return false;
+ }
+
+ onMicInUseChanged: {
+ if (!micInUse && GlobalState.activePopup === "MicInput")
+ GlobalState.close();
+ }
+
+ PwObjectTracker {
+ objects: root.source ? [root.source] : []
+ }
+
+ function setVolume(value) {
+ if (source?.ready && source?.audio) {
+ source.audio.muted = false;
+ source.audio.volume = value;
+ }
+ }
+
+ function toggleMute() {
+ if (source?.ready && source?.audio) {
+ source.audio.muted = !source.audio.muted;
+ }
+ }
+
+ function getDeviceIcon(node) {
+ return node?.properties?.["device.icon-name"] ?? "audio-input-microphone";
+ }
+
+ Squircle {
+ id: indicator
+ anchors.verticalCenter: parent.verticalCenter
+ width: 28
+ height: 22
+ cornerRadius: 8
+ fillColor: "#FFFF9500"
+
+ Image {
+ anchors.centerIn: parent
+ width: 16
+ height: 16
+ source: Quickshell.iconPath(root.muted ? "microphone-sensitivity-muted-symbolic" : "audio-input-microphone-symbolic")
+ sourceSize: Qt.size(width, height)
+ smooth: true
+ mipmap: true
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ cursorShape: Qt.PointingHandCursor
+ onClicked: mouse => {
+ if (mouse.button === Qt.RightButton)
+ root.toggleMute();
+ else
+ GlobalState.toggle("MicInput");
+ }
+ onWheel: wheel => {
+ const step = 0.05;
+ const next = wheel.angleDelta.y > 0 ? root.volume + step : root.volume - step;
+ root.setVolume(Math.max(0.0, Math.min(1.0, next)));
+ }
+ }
+
+ AnchoredPopup {
+ popupName: "MicInput"
+ anchorWindow: barWindow
+ anchorItem: root
+
+ PopupCard {
+ width: 280
+ margins: 14
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Microphone"
+ color: Theme.text
+ font {
+ family: Theme.mainFont
+ pixelSize: 14
+ weight: Font.DemiBold
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ Toggle {
+ checked: !root.muted
+ enabled: root.source !== null
+ onToggled: root.toggleMute()
+ }
+ }
+
+ PillSlider {
+ id: micSlider
+ Layout.fillWidth: true
+ icon: "audio-input-microphone-symbolic"
+ value: root.volume
+ enabled: root.source !== null
+ colorProgress: "#FFFF9500"
+ onMoved: root.setVolume(value)
+
+ Binding {
+ target: micSlider
+ property: "value"
+ value: root.volume
+ when: !micSlider.pressed
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: Theme.border
+ }
+
+ Text {
+ text: "Input Devices"
+ color: Theme.textMuted
+ font {
+ family: Theme.mainFont
+ pixelSize: 12
+ weight: Font.DemiBold
+ }
+ Layout.leftMargin: 4
+ }
+
+ Repeater {
+ model: Pipewire.nodes
+
+ delegate: Squircle {
+ id: inputItem
+ required property var modelData
+ readonly property bool isInput: modelData && (modelData.type & PwNodeType.AudioSource) && !modelData.isStream && modelData.name !== "Dummy-Driver"
+
+ visible: isInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: visible ? 40 : 0
+ cornerRadius: 6
+ fillColor: inputArea.containsMouse ? Theme.surfaceLighter : Theme.transparent
+
+ RowLayout {
+ anchors {
+ fill: parent
+ margins: 8
+ }
+ spacing: 12
+
+ IconCircle {
+ size: 24
+ source: root.getDeviceIcon(inputItem.modelData)
+ active: root.source && root.source.id === inputItem.modelData.id
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: inputItem.modelData.description || inputItem.modelData.nickname || inputItem.modelData.name
+ color: (root.source && root.source.id === inputItem.modelData.id) ? Theme.text : Theme.textDim
+ elide: Text.ElideRight
+ font {
+ family: Theme.mainFont
+ pixelSize: 13
+ }
+ }
+ }
+
+ MouseArea {
+ id: inputArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: Pipewire.preferredDefaultAudioSource = inputItem.modelData
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/system/quickshell/MusicVisualizer.qml b/modules/system/quickshell/MusicVisualizer.qml
index e136304..f0c3227 100644
--- a/modules/system/quickshell/MusicVisualizer.qml
+++ b/modules/system/quickshell/MusicVisualizer.qml
@@ -1,7 +1,5 @@
import QtQuick
-// iOS-style animated music visualizer bars.
-// Simulates audio reactivity by randomly changing bar heights when active.
Row {
id: root
property bool active: false
@@ -23,12 +21,15 @@ Row {
interval: 100 + Math.random() * 200
repeat: true
onTriggered: {
- bar.height = 4 + Math.random() * (root.height - 4)
+ bar.height = 4 + Math.random() * (root.height - 4);
}
}
Behavior on height {
- NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
}
}
}
diff --git a/modules/system/quickshell/NotificationCard.qml b/modules/system/quickshell/NotificationCard.qml
index 8b6478a..04810cb 100644
--- a/modules/system/quickshell/NotificationCard.qml
+++ b/modules/system/quickshell/NotificationCard.qml
@@ -22,7 +22,7 @@ Item {
property bool hovered: cardHover.containsMouse || closeHover.containsMouse || (replyInput && replyInput.activeFocus)
- readonly property bool hasBottom: (nActions ? nActions.count > 0 : false) || nHasInlineReply
+ readonly property bool hasBottom: (nActions ? nActions.length > 0 : false) || nHasInlineReply
signal actionInvoked(int id, string identifier)
signal replySent(int id, string text)
@@ -36,15 +36,23 @@ Item {
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
- if (diffMins < 1) return "Just now";
- if (diffMins < 60) return diffMins + "m ago";
- if (diffHours < 24) return diffHours + "h ago";
+ if (diffMins < 1)
+ return "Just now";
+ if (diffMins < 60)
+ return diffMins + "m ago";
+ if (diffHours < 24)
+ return diffHours + "h ago";
return Qt.formatTime(timestamp, "h:mm p");
}
width: ListView.view ? ListView.view.width : 400
height: contentColumn.implicitHeight + 32
- Behavior on height { NumberAnimation { duration: 200; easing.type: Easing.OutSine } }
+ Behavior on height {
+ NumberAnimation {
+ duration: 200
+ easing.type: Easing.OutSine
+ }
+ }
Squircle {
anchors.fill: parent
@@ -60,7 +68,8 @@ Item {
hoverEnabled: true
cursorShape: nClickable ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
- if (nClickable) card.activated(nId)
+ if (nClickable)
+ card.activated(nId);
}
}
@@ -79,7 +88,6 @@ Item {
spacing: 12
Layout.alignment: Qt.AlignTop
- // LEFT: Icon
Item {
width: 44
height: 44
@@ -94,14 +102,14 @@ Item {
Image {
visible: nAppIcon !== ""
anchors.centerIn: parent
- width: 30; height: 30
+ width: 30
+ height: 30
source: nAppIcon
fillMode: Image.PreserveAspectFit
smooth: true
}
}
- // MIDDLE: Text
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
@@ -171,7 +179,6 @@ Item {
}
}
- // RIGHT: Image
Item {
visible: nImage !== ""
width: visible ? 44 : 0
@@ -209,10 +216,10 @@ Item {
Layout.fillWidth: true
visible: card.hasBottom
spacing: 8
- Layout.leftMargin: 56 // Align with text
+ Layout.leftMargin: 56
RowLayout {
- visible: nActions && nActions.count > 0
+ visible: nActions && nActions.length > 0
Layout.fillWidth: true
spacing: 8
@@ -232,7 +239,12 @@ Item {
cornerRadius: 6
fillColor: actionHover.containsMouse ? Theme.surfaceHover : Theme.surfaceLighter
scale: actionHover.pressed ? 0.95 : 1.0
- Behavior on scale { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
+ Behavior on scale {
+ NumberAnimation {
+ duration: 150
+ easing.type: Easing.OutCubic
+ }
+ }
}
Text {
@@ -271,9 +283,12 @@ Item {
TextInput {
id: replyInput
anchors {
- left: parent.left; leftMargin: 10
- right: sendBtn.left; rightMargin: 6
- top: parent.top; bottom: parent.bottom
+ left: parent.left
+ leftMargin: 10
+ right: sendBtn.left
+ rightMargin: 6
+ top: parent.top
+ bottom: parent.bottom
}
verticalAlignment: TextInput.AlignVCenter
color: Theme.text
@@ -294,17 +309,25 @@ Item {
font: replyInput.font
}
- Keys.onReturnPressed: if (text.length > 0) { card.replySent(card.nId, text); text = "" }
- Keys.onEnterPressed: if (text.length > 0) { card.replySent(card.nId, text); text = "" }
+ Keys.onReturnPressed: if (text.length > 0) {
+ card.replySent(card.nId, text);
+ text = "";
+ }
+ Keys.onEnterPressed: if (text.length > 0) {
+ card.replySent(card.nId, text);
+ text = "";
+ }
}
Item {
id: sendBtn
anchors {
- right: parent.right; rightMargin: 4
+ right: parent.right
+ rightMargin: 4
verticalCenter: parent.verticalCenter
}
- width: 22; height: 22
+ width: 22
+ height: 22
opacity: replyInput.text.length > 0 ? 1.0 : 0.4
Rectangle {
@@ -326,8 +349,8 @@ Item {
enabled: replyInput.text.length > 0
cursorShape: Qt.PointingHandCursor
onClicked: {
- card.replySent(card.nId, replyInput.text)
- replyInput.text = ""
+ card.replySent(card.nId, replyInput.text);
+ replyInput.text = "";
}
}
}
@@ -335,7 +358,6 @@ Item {
}
}
- // Close button — top-left, visible on hover
Rectangle {
width: 20
height: 20
@@ -350,7 +372,11 @@ Item {
border.color: Theme.border
border.width: 1
opacity: card.hovered ? 1.0 : 0.0
- Behavior on opacity { NumberAnimation { duration: 150 } }
+ Behavior on opacity {
+ NumberAnimation {
+ duration: 150
+ }
+ }
Item {
anchors.centerIn: parent
@@ -397,9 +423,11 @@ Item {
target: card
function onHoveredChanged() {
if (!nResident) {
- if (card.hovered) hideTimer.stop()
- else hideTimer.restart()
+ if (card.hovered)
+ hideTimer.stop();
+ else
+ hideTimer.restart();
}
}
}
-} \ No newline at end of file
+}
diff --git a/modules/system/quickshell/NotificationPopupList.qml b/modules/system/quickshell/NotificationPopupList.qml
index 6b9280b..2ea7871 100644
--- a/modules/system/quickshell/NotificationPopupList.qml
+++ b/modules/system/quickshell/NotificationPopupList.qml
@@ -14,11 +14,14 @@ PanelWindow {
visible: popupList.count > 0
- anchors { top: true; right: true }
+ anchors {
+ top: true
+ right: true
+ }
WlrLayershell.margins.top: Theme.barHeight
exclusionMode: ExclusionMode.Ignore
color: "transparent"
-
+
readonly property int edgeMargin: 12
readonly property int animationSafeMargin: 80
readonly property int popupWidth: 400
@@ -43,7 +46,7 @@ PanelWindow {
model: root.popupModel
interactive: false
clip: false
-
+
delegate: NotificationCard {
onActionInvoked: (id, identifier) => root.actionInvoked(id, identifier)
onReplySent: (id, text) => root.replySent(id, text)
@@ -54,22 +57,46 @@ PanelWindow {
add: Transition {
ParallelAnimation {
- NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 250; easing.type: Easing.OutSine }
- NumberAnimation { property: "x"; from: popupWidth + edgeMargin; duration: 350; easing.type: Easing.OutBack }
+ NumberAnimation {
+ property: "opacity"
+ from: 0
+ to: 1
+ duration: 250
+ easing.type: Easing.OutSine
+ }
+ NumberAnimation {
+ property: "x"
+ from: popupWidth + edgeMargin
+ duration: 350
+ easing.type: Easing.OutBack
+ }
}
}
remove: Transition {
ParallelAnimation {
- NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 200; easing.type: Easing.InSine }
- NumberAnimation { property: "x"; to: popupWidth + edgeMargin; duration: 200; easing.type: Easing.InSine }
+ NumberAnimation {
+ property: "opacity"
+ from: 1
+ to: 0
+ duration: 200
+ easing.type: Easing.InSine
+ }
+ NumberAnimation {
+ property: "x"
+ to: popupWidth + edgeMargin
+ duration: 200
+ easing.type: Easing.InSine
+ }
}
}
displaced: Transition {
- NumberAnimation { properties: "x,y"; duration: 250; easing.type: Easing.OutSine }
+ NumberAnimation {
+ properties: "x,y"
+ duration: 250
+ easing.type: Easing.OutSine
+ }
}
}
}
-
-
diff --git a/modules/system/quickshell/Notifications.qml b/modules/system/quickshell/Notifications.qml
index d17e903..79223eb 100644
--- a/modules/system/quickshell/Notifications.qml
+++ b/modules/system/quickshell/Notifications.qml
@@ -11,68 +11,72 @@ Scope {
property int nextId: 0
property var liveNotifs: ({})
- ListModel { id: popupModel }
+ ListModel {
+ id: popupModel
+ }
function removeNotificationData(id) {
for (let i = 0; i < popupModel.count; i++) {
if (popupModel.get(i).nId === id) {
- popupModel.remove(i)
- break
+ popupModel.remove(i);
+ break;
}
}
- delete liveNotifs[id]
+ delete liveNotifs[id];
}
function dismissExplicitly(id) {
- const n = liveNotifs[id]
- if (n) n.dismiss()
- removeNotificationData(id)
+ const n = liveNotifs[id];
+ if (n)
+ n.dismiss();
+ removeNotificationData(id);
}
function hidePopup(id) {
for (let i = 0; i < popupModel.count; i++) {
if (popupModel.get(i).nId === id) {
- popupModel.remove(i)
- break
+ popupModel.remove(i);
+ break;
}
}
}
function activateById(id) {
- const n = liveNotifs[id]
- if (!n) return
-
- let invoked = false
+ const n = liveNotifs[id];
+ if (!n)
+ return;
+ let invoked = false;
for (const action of n.actions) {
if (action.identifier === "default") {
- action.invoke()
- invoked = true
- break
+ action.invoke();
+ invoked = true;
+ break;
}
}
if (!invoked && n.desktopEntry) {
- Quickshell.execDetached(["gtk-launch", n.desktopEntry])
+ Quickshell.execDetached(["gtk-launch", n.desktopEntry]);
}
- dismissExplicitly(id)
+ dismissExplicitly(id);
}
function invokeAction(id, identifier) {
- const n = liveNotifs[id]
+ const n = liveNotifs[id];
if (n) {
for (const action of n.actions) {
if (action.identifier === identifier) {
- action.invoke()
- break
+ action.invoke();
+ break;
}
}
}
- dismissExplicitly(id)
+ dismissExplicitly(id);
}
function sendReply(id, text) {
- const n = liveNotifs[id]
- if (n && n.hasInlineReply) n.sendInlineReply(text)
- dismissExplicitly(id)
+ const n = liveNotifs[id];
+ if (n && n.hasInlineReply)
+ n.sendInlineReply(text);
+ dismissExplicitly(id);
}
NotificationServer {
@@ -87,34 +91,38 @@ Scope {
persistenceSupported: true
onNotification: notif => {
- notif.tracked = true
+ notif.tracked = true;
- const id = nextId
- nextId = nextId + 1
- liveNotifs[id] = notif
+ const id = nextId;
+ nextId = nextId + 1;
+ liveNotifs[id] = notif;
- const acts = []
- let hasDefault = false
+ const acts = [];
+ let hasDefault = false;
for (const a of notif.actions) {
- if (a.identifier === "default") hasDefault = true
- else acts.push({ identifier: a.identifier, text: a.text })
+ if (a.identifier === "default")
+ hasDefault = true;
+ else
+ acts.push({
+ identifier: a.identifier,
+ text: a.text
+ });
}
- notif.closed.connect(() => removeNotificationData(id))
+ notif.closed.connect(() => removeNotificationData(id));
- let formattedAppIcon = notif.appIcon || ""
+ let formattedAppIcon = notif.appIcon || "";
if (formattedAppIcon !== "") {
- if (formattedAppIcon.startsWith("file://")) {
- } else if (formattedAppIcon.startsWith("/")) {
- formattedAppIcon = "file://" + formattedAppIcon
+ if (formattedAppIcon.startsWith("file://")) {} else if (formattedAppIcon.startsWith("/")) {
+ formattedAppIcon = "file://" + formattedAppIcon;
} else {
- formattedAppIcon = `image://icon/${formattedAppIcon}`
+ formattedAppIcon = `image://icon/${formattedAppIcon}`;
}
}
- let formattedImage = notif.image || ""
+ let formattedImage = notif.image || "";
if (formattedImage !== "" && formattedImage.startsWith("/")) {
- formattedImage = "file://" + formattedImage
+ formattedImage = "file://" + formattedImage;
}
const data = {
@@ -131,9 +139,10 @@ Scope {
nHasInlineReply: notif.hasInlineReply || false,
nReplyPlaceholder: notif.inlineReplyPlaceholder || "Reply",
nResident: notif.resident || false
- }
+ };
- popupModel.insert(0, data)
+ GlobalState.addNotification(data);
+ popupModel.insert(0, data);
}
}
@@ -146,4 +155,3 @@ Scope {
onHideRequested: id => scope.hidePopup(id)
}
}
-
diff --git a/modules/system/quickshell/PillSlider.qml b/modules/system/quickshell/PillSlider.qml
index 5330ef8..e24663d 100644
--- a/modules/system/quickshell/PillSlider.qml
+++ b/modules/system/quickshell/PillSlider.qml
@@ -12,15 +12,15 @@ Slider {
property color colorHandle: "#FFFFFF"
property color iconColor: Theme.iconDefault
- implicitHeight: 24
- padding: 0
+ implicitHeight: 24
+ padding: 0
background: Rectangle {
width: root.width
height: root.height
radius: height / 2
color: root.colorTrack
-
+
clip: true
Rectangle {
@@ -35,7 +35,7 @@ Slider {
anchors.left: parent.left
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
- source: Quickshell.iconPath(root.icon)
+ source: root.icon !== "" ? Quickshell.iconPath(root.icon) : ""
sourceSize: Qt.size(14, 14)
width: 14
height: 14
@@ -52,7 +52,7 @@ Slider {
handle: Item {
x: root.visualPosition * (root.availableWidth - width)
y: root.topPadding + root.availableHeight / 2 - height / 2
-
+
width: root.height
height: root.height
@@ -62,7 +62,7 @@ Slider {
radius: width / 2
color: root.colorHandle
border.width: 1
- border.color: "#1A000000"
+ border.color: "#1A000000"
}
MultiEffect {
diff --git a/modules/system/quickshell/Polkit.qml b/modules/system/quickshell/Polkit.qml
index 4ac8bd1..8c46084 100644
--- a/modules/system/quickshell/Polkit.qml
+++ b/modules/system/quickshell/Polkit.qml
@@ -10,12 +10,12 @@ PanelWindow {
PolkitAgent {
id: agent
-
+
Component.onCompleted: console.log("PolkitAgent status: " + (isRegistered ? "Registered" : "Not Registered"))
onIsRegisteredChanged: console.log("PolkitAgent registration changed: " + isRegistered)
-
+
onIsActiveChanged: {
- console.log("PolkitAgent active state: " + isActive)
+ console.log("PolkitAgent active state: " + isActive);
if (isActive) {
root.visible = true;
passwordInput.text = "";
@@ -35,14 +35,14 @@ PanelWindow {
right: true
}
exclusiveZone: 0
-
+
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
Rectangle {
anchors.fill: parent
color: Theme.scrim
- MouseArea {
+ MouseArea {
anchors.fill: parent
onClicked: {
if (agent.flow) {
@@ -64,7 +64,7 @@ PanelWindow {
MouseArea {
anchors.fill: parent
- onClicked: (mouse) => mouse.accepted = true
+ onClicked: mouse => mouse.accepted = true
}
RowLayout {
@@ -77,7 +77,7 @@ PanelWindow {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 64
Layout.preferredHeight: 64
-
+
Image {
anchors.fill: parent
source: agent.flow ? Quickshell.iconPath(agent.flow.iconName || "dialog-password-symbolic") : ""
@@ -136,7 +136,7 @@ PanelWindow {
font.pixelSize: 12
color: Theme.text
echoMode: (agent.flow && agent.flow.responseVisible) ? TextInput.Normal : TextInput.Password
-
+
background: Rectangle {
color: Theme.surface
radius: 4
@@ -153,17 +153,21 @@ PanelWindow {
}
}
- Item { Layout.preferredHeight: 4 }
+ Item {
+ Layout.preferredHeight: 4
+ }
RowLayout {
Layout.fillWidth: true
spacing: 8
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
Button {
text: "Cancel"
-
+
contentItem: Text {
text: parent.text
color: Theme.text
@@ -172,7 +176,7 @@ PanelWindow {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
-
+
background: Rectangle {
implicitWidth: 80
implicitHeight: 28
@@ -181,7 +185,7 @@ PanelWindow {
border.color: Theme.border
border.width: 1
}
-
+
onClicked: {
if (agent.flow) {
agent.flow.cancelAuthenticationRequest();
@@ -192,7 +196,7 @@ PanelWindow {
Button {
enabled: agent.flow && (!agent.flow.isResponseRequired || passwordInput.text !== "")
text: "Authenticate"
-
+
contentItem: Text {
text: parent.text
color: enabled ? Theme.text : Theme.textDisabled
@@ -200,14 +204,14 @@ PanelWindow {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
-
+
background: Rectangle {
implicitWidth: 100
implicitHeight: 28
color: parent.enabled ? (parent.hovered ? Theme.accentHover : Theme.accent) : Theme.surface
radius: 6
}
-
+
onClicked: {
if (agent.flow) {
agent.flow.submit(passwordInput.text);
diff --git a/modules/system/quickshell/PopupCard.qml b/modules/system/quickshell/PopupCard.qml
index 6563877..d4d609b 100644
--- a/modules/system/quickshell/PopupCard.qml
+++ b/modules/system/quickshell/PopupCard.qml
@@ -5,7 +5,7 @@ import QtQuick.Layouts
Squircle {
id: root
- readonly property int maxHeight: 440
+ property int maxHeight: 440
property int margins: 16
default property alias content: contentCol.data
@@ -13,7 +13,10 @@ Squircle {
height: Math.min(Math.max(contentCol.implicitHeight + 2 * margins, 80), maxHeight)
Behavior on height {
- SpringAnimation { spring: 3; damping: 0.25 }
+ SpringAnimation {
+ spring: 3
+ damping: 0.25
+ }
}
fillColor: Theme.bg
@@ -29,9 +32,7 @@ Squircle {
}
clip: true
contentWidth: availableWidth
- ScrollBar.vertical.policy: contentCol.implicitHeight > height
- ? ScrollBar.AsNeeded
- : ScrollBar.AlwaysOff
+ ScrollBar.vertical.policy: contentCol.implicitHeight > height ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
ColumnLayout {
id: contentCol
diff --git a/modules/system/quickshell/ScreenRecordIndicator.qml b/modules/system/quickshell/ScreenRecordIndicator.qml
new file mode 100644
index 0000000..6630b81
--- /dev/null
+++ b/modules/system/quickshell/ScreenRecordIndicator.qml
@@ -0,0 +1,38 @@
+import QtQuick
+import Quickshell
+import Quickshell.Services.Pipewire
+
+Item {
+ id: root
+ width: indicator.width
+ height: parent.height
+ visible: root.recording
+
+ readonly property bool recording: {
+ const nodes = Pipewire.nodes?.values || [];
+ for (const node of nodes) {
+ if (node && node.isStream && (node.type & PwNodeType.VideoSource))
+ return true;
+ }
+ return false;
+ }
+
+ Squircle {
+ id: indicator
+ anchors.verticalCenter: parent.verticalCenter
+ width: 28
+ height: 22
+ cornerRadius: 8
+ fillColor: Theme.destructive
+
+ Image {
+ anchors.centerIn: parent
+ width: 15
+ height: 15
+ source: Quickshell.iconPath("media-record-symbolic")
+ sourceSize: Qt.size(width, height)
+ smooth: true
+ mipmap: true
+ }
+ }
+}
diff --git a/modules/system/quickshell/SliderBox.qml b/modules/system/quickshell/SliderBox.qml
index 59b994d..1d84e72 100644
--- a/modules/system/quickshell/SliderBox.qml
+++ b/modules/system/quickshell/SliderBox.qml
@@ -7,7 +7,9 @@ Squircle {
property string label: ""
property string icon: ""
property real value: 0
+ property bool clickable: false
signal moved(real val)
+ signal clicked
Layout.fillWidth: true
height: 64
@@ -18,7 +20,9 @@ Squircle {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
- acceptedButtons: Qt.NoButton
+ acceptedButtons: root.clickable ? Qt.LeftButton : Qt.NoButton
+ cursorShape: root.clickable ? Qt.PointingHandCursor : Qt.ArrowCursor
+ onClicked: root.clicked()
}
ColumnLayout {
@@ -38,7 +42,9 @@ Squircle {
}
Layout.leftMargin: 2
}
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
}
PillSlider {
diff --git a/modules/system/quickshell/Theme.qml b/modules/system/quickshell/Theme.qml
index f409f98..249a808 100644
--- a/modules/system/quickshell/Theme.qml
+++ b/modules/system/quickshell/Theme.qml
@@ -1,22 +1,21 @@
-import QtQuick
-
pragma Singleton
+import QtQuick
QtObject {
// Basics
- readonly property color bg: "#33000000" // More translucent for macOS 18 style
+ readonly property color bg: "#33000000"
readonly property color barBg: "#66000000"
- readonly property color surface: "#4DFFFFFF" // Semi-transparent white/gray
+ readonly property color surface: "#4DFFFFFF"
readonly property color surfaceHover: "#66FFFFFF"
readonly property color surfaceLighter: "#80FFFFFF"
readonly property color border: "#1AFFFFFF"
-
+
// Accents
- readonly property color accent: "#007AFF" // macOS Blue
+ readonly property color accent: "#007AFF"
readonly property color accentHover: "#0066CC"
readonly property color destructive: "#FF3B30"
readonly property color focus: "#007AFF"
-
+
// Text
readonly property color text: "#FFFFFF"
readonly property color textDim: "#EBEBEB"
@@ -24,22 +23,22 @@ QtObject {
readonly property color textDisabled: "#999999"
readonly property color textPlaceholder: "#808080"
readonly property color textLight: "#FFFFFF"
-
+
// Icons
readonly property color iconDefault: "#FFFFFF"
readonly property color iconActive: "#007AFF"
-
+
// Components
readonly property color sliderTrack: "#33FFFFFF"
readonly property color sliderHandle: "#FFFFFF"
readonly property color sliderOutline: "#1A000000"
-
+
// Transparency Helpers
readonly property color scrim: "#80000000"
readonly property color transparent: "transparent"
// Layout
- readonly property int barHeight: 32 // Slightly taller for macOS look
+ readonly property int barHeight: 32
readonly property int popupGap: 8
// Fonts
diff --git a/modules/system/quickshell/Toggle.qml b/modules/system/quickshell/Toggle.qml
index 76eb36c..d9e83e2 100644
--- a/modules/system/quickshell/Toggle.qml
+++ b/modules/system/quickshell/Toggle.qml
@@ -1,12 +1,9 @@
import QtQuick
-// iOS-style on/off switch. `checked` is the visual state; the parent
-// owns truth and handles `toggled` by flipping it. Uses Item.enabled
-// for the disabled visual + input gating.
Rectangle {
id: root
property bool checked: false
- signal toggled()
+ signal toggled
width: 40
height: 22
@@ -19,13 +16,21 @@ Rectangle {
height: 18
radius: 9
color: Theme.text
- anchors { verticalCenter: parent.verticalCenter }
+ anchors {
+ verticalCenter: parent.verticalCenter
+ }
x: root.checked ? parent.width - width - 2 : 2
- Behavior on x { NumberAnimation { duration: 150 } }
+ Behavior on x {
+ NumberAnimation {
+ duration: 150
+ }
+ }
}
MouseArea {
- anchors { fill: parent }
+ anchors {
+ fill: parent
+ }
onClicked: root.toggled()
}
}
diff --git a/modules/system/quickshell/TrayMenu.qml b/modules/system/quickshell/TrayMenu.qml
index 9e2e9f2..e8dd837 100644
--- a/modules/system/quickshell/TrayMenu.qml
+++ b/modules/system/quickshell/TrayMenu.qml
@@ -6,33 +6,33 @@ Scope {
id: root
property var menuItem: null
property var parentWindow
- property var anchorItem
+ property var anchorItem: null
property bool active: false
function open(item) {
- anchorItem = item
- active = true
- menuAnchor.open()
+ anchorItem = item;
+ active = true;
+ menuAnchor.open();
}
function close() {
- active = false
- menuAnchor.close()
+ active = false;
+ menuAnchor.close();
}
QsMenuAnchor {
id: menuAnchor
menu: root.menuItem
-
+
anchor.window: root.parentWindow
anchor.item: root.anchorItem
anchor.edges: Edges.Bottom
anchor.gravity: Edges.Bottom
anchor.margins.top: Theme.popupGap
-
+
onVisibleChanged: {
if (!visible && root.active) {
- root.active = false
+ root.active = false;
}
}
}
diff --git a/modules/system/quickshell/Volume.qml b/modules/system/quickshell/Volume.qml
index c4d1625..ea151dc 100644
--- a/modules/system/quickshell/Volume.qml
+++ b/modules/system/quickshell/Volume.qml
@@ -9,12 +9,10 @@ Item {
width: childrenRect.width
height: parent.height
- // Properties bound directly to the Pipewire service
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property bool muted: sink && sink.audio ? sink.audio.muted : true
readonly property real volume: sink && sink.audio ? sink.audio.volume : 0
- // Pipewire nodes only emit property updates while tracked.
PwObjectTracker {
objects: root.sink ? [root.sink] : []
}
@@ -24,7 +22,6 @@ Item {
sink.audio.muted = false;
sink.audio.volume = value;
} else {
- // Fallback for unbound nodes
Quickshell.execDetached(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", value.toFixed(2)]);
}
}
@@ -38,7 +35,7 @@ Item {
}
function getDeviceIcon(node) {
- return node?.properties?.["device.icon-name"] ?? "audio-card"
+ return node?.properties?.["device.icon-name"] ?? "audio-card";
}
Row {
@@ -64,39 +61,23 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
- GlobalState.toggle("Volume")
+ GlobalState.toggle("Volume");
} else if (mouse.button === Qt.RightButton) {
- root.toggleMute()
+ root.toggleMute();
}
}
onWheel: wheel => {
- const step = 0.05
- const next = wheel.angleDelta.y > 0 ? root.volume + step : root.volume - step
- root.setVolume(Math.max(0.0, Math.min(1.0, next)))
+ const step = 0.05;
+ const next = wheel.angleDelta.y > 0 ? root.volume + step : root.volume - step;
+ root.setVolume(Math.max(0.0, Math.min(1.0, next)));
}
}
- PopupWindow {
- id: popup
- visible: GlobalState.activePopup === "Volume"
- grabFocus: true
- implicitWidth: bgRect.width
- implicitHeight: bgRect.height
-
- anchor {
- window: barWindow
- item: root
- edges: Edges.Bottom
- gravity: Edges.Bottom
- margins.top: Theme.popupGap
- }
-
- color: "transparent"
-
- onVisibleChanged: {
- if (visible) anchor.updateAnchor()
- }
-
+ AnchoredPopup {
+ popupName: "Volume"
+ anchorWindow: barWindow
+ anchorItem: root
+
Squircle {
id: bgRect
width: 260
@@ -140,7 +121,11 @@ Item {
}
}
- Rectangle { width: parent.width; height: 1; color: Theme.border }
+ Rectangle {
+ width: parent.width
+ height: 1
+ color: Theme.border
+ }
Text {
text: "Output Devices"
@@ -160,23 +145,23 @@ Item {
property bool isOutput: modelData && modelData.isSink && !modelData.isStream && modelData.name !== "Dummy-Driver"
visible: isOutput
height: isOutput ? 40 : 0
-
+
Squircle {
anchors.fill: parent
fillColor: mouseArea.containsMouse ? Theme.surfaceLighter : Theme.transparent
cornerRadius: 6
-
+
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 12
-
+
IconCircle {
size: 24
source: root.getDeviceIcon(modelData)
active: root.sink && root.sink.id === modelData.id
}
-
+
Text {
Layout.fillWidth: true
text: modelData.description || modelData.nickname || modelData.name
@@ -188,13 +173,13 @@ Item {
elide: Text.ElideRight
}
}
-
+
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
- Quickshell.execDetached(["wpctl", "set-default", modelData.id.toString()])
+ Quickshell.execDetached(["wpctl", "set-default", modelData.id.toString()]);
}
}
}
diff --git a/modules/system/quickshell/VolumeOSD.qml b/modules/system/quickshell/VolumeOSD.qml
index 8efe305..67a1d62 100644
--- a/modules/system/quickshell/VolumeOSD.qml
+++ b/modules/system/quickshell/VolumeOSD.qml
@@ -4,7 +4,6 @@ import Quickshell
import Quickshell.Services.Pipewire
import Quickshell.Wayland
-// Volume OSD that appears on volume change, styled to match the system menus.
Scope {
id: root
@@ -18,14 +17,14 @@ Scope {
Connections {
target: root.sink && root.sink.audio ? root.sink.audio : null
ignoreUnknownSignals: true
-
+
function onVolumeChanged() {
- root.visible = true
- hideTimer.restart()
+ root.visible = true;
+ hideTimer.restart();
}
function onMutedChanged() {
- root.visible = true
- hideTimer.restart()
+ root.visible = true;
+ hideTimer.restart();
}
}
@@ -37,19 +36,18 @@ Scope {
PanelWindow {
visible: root.visible
-
- // Compositor centers horizontally if only bottom anchor is set
+
anchors.bottom: true
margins.bottom: 100
exclusiveZone: 0
implicitWidth: 240
implicitHeight: 64
-
+
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
color: Theme.transparent
- mask: Region {} // Pass-through clicks
+ mask: Region {}
Squircle {
id: card
@@ -69,12 +67,16 @@ Scope {
IconCircle {
size: 32
source: {
- if (root.sink?.audio?.muted) return "audio-volume-muted"
- const vol = root.sink?.audio?.volume ?? 0
- if (vol <= 0) return "audio-volume-low"
- if (vol <= 0.33) return "audio-volume-low"
- if (vol <= 0.66) return "audio-volume-medium"
- return "audio-volume-high"
+ if (root.sink?.audio?.muted)
+ return "audio-volume-muted";
+ const vol = root.sink?.audio?.volume ?? 0;
+ if (vol <= 0)
+ return "audio-volume-low";
+ if (vol <= 0.33)
+ return "audio-volume-low";
+ if (vol <= 0.66)
+ return "audio-volume-medium";
+ return "audio-volume-high";
}
active: !(root.sink?.audio?.muted ?? true)
}
@@ -82,7 +84,7 @@ Scope {
PillSlider {
Layout.fillWidth: true
value: root.sink?.audio?.volume ?? 0
- enabled: false // OSD is for display only
+ enabled: false
}
}
}
diff --git a/modules/system/quickshell/Wifi.qml b/modules/system/quickshell/Wifi.qml
index 0ce57a0..9960575 100644
--- a/modules/system/quickshell/Wifi.qml
+++ b/modules/system/quickshell/Wifi.qml
@@ -11,72 +11,85 @@ Item {
id: internal
readonly property var device: {
- if (!Networking.devices) return null
+ if (!Networking.devices)
+ return null;
for (const d of Networking.devices.values || []) {
- if (d && d.scannerEnabled !== undefined) return d
+ if (d && d.scannerEnabled !== undefined)
+ return d;
}
- return null
+ return null;
}
readonly property var allNetworks: device?.networks ? device.networks.values : []
+ readonly property bool hasKnown: hasKnownNetwork()
+ readonly property bool hasOther: hasOtherNetwork()
+ readonly property var activeNetwork: findActiveNetwork()
- property bool hasKnown: false
- property bool hasOther: false
- property var activeNetwork: null
+ function hasKnownNetwork() {
+ for (const n of internal.allNetworks) {
+ if (n?.known)
+ return true;
+ }
+ return false;
+ }
- function updateState() {
- let known = false
- let other = false
- let active = null
-
+ function hasOtherNetwork() {
for (const n of internal.allNetworks) {
- if (n?.connected) active = n
- if (n?.known) known = true
- if (n && !n.known && n.name) other = true
+ if (n && !n.known && n.name)
+ return true;
}
-
- internal.hasKnown = known
- internal.hasOther = other
- internal.activeNetwork = active
+ return false;
}
- Connections {
- target: internal.device ? internal.device.networks : null
- function onValuesChanged() { internal.updateState() }
+ function findActiveNetwork() {
+ for (const n of internal.allNetworks) {
+ if (n?.connected)
+ return n;
+ }
+ return null;
}
}
function _getWifiIcon(strength) {
- if (!Networking.wifiEnabled) return "network-wireless-offline-symbolic"
- if (!internal.activeNetwork && !internal.device?.enabled) return "network-wireless-offline-symbolic"
-
- const s = strength ?? 0
- if (s >= 0.75) return "network-wireless-signal-excellent-symbolic"
- if (s >= 0.50) return "network-wireless-signal-good-symbolic"
- if (s >= 0.25) return "network-wireless-signal-ok-symbolic"
- if (s > 0) return "network-wireless-signal-weak-symbolic"
- return "network-wireless-signal-none-symbolic"
+ if (!Networking.wifiEnabled)
+ return "network-wireless-offline-symbolic";
+ if (!internal.activeNetwork && !internal.device?.enabled)
+ return "network-wireless-offline-symbolic";
+
+ const s = strength ?? 0;
+ if (s >= 0.75)
+ return "network-wireless-signal-excellent-symbolic";
+ if (s >= 0.50)
+ return "network-wireless-signal-good-symbolic";
+ if (s >= 0.25)
+ return "network-wireless-signal-ok-symbolic";
+ if (s > 0)
+ return "network-wireless-signal-weak-symbolic";
+ return "network-wireless-signal-none-symbolic";
}
function _onNetworkClick(net) {
- if (!net) return
- if (net.connected) { net.disconnect(); return }
- if (net.stateChanging) return
+ if (!net)
+ return;
+ if (net.connected) {
+ net.disconnect();
+ return;
+ }
+ if (net.stateChanging)
+ return;
if (net.known) {
- net.connect()
+ net.connect();
} else if ((net.security ?? 0) === 0) {
- net.connect()
+ net.connect();
} else {
- pskPrompt.network = net
- pskPrompt.open(net.name ?? "")
+ pskPrompt.network = net;
+ pskPrompt.open(net.name ?? "");
}
}
width: childrenRect.width
height: parent.height
- Component.onCompleted: internal.updateState()
-
Row {
anchors {
verticalCenter: parent.verticalCenter
@@ -98,44 +111,26 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
- GlobalState.toggle("Wifi")
+ GlobalState.toggle("Wifi");
} else if (mouse.button === Qt.RightButton) {
- Networking.wifiEnabled = !Networking.wifiEnabled
+ Networking.wifiEnabled = !Networking.wifiEnabled;
}
}
}
- PopupWindow {
- id: popup
- visible: GlobalState.activePopup === "Wifi"
- grabFocus: true
- implicitWidth: card.width
- implicitHeight: card.height
-
- anchor {
- window: barWindow
- item: root
- edges: Edges.Bottom
- gravity: Edges.Bottom
- margins.top: Theme.popupGap
- }
-
- color: Theme.transparent
-
- onVisibleChanged: {
- if (visible) {
- anchor.updateAnchor()
- if (internal.device) internal.device.scannerEnabled = true
- } else if (internal.device?.scannerEnabled) {
- internal.device.scannerEnabled = false
- }
- }
+ AnchoredPopup {
+ popupName: "Wifi"
+ anchorWindow: barWindow
+ anchorItem: root
+ onOpened: if (internal.device)
+ internal.device.scannerEnabled = true
+ onClosed: if (internal.device?.scannerEnabled)
+ internal.device.scannerEnabled = false
PopupCard {
id: card
margins: 16
- // Wi-Fi { Toggle }
RowLayout {
Layout.fillWidth: true
Layout.topMargin: 4
@@ -154,7 +149,9 @@ Item {
}
}
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
Toggle {
checked: Networking.wifiEnabled
@@ -171,7 +168,6 @@ Item {
Layout.bottomMargin: 4
}
- // Known Network Header
Text {
visible: internal.hasKnown
text: "Known Network"
@@ -186,7 +182,6 @@ Item {
Layout.bottomMargin: 2
}
- // Known networks
ColumnLayout {
Layout.fillWidth: true
spacing: 2
@@ -205,14 +200,6 @@ Item {
fillColor: knownArea.containsMouse ? Theme.surface : Theme.transparent
cornerRadius: 6
- Connections {
- target: knownItem.modelData
- function onKnownChanged() { internal.updateState() }
- function onConnectedChanged() { internal.updateState() }
- function onNameChanged() { internal.updateState() }
- function onSignalStrengthChanged() { internal.updateState() }
- }
-
MouseArea {
id: knownArea
anchors.fill: parent
@@ -265,7 +252,6 @@ Item {
Layout.bottomMargin: 4
}
- // Other Networks Header
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: 8
@@ -307,14 +293,13 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (internal.device) {
- internal.device.scannerEnabled = !internal.device.scannerEnabled
+ internal.device.scannerEnabled = !internal.device.scannerEnabled;
}
}
}
}
}
- // Other networks list
ColumnLayout {
Layout.fillWidth: true
spacing: 2
@@ -332,13 +317,6 @@ Item {
fillColor: otherArea.containsMouse ? Theme.surface : Theme.transparent
cornerRadius: 6
- Connections {
- target: otherItem.modelData
- function onKnownChanged() { internal.updateState() }
- function onNameChanged() { internal.updateState() }
- function onSignalStrengthChanged() { internal.updateState() }
- }
-
MouseArea {
id: otherArea
anchors.fill: parent
@@ -390,8 +368,9 @@ Item {
WifiPasswordPrompt {
id: pskPrompt
property var network: null
- onSubmitted: (text, remember) => {
- if (network) network.connectWithPsk(text)
+ onSubmitted: text => {
+ if (network)
+ network.connectWithPsk(text);
}
}
}
diff --git a/modules/system/quickshell/WifiPasswordPrompt.qml b/modules/system/quickshell/WifiPasswordPrompt.qml
index c77c351..7a5eafc 100644
--- a/modules/system/quickshell/WifiPasswordPrompt.qml
+++ b/modules/system/quickshell/WifiPasswordPrompt.qml
@@ -3,42 +3,38 @@ import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
-import Quickshell.Hyprland
+import Quickshell.Hyprland
PanelWindow {
id: root
- // Properties
property string networkName: ""
property string title: "The Wi-Fi network \"" + networkName + "\" requires a password."
property string submitLabel: "Join"
property string iconSource: "network-wireless-symbolic"
-
- signal submitted(string text, bool remember)
- signal cancelled()
- // Methods
+ signal submitted(string text)
+ signal cancelled
+
function open(name) {
- networkName = name
- passwordInput.text = ""
- showPasswordCheck.checked = false
- rememberNetworkCheck.checked = true
- visible = true
- Qt.callLater(() => passwordInput.forceActiveFocus())
+ networkName = name;
+ passwordInput.text = "";
+ showPasswordCheck.checked = false;
+ visible = true;
+ Qt.callLater(() => passwordInput.forceActiveFocus());
}
- // Window Configuration
visible: false
color: Theme.transparent
exclusiveZone: 0
-
+
anchors {
top: true
bottom: true
left: true
right: true
}
-
+
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
@@ -48,12 +44,11 @@ PanelWindow {
active: root.visible
}
- // UI Layout
Rectangle {
anchors.fill: parent
color: Theme.scrim
- MouseArea {
- anchors.fill: parent
+ MouseArea {
+ anchors.fill: parent
acceptedButtons: Qt.AllButtons
hoverEnabled: true
onWheel: wheel => wheel.accepted = true
@@ -71,8 +66,8 @@ PanelWindow {
cornerRadius: 12
Keys.onEscapePressed: {
- root.visible = false
- root.cancelled()
+ root.visible = false;
+ root.cancelled();
}
RowLayout {
@@ -85,7 +80,7 @@ PanelWindow {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 64
Layout.preferredHeight: 64
-
+
Image {
anchors.fill: parent
source: Quickshell.iconPath(root.iconSource)
@@ -133,7 +128,7 @@ PanelWindow {
font.pixelSize: 12
color: Theme.text
echoMode: showPasswordCheck.checked ? TextInput.Normal : TextInput.Password
-
+
background: Rectangle {
color: Theme.surface
radius: 4
@@ -142,8 +137,8 @@ PanelWindow {
}
onAccepted: {
- root.submitted(passwordInput.text, rememberNetworkCheck.checked)
- root.visible = false
+ root.submitted(passwordInput.text);
+ root.visible = false;
}
}
@@ -152,27 +147,24 @@ PanelWindow {
text: "Show password"
Layout.fillWidth: true
}
-
- CustomCheckBox {
- id: rememberNetworkCheck
- text: "Remember this network"
- checked: true
- Layout.fillWidth: true
- }
}
}
- Item { Layout.preferredHeight: 4 }
+ Item {
+ Layout.preferredHeight: 4
+ }
RowLayout {
Layout.fillWidth: true
spacing: 8
- Item { Layout.fillWidth: true }
+ Item {
+ Layout.fillWidth: true
+ }
Button {
text: "Cancel"
-
+
contentItem: Text {
text: parent.text
color: Theme.text
@@ -180,7 +172,7 @@ PanelWindow {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
-
+
background: Rectangle {
implicitWidth: 80
implicitHeight: 28
@@ -189,16 +181,16 @@ PanelWindow {
border.color: Theme.border
border.width: 1
}
-
+
onClicked: {
- root.visible = false
- root.cancelled()
+ root.visible = false;
+ root.cancelled();
}
}
Button {
text: root.submitLabel
-
+
contentItem: Text {
text: parent.text
color: Theme.text
@@ -206,17 +198,17 @@ PanelWindow {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
-
+
background: Rectangle {
implicitWidth: 80
implicitHeight: 28
color: parent.hovered ? Theme.accentHover : Theme.accent
radius: 6
}
-
+
onClicked: {
- root.submitted(passwordInput.text, rememberNetworkCheck.checked)
- root.visible = false
+ root.submitted(passwordInput.text);
+ root.visible = false;
}
}
}
diff --git a/modules/system/quickshell/Workspaces.qml b/modules/system/quickshell/Workspaces.qml
index b63826d..44c0743 100644
--- a/modules/system/quickshell/Workspaces.qml
+++ b/modules/system/quickshell/Workspaces.qml
@@ -8,17 +8,19 @@ Item {
implicitWidth: row.implicitWidth
implicitHeight: 32
- ListModel { id: workspaceModel }
+ ListModel {
+ id: workspaceModel
+ }
function updateWorkspaces(json) {
try {
- const ws = JSON.parse(json)
- workspaceModel.clear()
+ const ws = JSON.parse(json);
+ workspaceModel.clear();
ws.forEach(w => workspaceModel.append({
- wsNum: w.num,
- wsName: w.name,
- wsFocused: w.focused
- }))
+ wsNum: w.num,
+ wsName: w.name,
+ wsFocused: w.focused
+ }));
} catch (_) {}
}
@@ -29,7 +31,8 @@ Item {
stdout: SplitParser {
onRead: _ => {
- if (!refresher.running) refresher.running = true
+ if (!refresher.running)
+ refresher.running = true;
}
}
}
@@ -46,8 +49,8 @@ Item {
onRunningChanged: {
if (!running && buf !== "") {
- root.updateWorkspaces(buf)
- buf = ""
+ root.updateWorkspaces(buf);
+ buf = "";
}
}
@@ -89,8 +92,8 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: {
- switcher.running = false
- switcher.running = true
+ switcher.running = false;
+ switcher.running = true;
}
}
}
diff --git a/modules/system/quickshell/qmldir b/modules/system/quickshell/qmldir
index 1c0341b..7f82d41 100644
--- a/modules/system/quickshell/qmldir
+++ b/modules/system/quickshell/qmldir
@@ -1,7 +1,9 @@
Bar Bar.qml
+AnchoredPopup AnchoredPopup.qml
Background Background.qml
Bluetooth Bluetooth.qml
BrightnessService BrightnessService.qml
+Clock Clock.qml
ControlCenter ControlCenter.qml
singleton GlobalState GlobalState.qml
IconCircle IconCircle.qml
@@ -25,6 +27,8 @@ WifiPasswordPrompt WifiPasswordPrompt.qml
Workspaces Workspaces.qml
CustomCheckBox CustomCheckBox.qml
MediaCard MediaCard.qml
+MicInput MicInput.qml
+ScreenRecordIndicator ScreenRecordIndicator.qml
ConnectivityBox ConnectivityBox.qml
ControlTile ControlTile.qml
SliderBox SliderBox.qml
diff --git a/modules/system/quickshell/shell.qml b/modules/system/quickshell/shell.qml
index a61edad..bee997c 100644
--- a/modules/system/quickshell/shell.qml
+++ b/modules/system/quickshell/shell.qml
@@ -6,9 +6,9 @@ import Quickshell.Wayland
ShellRoot {
Component.onCompleted: {
- Qt.application.font.family = "Inter"
- Qt.application.font.hintingPreference = Font.PreferNoHinting
- Qt.application.font.styleStrategy = Font.NoSubpixelAntialias
+ Qt.application.font.family = "Inter";
+ Qt.application.font.hintingPreference = Font.PreferNoHinting;
+ Qt.application.font.styleStrategy = Font.NoSubpixelAntialias;
}
Variants {
@@ -44,7 +44,7 @@ ShellRoot {
WlSessionLock {
id: sessionLock
-
+
WlSessionLockSurface {
LockSurface {
anchors.fill: parent
@@ -56,13 +56,12 @@ ShellRoot {
IpcHandler {
target: "bar"
function toggleLauncher() {
- GlobalState.toggle("Launcher")
+ GlobalState.toggle("Launcher");
}
function lock() {
lockContext.reset();
- sessionLock.locked = true
+ sessionLock.locked = true;
}
}
}
-
diff --git a/modules/system/quickshell/squircle.frag b/modules/system/quickshell/squircle.frag
index df2477f..058468e 100644
--- a/modules/system/quickshell/squircle.frag
+++ b/modules/system/quickshell/squircle.frag
@@ -27,13 +27,11 @@ void main() {
vec2 halfSize = vec2(ubuf.width, ubuf.height) * 0.5;
vec2 p = (qt_TexCoord0 * vec2(ubuf.width, ubuf.height)) - halfSize;
- // Applied the scaling factor mentioned in the original comment
float r = ubuf.cornerRadius * 1.5286;
float dist = squircleSDF(p, halfSize, r);
float fwidth_dist = fwidth(dist);
- // Corrected edge parameter order to avoid undefined behavior
float alpha = 1.0 - smoothstep(-fwidth_dist, fwidth_dist, dist);
if (ubuf.strokeWidth > 0.0) {