summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--assets/pam.d/fprint3
-rw-r--r--assets/pam.d/passwd6
-rw-r--r--components/controls/SplitButtonRow.qml62
-rw-r--r--components/controls/ToggleButton.qml2
-rw-r--r--components/filedialog/FolderContents.qml228
-rw-r--r--components/filedialog/HeaderBar.qml139
-rw-r--r--config/BarConfig.qml2
-rw-r--r--config/Config.qml29
-rw-r--r--config/GeneralConfig.qml22
-rw-r--r--config/LauncherConfig.qml6
-rw-r--r--config/LockConfig.qml14
-rw-r--r--config/ServiceConfig.qml1
-rw-r--r--config/UserPaths.qml1
-rw-r--r--config/UtilitiesConfig.qml1
-rw-r--r--flake.lock14
-rw-r--r--modules/IdleMonitors.qml51
-rw-r--r--modules/background/Background.qml1
-rw-r--r--modules/bar/components/Clock.qml1
-rw-r--r--modules/bar/components/StatusIcons.qml3
-rw-r--r--modules/bar/components/workspaces/OccupiedBg.qml11
-rw-r--r--modules/bar/popouts/Battery.qml1
-rw-r--r--modules/bar/popouts/Bluetooth.qml1
-rw-r--r--modules/bar/popouts/Content.qml7
-rw-r--r--modules/bar/popouts/KbLayout.qml28
-rw-r--r--modules/bar/popouts/Network.qml20
-rw-r--r--modules/bar/popouts/TrayMenu.qml4
-rw-r--r--modules/bar/popouts/Wrapper.qml6
-rw-r--r--modules/bar/popouts/kblayout/KbLayout.qml158
-rw-r--r--modules/bar/popouts/kblayout/KbLayoutModel.qml200
-rw-r--r--modules/controlcenter/ControlCenter.qml1
-rw-r--r--modules/controlcenter/NavRail.qml1
-rw-r--r--modules/controlcenter/Panes.qml3
-rw-r--r--modules/controlcenter/Session.qml4
-rw-r--r--modules/controlcenter/audio/AudioPane.qml145
-rw-r--r--modules/controlcenter/components/SplitPaneLayout.qml2
-rw-r--r--modules/controlcenter/components/SplitPaneWithDetails.qml1
-rw-r--r--modules/controlcenter/network/NetworkSettings.qml2
-rw-r--r--modules/controlcenter/network/NetworkingPane.qml30
-rw-r--r--modules/dashboard/Content.qml10
-rw-r--r--modules/dashboard/dash/DateTime.qml9
-rw-r--r--modules/drawers/Drawers.qml2
-rw-r--r--modules/launcher/services/Apps.qml6
-rw-r--r--modules/lock/Center.qml417
-rw-r--r--modules/lock/Content.qml27
-rw-r--r--modules/lock/InputField.qml149
-rw-r--r--modules/lock/Lock.qml55
-rw-r--r--modules/lock/LockSurface.qml231
-rw-r--r--modules/lock/Pam.qml193
-rw-r--r--modules/notifications/Notification.qml4
-rw-r--r--modules/osd/Content.qml1
-rw-r--r--modules/sidebar/NotifDock.qml1
-rw-r--r--modules/sidebar/NotifGroup.qml2
-rw-r--r--modules/utilities/Content.qml2
-rw-r--r--modules/utilities/cards/IdleInhibit.qml125
-rw-r--r--modules/utilities/cards/Toggles.qml2
-rw-r--r--modules/windowinfo/Buttons.qml1
-rw-r--r--modules/windowinfo/Preview.qml1
-rw-r--r--nix/default.nix2
-rw-r--r--plugin/src/Caelestia/Internal/CMakeLists.txt1
-rw-r--r--plugin/src/Caelestia/Internal/logindmanager.cpp65
-rw-r--r--plugin/src/Caelestia/Internal/logindmanager.hpp27
-rw-r--r--plugin/src/Caelestia/cutils.cpp2
-rw-r--r--services/Audio.qml36
-rw-r--r--services/IdleInhibitor.qml56
-rw-r--r--services/Notifs.qml22
-rw-r--r--services/Time.qml8
-rw-r--r--services/VPN.qml7
-rw-r--r--shell.qml7
-rw-r--r--utils/Paths.qml3
-rw-r--r--utils/SysInfo.qml18
70 files changed, 1087 insertions, 1616 deletions
diff --git a/assets/pam.d/fprint b/assets/pam.d/fprint
deleted file mode 100644
index d4814e9..0000000
--- a/assets/pam.d/fprint
+++ /dev/null
@@ -1,3 +0,0 @@
-#%PAM-1.0
-
-auth required pam_fprintd.so max-tries=1
diff --git a/assets/pam.d/passwd b/assets/pam.d/passwd
deleted file mode 100644
index 4b14064..0000000
--- a/assets/pam.d/passwd
+++ /dev/null
@@ -1,6 +0,0 @@
-#%PAM-1.0
-
-auth required pam_faillock.so preauth
-auth [success=1 default=bad] pam_unix.so nullok
-auth [default=die] pam_faillock.so authfail
-auth required pam_faillock.so authsucc
diff --git a/components/controls/SplitButtonRow.qml b/components/controls/SplitButtonRow.qml
new file mode 100644
index 0000000..4ecc8bf
--- /dev/null
+++ b/components/controls/SplitButtonRow.qml
@@ -0,0 +1,62 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.components
+import qs.components.effects
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Layouts
+
+StyledRect {
+ id: root
+
+ required property string label
+ property int expandedZ: 100
+ property bool enabled: true
+
+ property alias menuItems: splitButton.menuItems
+ property alias active: splitButton.active
+ property alias expanded: splitButton.expanded
+ property alias type: splitButton.type
+
+ signal selected(item: MenuItem)
+
+ Layout.fillWidth: true
+ implicitHeight: row.implicitHeight + Appearance.padding.large * 2
+ radius: Appearance.rounding.normal
+ color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
+
+ clip: false
+ z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
+ opacity: enabled ? 1.0 : 0.5
+
+ RowLayout {
+ id: row
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.large
+ spacing: Appearance.spacing.normal
+
+ StyledText {
+ Layout.fillWidth: true
+ text: root.label
+ color: root.enabled ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
+ }
+
+ SplitButton {
+ id: splitButton
+ enabled: root.enabled
+ type: SplitButton.Filled
+
+ menu.z: 1
+
+ stateLayer.onClicked: {
+ splitButton.expanded = !splitButton.expanded
+ }
+
+ menu.onItemSelected: (item) => {
+ root.selected(item);
+ }
+ }
+ }
+}
diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml
index b2c2afe..8bdcb04 100644
--- a/components/controls/ToggleButton.qml
+++ b/components/controls/ToggleButton.qml
@@ -74,7 +74,6 @@ StyledRect {
}
Loader {
- asynchronous: true
active: !!root.label
visible: active
@@ -103,7 +102,6 @@ StyledRect {
Loader {
id: tooltipLoader
active: root.tooltip !== ""
- asynchronous: true
z: 10000
width: 0
height: 0
diff --git a/components/filedialog/FolderContents.qml b/components/filedialog/FolderContents.qml
new file mode 100644
index 0000000..e16c7a1
--- /dev/null
+++ b/components/filedialog/FolderContents.qml
@@ -0,0 +1,228 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import "../controls"
+import "../images"
+import qs.services
+import qs.config
+import qs.utils
+import Caelestia.Models
+import Quickshell
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Effects
+
+Item {
+ id: root
+
+ required property var dialog
+ property alias currentItem: view.currentItem
+
+ StyledRect {
+ anchors.fill: parent
+ color: Colours.tPalette.m3surfaceContainer
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ maskSource: mask
+ maskEnabled: true
+ maskInverted: true
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1
+ }
+ }
+
+ Item {
+ id: mask
+
+ anchors.fill: parent
+ layer.enabled: true
+ visible: false
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small
+ radius: Appearance.rounding.small
+ }
+ }
+
+ Loader {
+ anchors.centerIn: parent
+
+ opacity: view.count === 0 ? 1 : 0
+ active: opacity > 0
+
+ sourceComponent: ColumnLayout {
+ MaterialIcon {
+ Layout.alignment: Qt.AlignHCenter
+ text: "scan_delete"
+ color: Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.extraLarge * 2
+ font.weight: 500
+ }
+
+ StyledText {
+ text: qsTr("This folder is empty")
+ color: Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.large
+ font.weight: 500
+ }
+ }
+
+ Behavior on opacity {
+ Anim {}
+ }
+ }
+
+ GridView {
+ id: view
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small + Appearance.padding.normal
+
+ cellWidth: Sizes.itemWidth + Appearance.spacing.small
+ cellHeight: Sizes.itemWidth + Appearance.spacing.small * 2 + Appearance.padding.normal * 2 + 1
+
+ clip: true
+ focus: true
+ currentIndex: -1
+ Keys.onEscapePressed: currentIndex = -1
+
+ Keys.onReturnPressed: {
+ if (root.dialog.selectionValid)
+ root.dialog.accepted(currentItem.modelData.path);
+ }
+ Keys.onEnterPressed: {
+ if (root.dialog.selectionValid)
+ root.dialog.accepted(currentItem.modelData.path);
+ }
+
+ StyledScrollBar.vertical: StyledScrollBar {
+ flickable: view
+ }
+
+ model: FileSystemModel {
+ path: {
+ if (root.dialog.cwd[0] === "Home")
+ return `${Paths.home}/${root.dialog.cwd.slice(1).join("/")}`;
+ else
+ return root.dialog.cwd.join("/");
+ }
+ onPathChanged: view.currentIndex = -1
+ }
+
+ delegate: StyledRect {
+ id: item
+
+ required property int index
+ required property FileSystemEntry modelData
+
+ readonly property real nonAnimHeight: icon.implicitHeight + name.anchors.topMargin + name.implicitHeight + Appearance.padding.normal * 2
+
+ implicitWidth: Sizes.itemWidth
+ implicitHeight: nonAnimHeight
+
+ radius: Appearance.rounding.normal
+ color: Qt.alpha(Colours.tPalette.m3surfaceContainerHighest, GridView.isCurrentItem ? Colours.tPalette.m3surfaceContainerHighest.a : 0)
+ z: GridView.isCurrentItem || implicitHeight !== nonAnimHeight ? 1 : 0
+ clip: true
+
+ StateLayer {
+ onDoubleClicked: {
+ if (item.modelData.isDir)
+ root.dialog.cwd.push(item.modelData.name);
+ else if (root.dialog.selectionValid)
+ root.dialog.accepted(item.modelData.path);
+ }
+
+ function onClicked(): void {
+ view.currentIndex = item.index;
+ }
+ }
+
+ CachingIconImage {
+ id: icon
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: parent.top
+ anchors.topMargin: Appearance.padding.normal
+
+ implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
+
+ Component.onCompleted: {
+ const file = item.modelData;
+ if (file.isImage)
+ source = Qt.resolvedUrl(file.path);
+ else if (!file.isDir)
+ source = Quickshell.iconPath(file.mimeType.replace("/", "-"), "application-x-zerosize");
+ else if (root.dialog.cwd.length === 1 && ["Desktop", "Documents", "Downloads", "Music", "Pictures", "Public", "Templates", "Videos"].includes(file.name))
+ source = Quickshell.iconPath(`folder-${file.name.toLowerCase()}`);
+ else
+ source = Quickshell.iconPath("inode-directory");
+ }
+ }
+
+ StyledText {
+ id: name
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: icon.bottom
+ anchors.topMargin: Appearance.spacing.small
+ anchors.margins: Appearance.padding.normal
+
+ horizontalAlignment: Text.AlignHCenter
+ elide: item.GridView.isCurrentItem ? Text.ElideNone : Text.ElideRight
+ wrapMode: item.GridView.isCurrentItem ? Text.WrapAtWordBoundaryOrAnywhere : Text.NoWrap
+
+ Component.onCompleted: text = item.modelData.name
+ }
+
+ Behavior on implicitHeight {
+ Anim {}
+ }
+ }
+
+ add: Transition {
+ Anim {
+ properties: "opacity,scale"
+ from: 0
+ to: 1
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ }
+
+ remove: Transition {
+ Anim {
+ property: "opacity"
+ to: 0
+ }
+ Anim {
+ property: "scale"
+ to: 0.5
+ }
+ }
+
+ displaced: Transition {
+ Anim {
+ properties: "opacity,scale"
+ to: 1
+ easing.bezierCurve: Appearance.anim.curves.standardDecel
+ }
+ Anim {
+ properties: "x,y"
+ duration: Appearance.anim.durations.expressiveDefaultSpatial
+ easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
+ }
+ }
+ }
+
+ CurrentItem {
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: Appearance.padding.small
+
+ currentItem: view.currentItem
+ }
+}
diff --git a/components/filedialog/HeaderBar.qml b/components/filedialog/HeaderBar.qml
new file mode 100644
index 0000000..c9a3feb
--- /dev/null
+++ b/components/filedialog/HeaderBar.qml
@@ -0,0 +1,139 @@
+pragma ComponentBehavior: Bound
+
+import ".."
+import qs.services
+import qs.config
+import QtQuick
+import QtQuick.Layouts
+
+StyledRect {
+ id: root
+
+ required property var dialog
+
+ implicitWidth: inner.implicitWidth + Appearance.padding.normal * 2
+ implicitHeight: inner.implicitHeight + Appearance.padding.normal * 2
+
+ color: Colours.tPalette.m3surfaceContainer
+
+ RowLayout {
+ id: inner
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.normal
+ spacing: Appearance.spacing.small
+
+ Item {
+ implicitWidth: implicitHeight
+ implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
+
+ StateLayer {
+ radius: Appearance.rounding.small
+ disabled: root.dialog.cwd.length === 1
+
+ function onClicked(): void {
+ root.dialog.cwd.pop();
+ }
+ }
+
+ MaterialIcon {
+ id: upIcon
+
+ anchors.centerIn: parent
+ text: "drive_folder_upload"
+ color: root.dialog.cwd.length === 1 ? Colours.palette.m3outline : Colours.palette.m3onSurface
+ grade: 200
+ }
+ }
+
+ StyledRect {
+ Layout.fillWidth: true
+
+ radius: Appearance.rounding.small
+ color: Colours.tPalette.m3surfaceContainerHigh
+
+ implicitHeight: pathComponents.implicitHeight + pathComponents.anchors.margins * 2
+
+ RowLayout {
+ id: pathComponents
+
+ anchors.fill: parent
+ anchors.margins: Appearance.padding.small / 2
+ anchors.leftMargin: 0
+
+ spacing: Appearance.spacing.small
+
+ Repeater {
+ model: root.dialog.cwd
+
+ RowLayout {
+ id: folder
+
+ required property string modelData
+ required property int index
+
+ spacing: 0
+
+ Loader {
+ Layout.rightMargin: Appearance.spacing.small
+ active: folder.index > 0
+ sourceComponent: StyledText {
+ text: "/"
+ color: Colours.palette.m3onSurfaceVariant
+ font.bold: true
+ }
+ }
+
+ Item {
+ implicitWidth: homeIcon.implicitWidth + (homeIcon.active ? Appearance.padding.small : 0) + folderName.implicitWidth + Appearance.padding.normal * 2
+ implicitHeight: folderName.implicitHeight + Appearance.padding.small * 2
+
+ Loader {
+ anchors.fill: parent
+ active: folder.index < root.dialog.cwd.length - 1
+ sourceComponent: StateLayer {
+ radius: Appearance.rounding.small
+
+ function onClicked(): void {
+ root.dialog.cwd = root.dialog.cwd.slice(0, folder.index + 1);
+ }
+ }
+ }
+
+ Loader {
+ id: homeIcon
+
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: Appearance.padding.normal
+
+ active: folder.index === 0 && folder.modelData === "Home"
+ sourceComponent: MaterialIcon {
+ text: "home"
+ color: root.dialog.cwd.length === 1 ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
+ fill: 1
+ }
+ }
+
+ StyledText {
+ id: folderName
+
+ anchors.left: homeIcon.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.leftMargin: homeIcon.active ? Appearance.padding.small : 0
+
+ text: folder.modelData
+ color: folder.index < root.dialog.cwd.length - 1 ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3onSurface
+ font.bold: true
+ }
+ }
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+ }
+ }
+ }
+}
diff --git a/config/BarConfig.qml b/config/BarConfig.qml
index 00df1ea..c9c9f1f 100644
--- a/config/BarConfig.qml
+++ b/config/BarConfig.qml
@@ -85,6 +85,7 @@ JsonObject {
property bool showMicrophone: false
property bool showKbLayout: false
property bool showNetwork: true
+ property bool showWifi: true
property bool showBluetooth: true
property bool showBattery: true
property bool showLockStatus: true
@@ -100,5 +101,6 @@ JsonObject {
property int trayMenuWidth: 300
property int batteryWidth: 250
property int networkWidth: 320
+ property int kbLayoutWidth: 320
}
}
diff --git a/config/Config.qml b/config/Config.qml
index d9fb892..9f66c73 100644
--- a/config/Config.qml
+++ b/config/Config.qml
@@ -21,7 +21,6 @@ Singleton {
property alias osd: adapter.osd
property alias session: adapter.session
property alias winfo: adapter.winfo
- property alias lock: adapter.lock
property alias utilities: adapter.utilities
property alias sidebar: adapter.sidebar
property alias services: adapter.services
@@ -42,7 +41,6 @@ Singleton {
osd: serializeOsd(),
session: serializeSession(),
winfo: serializeWinfo(),
- lock: serializeLock(),
utilities: serializeUtilities(),
sidebar: serializeSidebar(),
services: serializeServices(),
@@ -77,17 +75,13 @@ Singleton {
function serializeGeneral(): var {
return {
+ logo: general.logo,
apps: {
terminal: general.apps.terminal,
audio: general.apps.audio,
playback: general.apps.playback,
explorer: general.apps.explorer
},
- idle: {
- lockBeforeSleep: general.idle.lockBeforeSleep,
- inhibitWhenAudio: general.idle.inhibitWhenAudio,
- timeouts: general.idle.timeouts
- },
battery: {
warnLevels: general.battery.warnLevels,
criticalLevel: general.battery.criticalLevel
@@ -130,6 +124,7 @@ Singleton {
showMicrophone: bar.status.showMicrophone,
showKbLayout: bar.status.showKbLayout,
showNetwork: bar.status.showNetwork,
+ showWifi: bar.status.showWifi,
showBluetooth: bar.status.showBluetooth,
showBattery: bar.status.showBattery,
showLockStatus: bar.status.showLockStatus
@@ -199,9 +194,7 @@ Singleton {
dragThreshold: launcher.dragThreshold,
vimKeybinds: launcher.vimKeybinds,
hiddenApps: launcher.hiddenApps,
- useFuzzy: {
- apps: launcher.useFuzzy.apps,
- },
+ useFuzzy: launcher.useFuzzy,
sizes: {
itemWidth: launcher.sizes.itemWidth,
itemHeight: launcher.sizes.itemHeight,
@@ -266,19 +259,6 @@ Singleton {
};
}
- function serializeLock(): var {
- return {
- recolourLogo: lock.recolourLogo,
- enableFprint: lock.enableFprint,
- maxFprintTries: lock.maxFprintTries,
- sizes: {
- heightMult: lock.sizes.heightMult,
- ratio: lock.sizes.ratio,
- centerWidth: lock.sizes.centerWidth
- }
- };
- }
-
function serializeUtilities(): var {
return {
enabled: utilities.enabled,
@@ -325,7 +305,6 @@ Singleton {
visualiserBars: services.visualiserBars,
audioIncrement: services.audioIncrement,
maxVolume: services.maxVolume,
- smartScheme: services.smartScheme,
defaultPlayer: services.defaultPlayer,
playerAliases: services.playerAliases
};
@@ -334,7 +313,6 @@ Singleton {
function serializePaths(): var {
return {
wallpaper: paths.wallpaper,
- face: paths.face,
sessionGif: paths.sessionGif,
mediaGif: paths.mediaGif
};
@@ -382,7 +360,6 @@ Singleton {
property OsdConfig osd: OsdConfig {}
property SessionConfig session: SessionConfig {}
property WInfoConfig winfo: WInfoConfig {}
- property LockConfig lock: LockConfig {}
property UtilitiesConfig utilities: UtilitiesConfig {}
property SidebarConfig sidebar: SidebarConfig {}
property ServiceConfig services: ServiceConfig {}
diff --git a/config/GeneralConfig.qml b/config/GeneralConfig.qml
index eecca01..2cf99e3 100644
--- a/config/GeneralConfig.qml
+++ b/config/GeneralConfig.qml
@@ -1,8 +1,8 @@
import Quickshell.Io
JsonObject {
+ property string logo: ""
property Apps apps: Apps {}
- property Idle idle: Idle {}
property Battery battery: Battery {}
component Apps: JsonObject {
@@ -12,26 +12,6 @@ JsonObject {
property list<string> explorer: ["thunar"]
}
- component Idle: JsonObject {
- property bool lockBeforeSleep: true
- property bool inhibitWhenAudio: true
- property list<var> timeouts: [
- {
- timeout: 180,
- idleAction: "lock"
- },
- {
- timeout: 300,
- idleAction: "dpms off",
- returnAction: "dpms on"
- },
- {
- timeout: 600,
- idleAction: ["systemctl", "suspend-then-hibernate"]
- }
- ]
- }
-
component Battery: JsonObject {
property list<var> warnLevels: [
{
diff --git a/config/LauncherConfig.qml b/config/LauncherConfig.qml
index 6ab44d1..72f14cf 100644
--- a/config/LauncherConfig.qml
+++ b/config/LauncherConfig.qml
@@ -8,13 +8,9 @@ JsonObject {
property int dragThreshold: 50
property bool vimKeybinds: false
property list<string> hiddenApps: []
- property UseFuzzy useFuzzy: UseFuzzy {}
+ property bool useFuzzy: false
property Sizes sizes: Sizes {}
- component UseFuzzy: JsonObject {
- property bool apps: false
- }
-
component Sizes: JsonObject {
property int itemWidth: 600
property int itemHeight: 57
diff --git a/config/LockConfig.qml b/config/LockConfig.qml
deleted file mode 100644
index 2af4e2c..0000000
--- a/config/LockConfig.qml
+++ /dev/null
@@ -1,14 +0,0 @@
-import Quickshell.Io
-
-JsonObject {
- property bool recolourLogo: false
- property bool enableFprint: true
- property int maxFprintTries: 3
- property Sizes sizes: Sizes {}
-
- component Sizes: JsonObject {
- property real heightMult: 0.7
- property real ratio: 16 / 9
- property int centerWidth: 600
- }
-}
diff --git a/config/ServiceConfig.qml b/config/ServiceConfig.qml
index d083b7a..0bd9145 100644
--- a/config/ServiceConfig.qml
+++ b/config/ServiceConfig.qml
@@ -10,7 +10,6 @@ JsonObject {
property real audioIncrement: 0.1
property real brightnessIncrement: 0.1
property real maxVolume: 1.0
- property bool smartScheme: true
property string defaultPlayer: "Spotify"
property list<var> playerAliases: [
{
diff --git a/config/UserPaths.qml b/config/UserPaths.qml
index 0daa4e1..1ff6697 100644
--- a/config/UserPaths.qml
+++ b/config/UserPaths.qml
@@ -3,7 +3,6 @@ import Quickshell.Io
JsonObject {
property string wallpaper: ""
- property string face: ""
property string sessionGif: "root:/assets/len.gif"
property string mediaGif: "root:/assets/bongocat.gif"
}
diff --git a/config/UtilitiesConfig.qml b/config/UtilitiesConfig.qml
index 64eca91..0840886 100644
--- a/config/UtilitiesConfig.qml
+++ b/config/UtilitiesConfig.qml
@@ -22,6 +22,7 @@ JsonObject {
property bool capsLockChanged: true
property bool numLockChanged: true
property bool kbLayoutChanged: true
+ property bool kbLimit: true
property bool vpnChanged: true
property bool nowPlaying: false
}
diff --git a/flake.lock b/flake.lock
index 779edca..ec7f756 100644
--- a/flake.lock
+++ b/flake.lock
@@ -23,11 +23,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1767379071,
- "narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=",
+ "lastModified": 1768886240,
+ "narHash": "sha256-C2TjvwYZ2VDxYWeqvvJ5XPPp6U7H66zeJlRaErJKoEM=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "fb7944c166a3b630f177938e478f0378e64ce108",
+ "rev": "80e4adbcf8992d3fd27ad4964fbb84907f9478b0",
"type": "github"
},
"original": {
@@ -44,11 +44,11 @@
]
},
"locked": {
- "lastModified": 1768204289,
- "narHash": "sha256-aXPr9W8AEmPz9+dH8CHIwrppKIjtS3mHyOXR2ppH/aw=",
+ "lastModified": 1768985439,
+ "narHash": "sha256-qkU4r+l+UPz4dutMMRZSin64HuVZkEv9iFpu9yMWVY0=",
"ref": "refs/heads/master",
- "rev": "db37dc580afc9db1bc598436649c650138b6166d",
- "revCount": 723,
+ "rev": "191085a8821b35680bba16ce5411fc9dbe912237",
+ "revCount": 731,
"type": "git",
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
},
diff --git a/modules/IdleMonitors.qml b/modules/IdleMonitors.qml
deleted file mode 100644
index b7ce058..0000000
--- a/modules/IdleMonitors.qml
+++ /dev/null
@@ -1,51 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import "lock"
-import qs.config
-import qs.services
-import Caelestia.Internal
-import Quickshell
-import Quickshell.Wayland
-
-Scope {
- id: root
-
- required property Lock lock
- readonly property bool enabled: !Config.general.idle.inhibitWhenAudio || !Players.list.some(p => p.isPlaying)
-
- function handleIdleAction(action: var): void {
- if (!action)
- return;
-
- if (action === "lock")
- lock.lock.locked = true;
- else if (action === "unlock")
- lock.lock.locked = false;
- else if (typeof action === "string")
- Hypr.dispatch(action);
- else
- Quickshell.execDetached(action);
- }
-
- LogindManager {
- onAboutToSleep: {
- if (Config.general.idle.lockBeforeSleep)
- root.lock.lock.locked = true;
- }
- onLockRequested: root.lock.lock.locked = true
- onUnlockRequested: root.lock.lock.unlock()
- }
-
- Variants {
- model: Config.general.idle.timeouts
-
- IdleMonitor {
- required property var modelData
-
- enabled: root.enabled && (modelData.enabled ?? true)
- timeout: modelData.timeout
- respectInhibitors: modelData.respectInhibitors ?? true
- onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.returnAction)
- }
- }
-}
diff --git a/modules/background/Background.qml b/modules/background/Background.qml
index 1bc567b..0acce6b 100644
--- a/modules/background/Background.qml
+++ b/modules/background/Background.qml
@@ -10,7 +10,6 @@ import Quickshell.Wayland
import QtQuick
Loader {
- asynchronous: true
active: Config.background.enabled
sourceComponent: Variants {
diff --git a/modules/bar/components/Clock.qml b/modules/bar/components/Clock.qml
index be29c0b..801e93d 100644
--- a/modules/bar/components/Clock.qml
+++ b/modules/bar/components/Clock.qml
@@ -17,7 +17,6 @@ Column {
active: Config.bar.clock.showIcon
visible: active
- asynchronous: true
sourceComponent: MaterialIcon {
text: "calendar_month"
diff --git a/modules/bar/components/StatusIcons.qml b/modules/bar/components/StatusIcons.qml
index 2f52596..442bd2c 100644
--- a/modules/bar/components/StatusIcons.qml
+++ b/modules/bar/components/StatusIcons.qml
@@ -143,7 +143,7 @@ StyledRect {
// Network icon
WrappedLoader {
name: "network"
- active: Config.bar.status.showNetwork
+ active: Config.bar.status.showNetwork && (! Nmcli.activeEthernet || Config.bar.status.showWifi)
sourceComponent: MaterialIcon {
animate: true
@@ -265,7 +265,6 @@ StyledRect {
required property string name
Layout.alignment: Qt.AlignHCenter
- asynchronous: true
visible: active
}
}
diff --git a/modules/bar/components/workspaces/OccupiedBg.qml b/modules/bar/components/workspaces/OccupiedBg.qml
index 0364575..da6fa55 100644
--- a/modules/bar/components/workspaces/OccupiedBg.qml
+++ b/modules/bar/components/workspaces/OccupiedBg.qml
@@ -16,12 +16,15 @@ Item {
property list<var> pills: []
onOccupiedChanged: {
+ if (!occupied) return;
let count = 0;
const start = groupOffset;
const end = start + Config.bar.workspaces.shown;
for (const [ws, occ] of Object.entries(occupied)) {
if (ws > start && ws <= end && occ) {
- if (!occupied[ws - 1]) {
+ const isFirstInGroup = Number(ws) === start + 1;
+ const isLastInGroup = Number(ws) === end;
+ if (isFirstInGroup || !occupied[ws - 1]) {
if (pills[count])
pills[count].start = ws;
else
@@ -30,7 +33,7 @@ Item {
}));
count++;
}
- if (!occupied[ws + 1])
+ if ((isLastInGroup || !occupied[ws + 1]) && pills[count - 1])
pills[count - 1].end = ws;
}
}
@@ -48,8 +51,8 @@ Item {
required property var modelData
- readonly property Workspace start: root.workspaces.itemAt(getWsIdx(modelData.start)) ?? null
- readonly property Workspace end: root.workspaces.itemAt(getWsIdx(modelData.end)) ?? null
+ readonly property Workspace start: root.workspaces.count > 0 ? root.workspaces.itemAt(getWsIdx(modelData.start)) ?? null : null
+ readonly property Workspace end: root.workspaces.count > 0 ? root.workspaces.itemAt(getWsIdx(modelData.end)) ?? null : null
function getWsIdx(ws: int): int {
let i = ws - 1;
diff --git a/modules/bar/popouts/Battery.qml b/modules/bar/popouts/Battery.qml
index 35d32c5..ac975e1 100644
--- a/modules/bar/popouts/Battery.qml
+++ b/modules/bar/popouts/Battery.qml
@@ -40,7 +40,6 @@ Column {
anchors.horizontalCenter: parent.horizontalCenter
active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None
- asynchronous: true
height: active ? (item?.implicitHeight ?? 0) : 0
diff --git a/modules/bar/popouts/Bluetooth.qml b/modules/bar/popouts/Bluetooth.qml
index 91ac560..676da82 100644
--- a/modules/bar/popouts/Bluetooth.qml
+++ b/modules/bar/popouts/Bluetooth.qml
@@ -142,7 +142,6 @@ ColumnLayout {
}
Loader {
- asynchronous: true
active: device.modelData.bonded
sourceComponent: Item {
implicitWidth: connectBtn.implicitWidth
diff --git a/modules/bar/popouts/Content.qml b/modules/bar/popouts/Content.qml
index da993fa..c9a7c5d 100644
--- a/modules/bar/popouts/Content.qml
+++ b/modules/bar/popouts/Content.qml
@@ -6,6 +6,8 @@ import Quickshell
import Quickshell.Services.SystemTray
import QtQuick
+import "./kblayout"
+
Item {
id: root
@@ -114,9 +116,12 @@ Item {
Popout {
name: "kblayout"
- sourceComponent: KbLayout {}
+ sourceComponent: KbLayout {
+ wrapper: root.wrapper
+ }
}
+
Popout {
name: "lockstatus"
sourceComponent: LockStatus {}
diff --git a/modules/bar/popouts/KbLayout.qml b/modules/bar/popouts/KbLayout.qml
deleted file mode 100644
index ace5af2..0000000
--- a/modules/bar/popouts/KbLayout.qml
+++ /dev/null
@@ -1,28 +0,0 @@
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.config
-import Quickshell
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- spacing: Appearance.spacing.normal
-
- StyledText {
- Layout.topMargin: Appearance.padding.normal
- Layout.rightMargin: Appearance.padding.normal
- text: qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull)
- font.weight: 500
- }
-
- TextButton {
- Layout.bottomMargin: Appearance.padding.normal
- Layout.rightMargin: Appearance.padding.normal
- Layout.fillWidth: true
-
- text: qsTr("Switch layout")
- onClicked: Hypr.extras.message("switchxkblayout all next")
- }
-}
diff --git a/modules/bar/popouts/Network.qml b/modules/bar/popouts/Network.qml
index 0e99613..5b32e4a 100644
--- a/modules/bar/popouts/Network.qml
+++ b/modules/bar/popouts/Network.qml
@@ -131,17 +131,13 @@ ColumnLayout {
Nmcli.disconnectFromNetwork();
} else {
root.connectingToSsid = networkItem.modelData.ssid;
- NetworkConnection.handleConnect(
- networkItem.modelData,
- null,
- (network) => {
- // Password is required - show password dialog
- root.passwordNetwork = network;
- root.showPasswordDialog = true;
- root.wrapper.currentName = "wirelesspassword";
- }
- );
-
+ NetworkConnection.handleConnect(networkItem.modelData, null, network => {
+ // Password is required - show password dialog
+ root.passwordNetwork = network;
+ root.showPasswordDialog = true;
+ root.wrapper.currentName = "wirelesspassword";
+ });
+
// Clear connecting state if connection succeeds immediately (saved profile)
// This is handled by the onActiveChanged connection below
}
@@ -216,7 +212,7 @@ ColumnLayout {
anchors.centerIn: parent
strokeWidth: Appearance.padding.small / 2
bgColour: "transparent"
- implicitHeight: parent.implicitHeight - Appearance.padding.smaller * 2
+ implicitSize: parent.implicitHeight - Appearance.padding.smaller * 2
running: Nmcli.scanning
}
}
diff --git a/modules/bar/popouts/TrayMenu.qml b/modules/bar/popouts/TrayMenu.qml
index c87c0e1..9b743db 100644
--- a/modules/bar/popouts/TrayMenu.qml
+++ b/modules/bar/popouts/TrayMenu.qml
@@ -85,7 +85,6 @@ StackView {
anchors.right: parent.right
active: !item.modelData.isSeparator
- asynchronous: true
sourceComponent: Item {
implicitHeight: label.implicitHeight
@@ -118,7 +117,6 @@ StackView {
anchors.left: parent.left
active: item.modelData.icon !== ""
- asynchronous: true
sourceComponent: IconImage {
implicitSize: label.implicitHeight
@@ -155,7 +153,6 @@ StackView {
anchors.right: parent.right
active: item.modelData.hasChildren
- asynchronous: true
sourceComponent: MaterialIcon {
text: "chevron_right"
@@ -169,7 +166,6 @@ StackView {
Loader {
active: menu.isSubMenu
- asynchronous: true
sourceComponent: Item {
implicitWidth: back.implicitWidth
diff --git a/modules/bar/popouts/Wrapper.qml b/modules/bar/popouts/Wrapper.qml
index 5ef4f9d..fc74222 100644
--- a/modules/bar/popouts/Wrapper.qml
+++ b/modules/bar/popouts/Wrapper.qml
@@ -35,8 +35,8 @@ Item {
if (mode === "winfo") {
detachedMode = mode;
} else {
- detachedMode = "any";
queuedMode = mode;
+ detachedMode = "any";
}
focus = true;
}
@@ -101,7 +101,6 @@ Item {
id: content
shouldBeActive: root.hasCurrent && !root.detachedMode
- asynchronous: true
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
@@ -112,7 +111,6 @@ Item {
Comp {
shouldBeActive: root.detachedMode === "winfo"
- asynchronous: true
anchors.centerIn: parent
sourceComponent: WindowInfo {
@@ -123,7 +121,6 @@ Item {
Comp {
shouldBeActive: root.detachedMode === "any"
- asynchronous: true
anchors.centerIn: parent
sourceComponent: ControlCenter {
@@ -173,7 +170,6 @@ Item {
property bool shouldBeActive
- asynchronous: true
active: false
opacity: 0
diff --git a/modules/bar/popouts/kblayout/KbLayout.qml b/modules/bar/popouts/kblayout/KbLayout.qml
new file mode 100644
index 0000000..f612f58
--- /dev/null
+++ b/modules/bar/popouts/kblayout/KbLayout.qml
@@ -0,0 +1,158 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.components
+import qs.components.controls
+import qs.services
+import qs.config
+import qs.utils
+
+import "."
+
+ColumnLayout {
+ id: root
+
+ required property Item wrapper
+
+ spacing: Appearance.spacing.small
+ width: Config.bar.sizes.kbLayoutWidth
+
+ KbLayoutModel { id: kb }
+
+ function refresh() { kb.refresh() }
+ Component.onCompleted: kb.start()
+
+ StyledText {
+ Layout.topMargin: Appearance.padding.normal
+ Layout.rightMargin: Appearance.padding.small
+ text: qsTr("Keyboard Layouts")
+ font.weight: 500
+ }
+
+ ListView {
+ id: list
+ model: kb.visibleModel
+
+ Layout.fillWidth: true
+ Layout.rightMargin: Appearance.padding.small
+ Layout.topMargin: Appearance.spacing.small
+
+ clip: true
+ interactive: true
+ implicitHeight: Math.min(contentHeight, 320)
+ visible: kb.visibleModel.count > 0
+ spacing: Appearance.spacing.small
+
+ add: Transition {
+ NumberAnimation { properties: "opacity"; from: 0; to: 1; duration: 140 }
+ NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic }
+ }
+ remove: Transition { NumberAnimation { properties: "opacity"; to: 0; duration: 100 } }
+ move: Transition { NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic } }
+ displaced: Transition { NumberAnimation { properties: "y"; duration: 180; easing.type: Easing.OutCubic } }
+
+ delegate: Item {
+ required property int layoutIndex
+ required property string label
+
+ width: list.width
+ height: Math.max(36, rowText.implicitHeight + Appearance.padding.small * 2)
+
+ readonly property bool isDisabled: layoutIndex > 3
+
+ StateLayer {
+ id: layer
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ implicitHeight: parent.height - 4
+
+ radius: Appearance.rounding.full
+ enabled: !isDisabled
+
+ function onClicked(): void {
+ if (!isDisabled)
+ kb.switchTo(layoutIndex);
+ }
+ }
+
+ StyledText {
+ id: rowText
+ anchors.verticalCenter: layer.verticalCenter
+ anchors.left: layer.left
+ anchors.right: layer.right
+ anchors.leftMargin: Appearance.padding.small
+ anchors.rightMargin: Appearance.padding.small
+ text: label
+ elide: Text.ElideRight
+ opacity: isDisabled ? 0.4 : 1.0
+ }
+
+ ToolTip.visible: isDisabled && layer.containsMouse
+ ToolTip.text: "XKB limitation: maximum 4 layouts allowed"
+ }
+ }
+
+ Rectangle {
+ visible: kb.activeLabel.length > 0
+ Layout.fillWidth: true
+ Layout.rightMargin: Appearance.padding.small
+ Layout.topMargin: Appearance.spacing.small
+
+ height: 1
+ color: Colours.palette.m3onSurfaceVariant
+ opacity: 0.35
+ }
+
+ RowLayout {
+ id: activeRow
+
+ visible: kb.activeLabel.length > 0
+ Layout.fillWidth: true
+ Layout.rightMargin: Appearance.padding.small
+ Layout.topMargin: Appearance.spacing.small
+ spacing: Appearance.spacing.small
+
+ opacity: 1
+ scale: 1
+
+ MaterialIcon {
+ text: "keyboard"
+ color: Colours.palette.m3primary
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+ text: kb.activeLabel
+ elide: Text.ElideRight
+ font.weight: 500
+ color: Colours.palette.m3primary
+ }
+
+ Connections {
+ target: kb
+ function onActiveLabelChanged() {
+ if (!activeRow.visible)
+ return;
+ popIn.restart();
+ }
+ }
+
+ SequentialAnimation {
+ id: popIn
+ running: false
+
+ ParallelAnimation {
+ NumberAnimation { target: activeRow; property: "opacity"; to: 0.0; duration: 70 }
+ NumberAnimation { target: activeRow; property: "scale"; to: 0.92; duration: 70 }
+ }
+
+ ParallelAnimation {
+ NumberAnimation { target: activeRow; property: "opacity"; to: 1.0; duration: 160; easing.type: Easing.OutCubic }
+ NumberAnimation { target: activeRow; property: "scale"; to: 1.0; duration: 220; easing.type: Easing.OutBack }
+ }
+ }
+ }
+}
diff --git a/modules/bar/popouts/kblayout/KbLayoutModel.qml b/modules/bar/popouts/kblayout/KbLayoutModel.qml
new file mode 100644
index 0000000..41e45b3
--- /dev/null
+++ b/modules/bar/popouts/kblayout/KbLayoutModel.qml
@@ -0,0 +1,200 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+
+import Quickshell
+import Quickshell.Io
+
+import qs.config
+import Caelestia
+
+Item {
+ id: model
+ visible: false
+
+ ListModel { id: _visibleModel }
+ property alias visibleModel: _visibleModel
+
+ property string activeLabel: ""
+ property int activeIndex: -1
+
+ function start() {
+ _xkbXmlBase.running = true;
+ _getKbLayoutOpt.running = true;
+ }
+
+ function refresh() {
+ _notifiedLimit = false;
+ _getKbLayoutOpt.running = true;
+ }
+
+ function switchTo(idx) {
+ _switchProc.command = ["hyprctl", "switchxkblayout", "all", String(idx)];
+ _switchProc.running = true;
+ }
+
+ ListModel { id: _layoutsModel }
+
+ property var _xkbMap: ({})
+ property bool _notifiedLimit: false
+
+ Process {
+ id: _xkbXmlBase
+ command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/base.xml"]
+ stdout: StdioCollector { onStreamFinished: _buildXmlMap(text) }
+ onRunningChanged: if (!running && (typeof exitCode !== "undefined") && exitCode !== 0) _xkbXmlEvdev.running = true
+ }
+
+ Process {
+ id: _xkbXmlEvdev
+ command: ["xmllint", "--xpath", "//layout/configItem[name and description]", "/usr/share/X11/xkb/rules/evdev.xml"]
+ stdout: StdioCollector { onStreamFinished: _buildXmlMap(text) }
+ }
+
+ function _buildXmlMap(xml) {
+ const map = {};
+
+ const re = /<name>\s*([^<]+?)\s*<\/name>[\s\S]*?<description>\s*([^<]+?)\s*<\/description>/g;
+
+ let m;
+ while ((m = re.exec(xml)) !== null) {
+ const code = (m[1] || "").trim();
+ const desc = (m[2] || "").trim();
+ if (!code || !desc) continue;
+ map[code] = _short(desc);
+ }
+
+ if (Object.keys(map).length === 0)
+ return;
+
+ _xkbMap = map;
+
+ if (_layoutsModel.count > 0) {
+ const tmp = [];
+ for (let i = 0; i < _layoutsModel.count; i++) {
+ const it = _layoutsModel.get(i);
+ tmp.push({ layoutIndex: it.layoutIndex, token: it.token, label: _pretty(it.token) });
+ }
+ _layoutsModel.clear();
+ tmp.forEach(t => _layoutsModel.append(t));
+ _fetchActiveLayouts.running = true;
+ }
+ }
+
+ function _short(desc) {
+ const m = desc.match(/^(.*)\((.*)\)$/);
+ if (!m) return desc;
+ const lang = m[1].trim();
+ const region = m[2].trim();
+ const code = (region.split(/[,\s-]/)[0] || region).slice(0, 2).toUpperCase();
+ return `${lang} (${code})`;
+ }
+
+ Process {
+ id: _getKbLayoutOpt
+ command: ["hyprctl", "-j", "getoption", "input:kb_layout"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ try {
+ const j = JSON.parse(text);
+ const raw = (j?.str || j?.value || "").toString().trim();
+ if (raw.length) {
+ _setLayouts(raw);
+ _fetchActiveLayouts.running = true;
+ return;
+ }
+ } catch (e) {}
+ _fetchLayoutsFromDevices.running = true;
+ }
+ }
+ }
+
+ Process {
+ id: _fetchLayoutsFromDevices
+ command: ["hyprctl", "-j", "devices"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ try {
+ const dev = JSON.parse(text);
+ const kb = dev?.keyboards?.find(k => k.main) || dev?.keyboards?.[0];
+ const raw = (kb?.layout || "").trim();
+ if (raw.length) _setLayouts(raw);
+ } catch (e) {}
+ _fetchActiveLayouts.running = true;
+ }
+ }
+ }
+
+ Process {
+ id: _fetchActiveLayouts
+ command: ["hyprctl", "-j", "devices"]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ try {
+ const dev = JSON.parse(text);
+ const kb = dev?.keyboards?.find(k => k.main) || dev?.keyboards?.[0];
+ const idx = kb?.active_layout_index ?? -1;
+
+ activeIndex = idx >= 0 ? idx : -1;
+ activeLabel =
+ (idx >= 0 && idx < _layoutsModel.count)
+ ? _layoutsModel.get(idx).label
+ : "";
+ } catch (e) {
+ activeIndex = -1;
+ activeLabel = "";
+ }
+
+ _rebuildVisible();
+ }
+ }
+ }
+
+ Process {
+ id: _switchProc
+ onRunningChanged: if (!running) _fetchActiveLayouts.running = true
+ }
+
+ function _setLayouts(raw) {
+ const parts = raw.split(",").map(s => s.trim()).filter(Boolean);
+ _layoutsModel.clear();
+
+ const seen = new Set();
+ let idx = 0;
+
+ for (const p of parts) {
+ if (seen.has(p)) continue;
+ seen.add(p);
+ _layoutsModel.append({ layoutIndex: idx, token: p, label: _pretty(p) });
+ idx++;
+ }
+ }
+
+ function _rebuildVisible() {
+ _visibleModel.clear();
+
+ let arr = [];
+ for (let i = 0; i < _layoutsModel.count; i++)
+ arr.push(_layoutsModel.get(i));
+
+ arr = arr.filter(i => i.layoutIndex !== activeIndex);
+ arr.forEach(i => _visibleModel.append(i));
+
+ if (!Config.utilities.toasts.kbLimit)
+ return;
+
+ if (_layoutsModel.count > 4) {
+ Toaster.toast(
+ qsTr("Keyboard layout limit"),
+ qsTr("XKB supports only 4 layouts at a time"),
+ "warning"
+ );
+ }
+ }
+
+ function _pretty(token) {
+ const code = token.replace(/\(.*\)$/, "").trim();
+ if (_xkbMap[code]) return code.toUpperCase() + " - " + _xkbMap[code];
+ return code.toUpperCase() + " - " + code;
+ }
+}
diff --git a/modules/controlcenter/ControlCenter.qml b/modules/controlcenter/ControlCenter.qml
index fdb824e..043e7e1 100644
--- a/modules/controlcenter/ControlCenter.qml
+++ b/modules/controlcenter/ControlCenter.qml
@@ -42,7 +42,6 @@ Item {
Layout.fillWidth: true
Layout.columnSpan: 2
- asynchronous: true
active: root.floating
visible: active
diff --git a/modules/controlcenter/NavRail.qml b/modules/controlcenter/NavRail.qml
index ef338b2..e61a741 100644
--- a/modules/controlcenter/NavRail.qml
+++ b/modules/controlcenter/NavRail.qml
@@ -43,7 +43,6 @@ Item {
Loader {
Layout.topMargin: Appearance.spacing.large
- asynchronous: true
active: !root.session.floating
visible: active
diff --git a/modules/controlcenter/Panes.qml b/modules/controlcenter/Panes.qml
index 32ee708..03f74be 100644
--- a/modules/controlcenter/Panes.qml
+++ b/modules/controlcenter/Panes.qml
@@ -126,11 +126,10 @@ ClippingRectangle {
anchors.fill: parent
clip: false
- asynchronous: true
active: false
Component.onCompleted: {
- pane.updateActive();
+ Qt.callLater(pane.updateActive);
}
onActiveChanged: {
diff --git a/modules/controlcenter/Session.qml b/modules/controlcenter/Session.qml
index 090ae9b..164e6cd 100644
--- a/modules/controlcenter/Session.qml
+++ b/modules/controlcenter/Session.qml
@@ -15,6 +15,6 @@ QtObject {
readonly property NetworkState network: NetworkState {}
readonly property EthernetState ethernet: EthernetState {}
- onActiveChanged: activeIndex = panes.indexOf(active)
- onActiveIndexChanged: active = panes[activeIndex]
+ onActiveChanged: activeIndex = Math.max(0, panes.indexOf(active))
+ onActiveIndexChanged: if (panes[activeIndex]) active = panes[activeIndex]
}
diff --git a/modules/controlcenter/audio/AudioPane.qml b/modules/controlcenter/audio/AudioPane.qml
index 76122f9..e4a1a64 100644
--- a/modules/controlcenter/audio/AudioPane.qml
+++ b/modules/controlcenter/audio/AudioPane.qml
@@ -460,6 +460,151 @@ Item {
}
}
}
+
+ SectionHeader {
+ title: qsTr("Applications")
+ description: qsTr("Control volume for individual applications")
+ }
+
+ SectionContainer {
+ contentSpacing: Appearance.spacing.normal
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.small
+
+ Repeater {
+ model: Audio.streams
+ Layout.fillWidth: true
+
+ delegate: ColumnLayout {
+ required property var modelData
+ required property int index
+
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.smaller
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Appearance.spacing.normal
+
+ MaterialIcon {
+ text: "apps"
+ font.pointSize: Appearance.font.size.normal
+ fill: 0
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ text: Audio.getStreamName(modelData)
+ font.pointSize: Appearance.font.size.normal
+ font.weight: 500
+ }
+
+ StyledInputField {
+ id: streamVolumeInput
+ Layout.preferredWidth: 70
+ validator: IntValidator { bottom: 0; top: 100 }
+ enabled: !Audio.getStreamMuted(modelData)
+
+ Component.onCompleted: {
+ text = Math.round(Audio.getStreamVolume(modelData) * 100).toString();
+ }
+
+ Connections {
+ target: modelData
+ function onAudioChanged() {
+ if (!streamVolumeInput.hasFocus && modelData?.audio) {
+ streamVolumeInput.text = Math.round(modelData.audio.volume * 100).toString();
+ }
+ }
+ }
+
+ onTextEdited: (text) => {
+ if (hasFocus) {
+ const val = parseInt(text);
+ if (!isNaN(val) && val >= 0 && val <= 100) {
+ Audio.setStreamVolume(modelData, val / 100);
+ }
+ }
+ }
+
+ onEditingFinished: {
+ const val = parseInt(text);
+ if (isNaN(val) || val < 0 || val > 100) {
+ text = Math.round(Audio.getStreamVolume(modelData) * 100).toString();
+ }
+ }
+ }
+
+ StyledText {
+ text: "%"
+ color: Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.normal
+ opacity: Audio.getStreamMuted(modelData) ? 0.5 : 1
+ }
+
+ StyledRect {
+ implicitWidth: implicitHeight
+ implicitHeight: streamMuteIcon.implicitHeight + Appearance.padding.normal * 2
+
+ radius: Appearance.rounding.normal
+ color: Audio.getStreamMuted(modelData) ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
+
+ StateLayer {
+ function onClicked(): void {
+ Audio.setStreamMuted(modelData, !Audio.getStreamMuted(modelData));
+ }
+ }
+
+ MaterialIcon {
+ id: streamMuteIcon
+
+ anchors.centerIn: parent
+ text: Audio.getStreamMuted(modelData) ? "volume_off" : "volume_up"
+ color: Audio.getStreamMuted(modelData) ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
+ }
+ }
+ }
+
+ StyledSlider {
+ Layout.fillWidth: true
+ implicitHeight: Appearance.padding.normal * 3
+
+ value: Audio.getStreamVolume(modelData)
+ enabled: !Audio.getStreamMuted(modelData)
+ opacity: enabled ? 1 : 0.5
+ onMoved: {
+ Audio.setStreamVolume(modelData, value);
+ if (!streamVolumeInput.hasFocus) {
+ streamVolumeInput.text = Math.round(value * 100).toString();
+ }
+ }
+
+ Connections {
+ target: modelData
+ function onAudioChanged() {
+ if (modelData?.audio) {
+ value = modelData.audio.volume;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ StyledText {
+ Layout.fillWidth: true
+ visible: Audio.streams.length === 0
+ text: qsTr("No applications currently playing audio")
+ color: Colours.palette.m3outline
+ font.pointSize: Appearance.font.size.small
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+ }
}
}
}
diff --git a/modules/controlcenter/components/SplitPaneLayout.qml b/modules/controlcenter/components/SplitPaneLayout.qml
index 8b4f0d9..3609a74 100644
--- a/modules/controlcenter/components/SplitPaneLayout.qml
+++ b/modules/controlcenter/components/SplitPaneLayout.qml
@@ -49,7 +49,6 @@ RowLayout {
anchors.leftMargin: Appearance.padding.large
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
- asynchronous: true
sourceComponent: root.leftContent
Component.onCompleted: {
@@ -91,7 +90,6 @@ RowLayout {
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
- asynchronous: true
sourceComponent: root.rightContent
Component.onCompleted: {
diff --git a/modules/controlcenter/components/SplitPaneWithDetails.qml b/modules/controlcenter/components/SplitPaneWithDetails.qml
index e873923..5db5bdb 100644
--- a/modules/controlcenter/components/SplitPaneWithDetails.qml
+++ b/modules/controlcenter/components/SplitPaneWithDetails.qml
@@ -56,7 +56,6 @@ Item {
transformOrigin: Item.Center
clip: false
- asynchronous: true
sourceComponent: rightPaneItem.targetComponent
}
diff --git a/modules/controlcenter/network/NetworkSettings.qml b/modules/controlcenter/network/NetworkSettings.qml
index 22e07cb..81175fb 100644
--- a/modules/controlcenter/network/NetworkSettings.qml
+++ b/modules/controlcenter/network/NetworkSettings.qml
@@ -4,10 +4,12 @@ import ".."
import "../components"
import qs.components
import qs.components.controls
+import qs.components.containers
import qs.components.effects
import qs.services
import qs.config
import QtQuick
+import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
diff --git a/modules/controlcenter/network/NetworkingPane.qml b/modules/controlcenter/network/NetworkingPane.qml
index b430cce..9047174 100644
--- a/modules/controlcenter/network/NetworkingPane.qml
+++ b/modules/controlcenter/network/NetworkingPane.qml
@@ -153,9 +153,9 @@ Item {
rightContent: Component {
Item {
id: rightPaneItem
-
- property var ethernetPane: root.session.ethernet.active
- property var wirelessPane: root.session.network.active
+
+ property var ethernetPane: root.session && root.session.ethernet ? root.session.ethernet.active : null
+ property var wirelessPane: root.session && root.session.network ? root.session.network.active : null
property var pane: ethernetPane || wirelessPane
property string paneId: ethernetPane ? ("eth:" + (ethernetPane.interface || "")) : (wirelessPane ? ("wifi:" + (wirelessPane.ssid || wirelessPane.bssid || "")) : "settings")
property Component targetComponent: settingsComponent
@@ -173,28 +173,28 @@ Item {
}
Connections {
- target: root.session.ethernet
+ target: root.session && root.session.ethernet ? root.session.ethernet : null
+ enabled: target !== null
+
function onActiveChanged() {
- // Clear wireless when ethernet is selected
- if (root.session.ethernet.active && root.session.network.active) {
- root.session.network.active = null;
- return; // Let the network.onActiveChanged handle the update
+ // Clear others when ethernet is selected
+ if (root.session && root.session.ethernet && root.session.ethernet.active) {
+ if (root.session.network && root.session.network.active) root.session.network.active = null;
}
rightPaneItem.nextComponent = rightPaneItem.getComponentForPane();
- // paneId will automatically update via property binding
}
}
Connections {
- target: root.session.network
+ target: root.session && root.session.network ? root.session.network : null
+ enabled: target !== null
+
function onActiveChanged() {
- // Clear ethernet when wireless is selected
- if (root.session.network.active && root.session.ethernet.active) {
- root.session.ethernet.active = null;
- return; // Let the ethernet.onActiveChanged handle the update
+ // Clear others when wireless is selected
+ if (root.session && root.session.network && root.session.network.active) {
+ if (root.session.ethernet && root.session.ethernet.active) root.session.ethernet.active = null;
}
rightPaneItem.nextComponent = rightPaneItem.getComponentForPane();
- // paneId will automatically update via property binding
}
}
diff --git a/modules/dashboard/Content.qml b/modules/dashboard/Content.qml
index f5f0e46..79659c5 100644
--- a/modules/dashboard/Content.qml
+++ b/modules/dashboard/Content.qml
@@ -85,6 +85,7 @@ Item {
id: row
Pane {
+ index: 0
sourceComponent: Dash {
visibilities: root.visibilities
state: root.state
@@ -92,16 +93,19 @@ Item {
}
Pane {
+ index: 1
sourceComponent: Media {
visibilities: root.visibilities
}
}
Pane {
+ index: 2
sourceComponent: Performance {}
}
Pane {
+ index: 3
sourceComponent: Weather {}
}
}
@@ -127,9 +131,15 @@ Item {
}
component Pane: Loader {
+ id: pane
+
+ required property int index
+
Layout.alignment: Qt.AlignTop
Component.onCompleted: active = Qt.binding(() => {
+ // Always keep current tab loaded
+ if (pane.index === view.currentIndex) return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
diff --git a/modules/dashboard/dash/DateTime.qml b/modules/dashboard/dash/DateTime.qml
index bbef067..e740448 100644
--- a/modules/dashboard/dash/DateTime.qml
+++ b/modules/dashboard/dash/DateTime.qml
@@ -9,8 +9,6 @@ import QtQuick.Layouts
Item {
id: root
- readonly property list<string> timeComponents: Time.format(Config.services.useTwelveHourClock ? "hh:mm:A" : "hh:mm").split(":")
-
anchors.top: parent.top
anchors.bottom: parent.bottom
implicitWidth: Config.dashboard.sizes.dateTimeWidth
@@ -24,7 +22,7 @@ Item {
StyledText {
Layout.bottomMargin: -(font.pointSize * 0.4)
Layout.alignment: Qt.AlignHCenter
- text: root.timeComponents[0]
+ text: Time.hourStr
color: Colours.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
font.family: Appearance.font.family.clock
@@ -42,7 +40,7 @@ Item {
StyledText {
Layout.topMargin: -(font.pointSize * 0.4)
Layout.alignment: Qt.AlignHCenter
- text: root.timeComponents[1]
+ text: Time.minuteStr
color: Colours.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
font.family: Appearance.font.family.clock
@@ -52,12 +50,11 @@ Item {
Loader {
Layout.alignment: Qt.AlignHCenter
- asynchronous: true
active: Config.services.useTwelveHourClock
visible: active
sourceComponent: StyledText {
- text: root.timeComponents[2] ?? ""
+ text: Time.amPmStr
color: Colours.palette.m3primary
font.pointSize: Appearance.font.size.large
font.family: Appearance.font.family.clock
diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml
index 9fc38bd..00f9596 100644
--- a/modules/drawers/Drawers.qml
+++ b/modules/drawers/Drawers.qml
@@ -47,7 +47,7 @@ Variants {
return 0;
const mon = Hypr.monitorFor(screen);
- if (mon?.lastIpcObject.specialWorkspace.name || mon?.activeWorkspace?.lastIpcObject.windows > 0)
+ if (mon?.lastIpcObject?.specialWorkspace?.name || mon?.activeWorkspace?.lastIpcObject?.windows > 0)
return 0;
const thresholds = [];
diff --git a/modules/launcher/services/Apps.qml b/modules/launcher/services/Apps.qml
index 79f3754..0c392c6 100644
--- a/modules/launcher/services/Apps.qml
+++ b/modules/launcher/services/Apps.qml
@@ -48,8 +48,8 @@ Searcher {
keys = ["keywords", "name"];
weights = [0.9, 0.1];
} else {
- keys = ["name"];
- weights = [1];
+ keys = ["name", "execString"];
+ weights = [1, 0.1];
if (!search.startsWith(`${prefix}t `))
return query(search).map(e => e.entry);
@@ -66,7 +66,7 @@ Searcher {
}
list: appDb.apps
- useFuzzy: Config.launcher.useFuzzy.apps
+ useFuzzy: Config.launcher.useFuzzy
AppDb {
id: appDb
diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml
deleted file mode 100644
index 4e2215b..0000000
--- a/modules/lock/Center.qml
+++ /dev/null
@@ -1,417 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.config
-import qs.utils
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property var lock
- readonly property list<string> timeComponents: Time.format(Config.services.useTwelveHourClock ? "hh:mm:A" : "hh:mm").split(":")
- readonly property real centerScale: Math.min(1, (lock.screen?.height ?? 1440) / 1440)
- readonly property int centerWidth: Config.lock.sizes.centerWidth * centerScale
-
- Layout.preferredWidth: centerWidth
- Layout.fillWidth: false
- Layout.fillHeight: true
-
- spacing: Appearance.spacing.large * 2
-
- RowLayout {
- Layout.alignment: Qt.AlignHCenter
- spacing: Appearance.spacing.small
-
- StyledText {
- Layout.alignment: Qt.AlignVCenter
- text: root.timeComponents[0]
- color: Colours.palette.m3secondary
- font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
- font.family: Appearance.font.family.clock
- font.bold: true
- }
-
- StyledText {
- Layout.alignment: Qt.AlignVCenter
- text: ":"
- color: Colours.palette.m3primary
- font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
- font.family: Appearance.font.family.clock
- font.bold: true
- }
-
- StyledText {
- Layout.alignment: Qt.AlignVCenter
- text: root.timeComponents[1]
- color: Colours.palette.m3secondary
- font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
- font.family: Appearance.font.family.clock
- font.bold: true
- }
-
- Loader {
- Layout.leftMargin: Appearance.spacing.small
- Layout.alignment: Qt.AlignVCenter
-
- asynchronous: true
- active: Config.services.useTwelveHourClock
- visible: active
-
- sourceComponent: StyledText {
- text: root.timeComponents[2] ?? ""
- color: Colours.palette.m3primary
- font.pointSize: Math.floor(Appearance.font.size.extraLarge * 2 * root.centerScale)
- font.family: Appearance.font.family.clock
- font.bold: true
- }
- }
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- Layout.topMargin: -Appearance.padding.large * 2
-
- text: Time.format("dddd, d MMMM yyyy")
- color: Colours.palette.m3tertiary
- font.pointSize: Math.floor(Appearance.font.size.extraLarge * root.centerScale)
- font.family: Appearance.font.family.mono
- font.bold: true
- }
-
- StyledClippingRect {
- Layout.topMargin: Appearance.spacing.large * 2
- Layout.alignment: Qt.AlignHCenter
-
- implicitWidth: root.centerWidth / 2
- implicitHeight: root.centerWidth / 2
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.full
-
- MaterialIcon {
- anchors.centerIn: parent
-
- text: "person"
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Math.floor(root.centerWidth / 4)
- }
-
- Image {
- id: pfp
-
- anchors.fill: parent
- source: Paths.face
- }
- }
-
- StyledRect {
- Layout.alignment: Qt.AlignHCenter
-
- implicitWidth: root.centerWidth * 0.8
- implicitHeight: input.implicitHeight + Appearance.padding.small * 2
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.full
-
- focus: true
- onActiveFocusChanged: {
- if (!activeFocus)
- forceActiveFocus();
- }
-
- Keys.onPressed: event => {
- if (root.lock.unlocking)
- return;
-
- if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
- inputField.placeholder.animate = false;
-
- root.lock.pam.handleKey(event);
- }
-
- StateLayer {
- hoverEnabled: false
- cursorShape: Qt.IBeamCursor
-
- function onClicked(): void {
- parent.forceActiveFocus();
- }
- }
-
- RowLayout {
- id: input
-
- anchors.fill: parent
- anchors.margins: Appearance.padding.small
- spacing: Appearance.spacing.normal
-
- Item {
- implicitWidth: implicitHeight
- implicitHeight: fprintIcon.implicitHeight + Appearance.padding.small * 2
-
- MaterialIcon {
- id: fprintIcon
-
- anchors.centerIn: parent
- animate: true
- text: {
- if (root.lock.pam.fprint.tries >= Config.lock.maxFprintTries)
- return "fingerprint_off";
- if (root.lock.pam.fprint.active)
- return "fingerprint";
- return "lock";
- }
- color: root.lock.pam.fprint.tries >= Config.lock.maxFprintTries ? Colours.palette.m3error : Colours.palette.m3onSurface
- opacity: root.lock.pam.passwd.active ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- CircularIndicator {
- anchors.fill: parent
- running: root.lock.pam.passwd.active
- }
- }
-
- InputField {
- id: inputField
-
- pam: root.lock.pam
- }
-
- StyledRect {
- implicitWidth: implicitHeight
- implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2
-
- color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
- radius: Appearance.rounding.full
-
- StateLayer {
- color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
-
- function onClicked(): void {
- root.lock.pam.passwd.start();
- }
- }
-
- MaterialIcon {
- id: enterIcon
-
- anchors.centerIn: parent
- text: "arrow_forward"
- color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
- font.weight: 500
- }
- }
- }
- }
-
- Item {
- Layout.fillWidth: true
- Layout.topMargin: -Appearance.spacing.large
-
- implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
-
- Behavior on implicitHeight {
- Anim {}
- }
-
- StyledText {
- id: stateMessage
-
- readonly property string msg: {
- if (Hypr.kbLayout !== Hypr.defaultKbLayout) {
- if (Hypr.capsLock && Hypr.numLock)
- return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull);
- if (Hypr.capsLock)
- return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
- if (Hypr.numLock)
- return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
- return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull);
- }
-
- if (Hypr.capsLock && Hypr.numLock)
- return qsTr("Caps lock and Num lock are ON.");
- if (Hypr.capsLock)
- return qsTr("Caps lock is ON.");
- if (Hypr.numLock)
- return qsTr("Num lock is ON.");
-
- return "";
- }
-
- property bool shouldBeVisible
-
- onMsgChanged: {
- if (msg) {
- if (opacity > 0) {
- animate = true;
- text = msg;
- animate = false;
- } else {
- text = msg;
- }
- shouldBeVisible = true;
- } else {
- shouldBeVisible = false;
- }
- }
-
- anchors.left: parent.left
- anchors.right: parent.right
-
- scale: shouldBeVisible && !message.msg ? 1 : 0.7
- opacity: shouldBeVisible && !message.msg ? 1 : 0
- color: Colours.palette.m3onSurfaceVariant
- animateProp: "opacity"
-
- font.family: Appearance.font.family.mono
- horizontalAlignment: Qt.AlignHCenter
- wrapMode: Text.WrapAtWordBoundaryOrAnywhere
- lineHeight: 1.2
-
- Behavior on scale {
- Anim {}
- }
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- StyledText {
- id: message
-
- readonly property Pam pam: root.lock.pam
- readonly property string msg: {
- if (pam.fprintState === "error")
- return qsTr("FP ERROR: %1").arg(pam.fprint.message);
- if (pam.state === "error")
- return qsTr("PW ERROR: %1").arg(pam.passwd.message);
-
- if (pam.lockMessage)
- return pam.lockMessage;
-
- if (pam.state === "max" && pam.fprintState === "max")
- return qsTr("Maximum password and fingerprint attempts reached.");
- if (pam.state === "max") {
- if (pam.fprint.available)
- return qsTr("Maximum password attempts reached. Please use fingerprint.");
- return qsTr("Maximum password attempts reached.");
- }
- if (pam.fprintState === "max")
- return qsTr("Maximum fingerprint attempts reached. Please use password.");
-
- if (pam.state === "fail") {
- if (pam.fprint.available)
- return qsTr("Incorrect password. Please try again or use fingerprint.");
- return qsTr("Incorrect password. Please try again.");
- }
- if (pam.fprintState === "fail")
- return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries);
-
- return "";
- }
-
- anchors.left: parent.left
- anchors.right: parent.right
-
- scale: 0.7
- opacity: 0
- color: Colours.palette.m3error
-
- font.pointSize: Appearance.font.size.small
- font.family: Appearance.font.family.mono
- horizontalAlignment: Qt.AlignHCenter
- wrapMode: Text.WrapAtWordBoundaryOrAnywhere
-
- onMsgChanged: {
- if (msg) {
- if (opacity > 0) {
- animate = true;
- text = msg;
- animate = false;
-
- exitAnim.stop();
- if (scale < 1)
- appearAnim.restart();
- else
- flashAnim.restart();
- } else {
- text = msg;
- exitAnim.stop();
- appearAnim.restart();
- }
- } else {
- appearAnim.stop();
- flashAnim.stop();
- exitAnim.start();
- }
- }
-
- Connections {
- target: root.lock.pam
-
- function onFlashMsg(): void {
- exitAnim.stop();
- if (message.scale < 1)
- appearAnim.restart();
- else
- flashAnim.restart();
- }
- }
-
- Anim {
- id: appearAnim
-
- target: message
- properties: "scale,opacity"
- to: 1
- onFinished: flashAnim.restart()
- }
-
- SequentialAnimation {
- id: flashAnim
-
- loops: 2
-
- FlashAnim {
- to: 0.3
- }
- FlashAnim {
- to: 1
- }
- }
-
- ParallelAnimation {
- id: exitAnim
-
- Anim {
- target: message
- property: "scale"
- to: 0.7
- duration: Appearance.anim.durations.large
- }
- Anim {
- target: message
- property: "opacity"
- to: 0
- duration: Appearance.anim.durations.large
- }
- }
- }
- }
-
- component FlashAnim: NumberAnimation {
- target: message
- property: "opacity"
- duration: Appearance.anim.durations.small
- easing.type: Easing.Linear
- }
-}
diff --git a/modules/lock/Content.qml b/modules/lock/Content.qml
deleted file mode 100644
index daa87ac..0000000
--- a/modules/lock/Content.qml
+++ /dev/null
@@ -1,27 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-RowLayout {
- id: root
-
- required property var lock
-
- spacing: Appearance.spacing.large * 2
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
- }
-
- Center {
- lock: root.lock
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
- }
-}
diff --git a/modules/lock/InputField.qml b/modules/lock/InputField.qml
deleted file mode 100644
index 1acc787..0000000
--- a/modules/lock/InputField.qml
+++ /dev/null
@@ -1,149 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.services
-import qs.config
-import Quickshell
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property Pam pam
- readonly property alias placeholder: placeholder
- property string buffer
-
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- clip: true
-
- Connections {
- target: root.pam
-
- function onBufferChanged(): void {
- if (root.pam.buffer.length > root.buffer.length) {
- charList.bindImWidth();
- } else if (root.pam.buffer.length === 0) {
- charList.implicitWidth = charList.implicitWidth;
- placeholder.animate = true;
- }
-
- root.buffer = root.pam.buffer;
- }
- }
-
- StyledText {
- id: placeholder
-
- anchors.centerIn: parent
-
- text: {
- if (root.pam.passwd.active)
- return qsTr("Loading...");
- if (root.pam.state === "max")
- return qsTr("You have reached the maximum number of tries");
- return qsTr("Enter your password");
- }
-
- animate: true
- color: root.pam.passwd.active ? Colours.palette.m3secondary : Colours.palette.m3outline
- font.pointSize: Appearance.font.size.normal
- font.family: Appearance.font.family.mono
-
- opacity: root.buffer ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- ListView {
- id: charList
-
- readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
-
- function bindImWidth(): void {
- imWidthBehavior.enabled = false;
- implicitWidth = Qt.binding(() => fullWidth);
- imWidthBehavior.enabled = true;
- }
-
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
-
- implicitWidth: fullWidth
- implicitHeight: Appearance.font.size.normal
-
- orientation: Qt.Horizontal
- spacing: Appearance.spacing.small / 2
- interactive: false
-
- model: ScriptModel {
- values: root.buffer.split("")
- }
-
- delegate: StyledRect {
- id: ch
-
- implicitWidth: implicitHeight
- implicitHeight: charList.implicitHeight
-
- color: Colours.palette.m3onSurface
- radius: Appearance.rounding.full
-
- opacity: 0
- scale: 0
- Component.onCompleted: {
- opacity = 1;
- scale = 1;
- }
- ListView.onRemove: removeAnim.start()
-
- SequentialAnimation {
- id: removeAnim
-
- PropertyAction {
- target: ch
- property: "ListView.delayRemove"
- value: true
- }
- ParallelAnimation {
- Anim {
- target: ch
- property: "opacity"
- to: 0
- }
- Anim {
- target: ch
- property: "scale"
- to: 0.5
- }
- }
- PropertyAction {
- target: ch
- property: "ListView.delayRemove"
- value: false
- }
- }
-
- Behavior on opacity {
- Anim {}
- }
-
- Behavior on scale {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
- }
-
- Behavior on implicitWidth {
- id: imWidthBehavior
-
- Anim {}
- }
- }
-}
diff --git a/modules/lock/Lock.qml b/modules/lock/Lock.qml
deleted file mode 100644
index 6fd5277..0000000
--- a/modules/lock/Lock.qml
+++ /dev/null
@@ -1,55 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components.misc
-import Quickshell
-import Quickshell.Io
-import Quickshell.Wayland
-
-Scope {
- property alias lock: lock
-
- WlSessionLock {
- id: lock
-
- signal unlock
-
- LockSurface {
- lock: lock
- pam: pam
- }
- }
-
- Pam {
- id: pam
-
- lock: lock
- }
-
- CustomShortcut {
- name: "lock"
- description: "Lock the current session"
- onPressed: lock.locked = true
- }
-
- CustomShortcut {
- name: "unlock"
- description: "Unlock the current session"
- onPressed: lock.unlock()
- }
-
- IpcHandler {
- target: "lock"
-
- function lock(): void {
- lock.locked = true;
- }
-
- function unlock(): void {
- lock.unlock();
- }
-
- function isLocked(): bool {
- return lock.locked;
- }
- }
-}
diff --git a/modules/lock/LockSurface.qml b/modules/lock/LockSurface.qml
deleted file mode 100644
index 0c3ce69..0000000
--- a/modules/lock/LockSurface.qml
+++ /dev/null
@@ -1,231 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.services
-import qs.config
-import qs.utils
-import Quickshell.Wayland
-import QtQuick
-import QtQuick.Effects
-
-WlSessionLockSurface {
- id: root
-
- required property WlSessionLock lock
- required property Pam pam
-
- readonly property alias unlocking: unlockAnim.running
-
- color: "transparent"
-
- Connections {
- target: root.lock
-
- function onUnlock(): void {
- unlockAnim.start();
- }
- }
-
- SequentialAnimation {
- id: unlockAnim
-
- ParallelAnimation {
- Anim {
- target: lockContent
- properties: "implicitWidth,implicitHeight"
- to: lockContent.size
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- Anim {
- target: lockBg
- property: "radius"
- to: lockContent.radius
- }
- Anim {
- target: content
- property: "scale"
- to: 0
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- Anim {
- target: content
- property: "opacity"
- to: 0
- duration: Appearance.anim.durations.small
- }
- Anim {
- target: lockIcon
- property: "opacity"
- to: 1
- duration: Appearance.anim.durations.large
- }
- Anim {
- target: background
- property: "opacity"
- to: 0
- duration: Appearance.anim.durations.large
- }
- SequentialAnimation {
- PauseAnimation {
- duration: Appearance.anim.durations.small
- }
- Anim {
- target: lockContent
- property: "opacity"
- to: 0
- }
- }
- }
- PropertyAction {
- target: root.lock
- property: "locked"
- value: false
- }
- }
-
- ParallelAnimation {
- id: initAnim
-
- running: true
-
- Anim {
- target: background
- property: "opacity"
- to: 1
- duration: Appearance.anim.durations.large
- }
- SequentialAnimation {
- ParallelAnimation {
- Anim {
- target: lockContent
- property: "scale"
- to: 1
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- Anim {
- target: lockContent
- property: "rotation"
- to: 360
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.standardAccel
- }
- }
- ParallelAnimation {
- Anim {
- target: lockIcon
- property: "rotation"
- to: 360
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- target: lockIcon
- property: "opacity"
- to: 0
- }
- Anim {
- target: content
- property: "opacity"
- to: 1
- }
- Anim {
- target: content
- property: "scale"
- to: 1
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- Anim {
- target: lockBg
- property: "radius"
- to: Appearance.rounding.large * 1.5
- }
- Anim {
- target: lockContent
- property: "implicitWidth"
- to: root.screen?.height * Config.lock.sizes.heightMult * Config.lock.sizes.ratio
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- Anim {
- target: lockContent
- property: "implicitHeight"
- to: root.screen?.height * Config.lock.sizes.heightMult
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
- }
- }
-
- Image {
- id: background
-
- anchors.fill: parent
- source: Paths.wallpaper ?? ""
- opacity: 1
-
- layer.enabled: true
- layer.effect: MultiEffect {
- autoPaddingEnabled: false
- blurEnabled: true
- blur: 1
- blurMax: 64
- blurMultiplier: 1
- }
- }
-
- Item {
- id: lockContent
-
- readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
- readonly property int radius: size / 4 * Appearance.rounding.scale
-
- anchors.centerIn: parent
- implicitWidth: size
- implicitHeight: size
-
- rotation: 180
- scale: 0
-
- StyledRect {
- id: lockBg
-
- anchors.fill: parent
- color: Colours.palette.m3surface
- radius: parent.radius
- opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
-
- layer.enabled: true
- layer.effect: MultiEffect {
- shadowEnabled: true
- blurMax: 15
- shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
- }
- }
-
- MaterialIcon {
- id: lockIcon
-
- anchors.centerIn: parent
- text: "lock"
- font.pointSize: Appearance.font.size.extraLarge * 4
- font.bold: true
- rotation: 180
- }
-
- Content {
- id: content
-
- anchors.centerIn: parent
- width: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult * Config.lock.sizes.ratio - Appearance.padding.large * 2
- height: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult - Appearance.padding.large * 2
-
- lock: root
- opacity: 0
- scale: 0
- }
- }
-}
diff --git a/modules/lock/Pam.qml b/modules/lock/Pam.qml
deleted file mode 100644
index 0186c2f..0000000
--- a/modules/lock/Pam.qml
+++ /dev/null
@@ -1,193 +0,0 @@
-import qs.config
-import Quickshell
-import Quickshell.Io
-import Quickshell.Wayland
-import Quickshell.Services.Pam
-import QtQuick
-
-Scope {
- id: root
-
- required property WlSessionLock lock
-
- readonly property alias passwd: passwd
- readonly property alias fprint: fprint
- property string lockMessage
- property string state
- property string fprintState
- property string buffer
-
- signal flashMsg
-
- function handleKey(event: KeyEvent): void {
- if (passwd.active || state === "max")
- return;
-
- if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
- passwd.start();
- } else if (event.key === Qt.Key_Backspace) {
- if (event.modifiers & Qt.ControlModifier) {
- buffer = "";
- } else {
- buffer = buffer.slice(0, -1);
- }
- } else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
- // No illegal characters (you are insane if you use unicode in your password)
- buffer += event.text;
- }
- }
-
- PamContext {
- id: passwd
-
- config: "passwd"
- configDirectory: Quickshell.shellDir + "/assets/pam.d"
-
- onMessageChanged: {
- if (message.startsWith("The account is locked"))
- root.lockMessage = message;
- else if (root.lockMessage && message.endsWith(" left to unlock)"))
- root.lockMessage += "\n" + message;
- }
-
- onResponseRequiredChanged: {
- if (!responseRequired)
- return;
-
- respond(root.buffer);
- root.buffer = "";
- }
-
- onCompleted: res => {
- if (res === PamResult.Success)
- return root.lock.unlock();
-
- if (res === PamResult.Error)
- root.state = "error";
- else if (res === PamResult.MaxTries)
- root.state = "max";
- else if (res === PamResult.Failed)
- root.state = "fail";
-
- root.flashMsg();
- stateReset.restart();
- }
- }
-
- PamContext {
- id: fprint
-
- property bool available
- property int tries
- property int errorTries
-
- function checkAvail(): void {
- if (!available || !Config.lock.enableFprint || !root.lock.secure) {
- abort();
- return;
- }
-
- tries = 0;
- errorTries = 0;
- start();
- }
-
- config: "fprint"
- configDirectory: Quickshell.shellDir + "/assets/pam.d"
-
- onCompleted: res => {
- if (!available)
- return;
-
- if (res === PamResult.Success)
- return root.lock.unlock();
-
- if (res === PamResult.Error) {
- root.fprintState = "error";
- errorTries++;
- if (errorTries < 5) {
- abort();
- errorRetry.restart();
- }
- } else if (res === PamResult.MaxTries) {
- // Isn't actually the real max tries as pam only reports completed
- // when max tries is reached.
- tries++;
- if (tries < Config.lock.maxFprintTries) {
- // Restart if not actually real max tries
- root.fprintState = "fail";
- start();
- } else {
- root.fprintState = "max";
- abort();
- }
- }
-
- root.flashMsg();
- fprintStateReset.start();
- }
- }
-
- Process {
- id: availProc
-
- command: ["sh", "-c", "fprintd-list $USER"]
- onExited: code => {
- fprint.available = code === 0;
- fprint.checkAvail();
- }
- }
-
- Timer {
- id: errorRetry
-
- interval: 800
- onTriggered: fprint.start()
- }
-
- Timer {
- id: stateReset
-
- interval: 4000
- onTriggered: {
- if (root.state !== "max")
- root.state = "";
- }
- }
-
- Timer {
- id: fprintStateReset
-
- interval: 4000
- onTriggered: {
- root.fprintState = "";
- fprint.errorTries = 0;
- }
- }
-
- Connections {
- target: root.lock
-
- function onSecureChanged(): void {
- if (root.lock.secure) {
- availProc.running = true;
- root.buffer = "";
- root.state = "";
- root.fprintState = "";
- root.lockMessage = "";
- }
- }
-
- function onUnlock(): void {
- fprint.abort();
- }
- }
-
- Connections {
- target: Config.lock
-
- function onEnableFprintChanged(): void {
- fprint.checkAvail();
- }
- }
-}
diff --git a/modules/notifications/Notification.qml b/modules/notifications/Notification.qml
index b5376cf..8c2d3ec 100644
--- a/modules/notifications/Notification.qml
+++ b/modules/notifications/Notification.qml
@@ -108,7 +108,6 @@ StyledRect {
id: image
active: root.hasImage
- asynchronous: true
anchors.left: parent.left
anchors.top: parent.top
@@ -135,7 +134,6 @@ StyledRect {
id: appIcon
active: root.hasAppIcon || !root.hasImage
- asynchronous: true
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
@@ -152,7 +150,6 @@ StyledRect {
id: icon
active: root.hasAppIcon
- asynchronous: true
anchors.centerIn: parent
@@ -169,7 +166,6 @@ StyledRect {
Loader {
active: !root.hasAppIcon
- asynchronous: true
anchors.centerIn: parent
anchors.horizontalCenterOffset: -Appearance.font.size.large * 0.02
anchors.verticalCenterOffset: Appearance.font.size.large * 0.02
diff --git a/modules/osd/Content.qml b/modules/osd/Content.qml
index 619810f..622f2d7 100644
--- a/modules/osd/Content.qml
+++ b/modules/osd/Content.qml
@@ -112,7 +112,6 @@ Item {
Layout.preferredHeight: shouldBeActive ? Config.osd.sizes.sliderHeight : 0
opacity: shouldBeActive ? 1 : 0
active: opacity > 0
- asynchronous: true
visible: active
Behavior on Layout.preferredHeight {
diff --git a/modules/sidebar/NotifDock.qml b/modules/sidebar/NotifDock.qml
index b915ed9..d039d15 100644
--- a/modules/sidebar/NotifDock.qml
+++ b/modules/sidebar/NotifDock.qml
@@ -87,7 +87,6 @@ Item {
Loader {
anchors.centerIn: parent
- asynchronous: true
active: opacity > 0
opacity: root.notifCount > 0 ? 0 : 1
diff --git a/modules/sidebar/NotifGroup.qml b/modules/sidebar/NotifGroup.qml
index c230a5c..16aac33 100644
--- a/modules/sidebar/NotifGroup.qml
+++ b/modules/sidebar/NotifGroup.qml
@@ -109,7 +109,6 @@ StyledRect {
Loader {
anchors.centerIn: parent
- asynchronous: true
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
}
}
@@ -117,7 +116,6 @@ StyledRect {
Loader {
anchors.right: parent.right
anchors.bottom: parent.bottom
- asynchronous: true
active: root.appIcon && root.image
sourceComponent: StyledRect {
diff --git a/modules/utilities/Content.qml b/modules/utilities/Content.qml
index f6d8d63..770a774 100644
--- a/modules/utilities/Content.qml
+++ b/modules/utilities/Content.qml
@@ -19,8 +19,6 @@ Item {
anchors.fill: parent
spacing: Appearance.spacing.normal
- IdleInhibit {}
-
Toggles {
visibilities: root.visibilities
popouts: root.popouts
diff --git a/modules/utilities/cards/IdleInhibit.qml b/modules/utilities/cards/IdleInhibit.qml
deleted file mode 100644
index 0344e3a..0000000
--- a/modules/utilities/cards/IdleInhibit.qml
+++ /dev/null
@@ -1,125 +0,0 @@
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- Layout.fillWidth: true
- implicitHeight: layout.implicitHeight + (IdleInhibitor.enabled ? activeChip.implicitHeight + activeChip.anchors.topMargin : 0) + Appearance.padding.large * 2
-
- radius: Appearance.rounding.normal
- color: Colours.tPalette.m3surfaceContainer
- clip: true
-
- RowLayout {
- id: layout
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.normal
-
- StyledRect {
- implicitWidth: implicitHeight
- implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2
-
- radius: Appearance.rounding.full
- color: IdleInhibitor.enabled ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
-
- MaterialIcon {
- id: icon
-
- anchors.centerIn: parent
- text: "coffee"
- color: IdleInhibitor.enabled ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
- font.pointSize: Appearance.font.size.large
- }
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 0
-
- StyledText {
- Layout.fillWidth: true
- text: qsTr("Keep Awake")
- font.pointSize: Appearance.font.size.normal
- elide: Text.ElideRight
- }
-
- StyledText {
- Layout.fillWidth: true
- text: IdleInhibitor.enabled ? qsTr("Preventing sleep mode") : qsTr("Normal power management")
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.small
- elide: Text.ElideRight
- }
- }
-
- StyledSwitch {
- checked: IdleInhibitor.enabled
- onToggled: IdleInhibitor.enabled = checked
- }
- }
-
- Loader {
- id: activeChip
-
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.topMargin: Appearance.spacing.larger
- anchors.bottomMargin: IdleInhibitor.enabled ? Appearance.padding.large : -implicitHeight
- anchors.leftMargin: Appearance.padding.large
-
- opacity: IdleInhibitor.enabled ? 1 : 0
- scale: IdleInhibitor.enabled ? 1 : 0.5
-
- Component.onCompleted: active = Qt.binding(() => opacity > 0)
-
- sourceComponent: StyledRect {
- implicitWidth: activeText.implicitWidth + Appearance.padding.normal * 2
- implicitHeight: activeText.implicitHeight + Appearance.padding.small * 2
-
- radius: Appearance.rounding.full
- color: Colours.palette.m3primary
-
- StyledText {
- id: activeText
-
- anchors.centerIn: parent
- text: qsTr("Active since %1").arg(Qt.formatTime(IdleInhibitor.enabledSince, Config.services.useTwelveHourClock ? "hh:mm a" : "hh:mm"))
- color: Colours.palette.m3onPrimary
- font.pointSize: Math.round(Appearance.font.size.small * 0.9)
- }
- }
-
- Behavior on anchors.bottomMargin {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- Behavior on opacity {
- Anim {
- duration: Appearance.anim.durations.small
- }
- }
-
- Behavior on scale {
- Anim {}
- }
- }
-
- Behavior on implicitHeight {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-}
diff --git a/modules/utilities/cards/Toggles.qml b/modules/utilities/cards/Toggles.qml
index e62d3c7..558f2d5 100644
--- a/modules/utilities/cards/Toggles.qml
+++ b/modules/utilities/cards/Toggles.qml
@@ -82,7 +82,7 @@ StyledRect {
icon: "vpn_key"
checked: VPN.connected
enabled: !VPN.connecting
- visible: VPN.enabled
+ visible: Config.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false)
onClicked: VPN.toggle()
}
diff --git a/modules/windowinfo/Buttons.qml b/modules/windowinfo/Buttons.qml
index d7ef095..89acfe6 100644
--- a/modules/windowinfo/Buttons.qml
+++ b/modules/windowinfo/Buttons.qml
@@ -118,7 +118,6 @@ ColumnLayout {
Loader {
active: root.client?.lastIpcObject.floating
- asynchronous: true
Layout.fillWidth: active
Layout.leftMargin: active ? 0 : -parent.spacing
Layout.rightMargin: active ? 0 : -parent.spacing
diff --git a/modules/windowinfo/Preview.qml b/modules/windowinfo/Preview.qml
index d2c5682..4cc0aab 100644
--- a/modules/windowinfo/Preview.qml
+++ b/modules/windowinfo/Preview.qml
@@ -35,7 +35,6 @@ Item {
Loader {
anchors.centerIn: parent
active: !root.client
- asynchronous: true
sourceComponent: ColumnLayout {
spacing: 0
diff --git a/nix/default.nix b/nix/default.nix
index 9f2e4af..0f2675a 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -124,8 +124,6 @@ in
dontStrip = debug;
prePatch = ''
- substituteInPlace assets/pam.d/fprint \
- --replace-fail pam_fprintd.so /run/current-system/sw/lib/security/pam_fprintd.so
substituteInPlace shell.qml \
--replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false'
'';
diff --git a/plugin/src/Caelestia/Internal/CMakeLists.txt b/plugin/src/Caelestia/Internal/CMakeLists.txt
index 0240de8..d0b7548 100644
--- a/plugin/src/Caelestia/Internal/CMakeLists.txt
+++ b/plugin/src/Caelestia/Internal/CMakeLists.txt
@@ -4,7 +4,6 @@ qml_module(caelestia-internal
circularindicatormanager.hpp circularindicatormanager.cpp
hyprdevices.hpp hyprdevices.cpp
hyprextras.hpp hyprextras.cpp
- logindmanager.hpp logindmanager.cpp
LIBRARIES
Qt::Gui
Qt::Quick
diff --git a/plugin/src/Caelestia/Internal/logindmanager.cpp b/plugin/src/Caelestia/Internal/logindmanager.cpp
deleted file mode 100644
index 4194ee1..0000000
--- a/plugin/src/Caelestia/Internal/logindmanager.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#include "logindmanager.hpp"
-
-#include <QtDBus/qdbusconnection.h>
-#include <QtDBus/qdbuserror.h>
-#include <QtDBus/qdbusinterface.h>
-#include <QtDBus/qdbusreply.h>
-
-namespace caelestia::internal {
-
-LogindManager::LogindManager(QObject* parent)
- : QObject(parent) {
- auto bus = QDBusConnection::systemBus();
- if (!bus.isConnected()) {
- qWarning() << "LogindManager::LogindManager: failed to connect to system bus:" << bus.lastError().message();
- return;
- }
-
- bool ok = bus.connect("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
- "PrepareForSleep", this, SLOT(handlePrepareForSleep(bool)));
-
- if (!ok) {
- qWarning() << "LogindManager::LogindManager: failed to connect to PrepareForSleep signal:"
- << bus.lastError().message();
- }
-
- QDBusInterface login1("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", bus);
- const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
- if (!reply.isValid()) {
- qWarning() << "LogindManager::LogindManager: failed to get session path";
- return;
- }
- const auto sessionPath = reply.value().path();
-
- ok = bus.connect("org.freedesktop.login1", sessionPath, "org.freedesktop.login1.Session", "Lock", this,
- SLOT(handleLockRequested()));
-
- if (!ok) {
- qWarning() << "LogindManager::LogindManager: failed to connect to Lock signal:" << bus.lastError().message();
- }
-
- ok = bus.connect("org.freedesktop.login1", sessionPath, "org.freedesktop.login1.Session", "Unlock", this,
- SLOT(handleUnlockRequested()));
-
- if (!ok) {
- qWarning() << "LogindManager::LogindManager: failed to connect to Unlock signal:" << bus.lastError().message();
- }
-}
-
-void LogindManager::handlePrepareForSleep(bool sleep) {
- if (sleep) {
- emit aboutToSleep();
- } else {
- emit resumed();
- }
-}
-
-void LogindManager::handleLockRequested() {
- emit lockRequested();
-}
-
-void LogindManager::handleUnlockRequested() {
- emit unlockRequested();
-}
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/logindmanager.hpp b/plugin/src/Caelestia/Internal/logindmanager.hpp
deleted file mode 100644
index 72a3401..0000000
--- a/plugin/src/Caelestia/Internal/logindmanager.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <qobject.h>
-#include <qqmlintegration.h>
-
-namespace caelestia::internal {
-
-class LogindManager : public QObject {
- Q_OBJECT
- QML_ELEMENT
-
-public:
- explicit LogindManager(QObject* parent = nullptr);
-
-signals:
- void aboutToSleep();
- void resumed();
- void lockRequested();
- void unlockRequested();
-
-private slots:
- void handlePrepareForSleep(bool sleep);
- void handleLockRequested();
- void handleUnlockRequested();
-};
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/cutils.cpp b/plugin/src/Caelestia/cutils.cpp
index 0e1778c..6e3bfa9 100644
--- a/plugin/src/Caelestia/cutils.cpp
+++ b/plugin/src/Caelestia/cutils.cpp
@@ -100,7 +100,7 @@ bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) co
return false;
}
- if (overwrite) {
+ if (overwrite && QFile::exists(target.toLocalFile())) {
if (!QFile::remove(target.toLocalFile())) {
qWarning() << "CUtils::copyFile: overwrite was specified but failed to remove" << target.toLocalFile();
return false;
diff --git a/services/Audio.qml b/services/Audio.qml
index 71ccb86..20d9cc8 100644
--- a/services/Audio.qml
+++ b/services/Audio.qml
@@ -19,15 +19,20 @@ Singleton {
acc.sinks.push(node);
else if (node.audio)
acc.sources.push(node);
+ } else if (node.isStream && node.audio) {
+ // Application streams (output streams)
+ acc.streams.push(node);
}
return acc;
}, {
sources: [],
- sinks: []
+ sinks: [],
+ streams: []
})
readonly property list<PwNode> sinks: nodes.sinks
readonly property list<PwNode> sources: nodes.sources
+ readonly property list<PwNode> streams: nodes.streams
readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource
@@ -79,6 +84,33 @@ Singleton {
Pipewire.preferredDefaultAudioSource = newSource;
}
+ function setStreamVolume(stream: PwNode, newVolume: real): void {
+ if (stream?.ready && stream?.audio) {
+ stream.audio.muted = false;
+ stream.audio.volume = Math.max(0, Math.min(Config.services.maxVolume, newVolume));
+ }
+ }
+
+ function setStreamMuted(stream: PwNode, muted: bool): void {
+ if (stream?.ready && stream?.audio) {
+ stream.audio.muted = muted;
+ }
+ }
+
+ function getStreamVolume(stream: PwNode): real {
+ return stream?.audio?.volume ?? 0;
+ }
+
+ function getStreamMuted(stream: PwNode): bool {
+ return !!stream?.audio?.muted;
+ }
+
+ function getStreamName(stream: PwNode): string {
+ if (!stream) return qsTr("Unknown");
+ // Try application name first, then description, then name
+ return stream.applicationName || stream.description || stream.name || qsTr("Unknown Application");
+ }
+
onSinkChanged: {
if (!sink?.ready)
return;
@@ -109,7 +141,7 @@ Singleton {
}
PwObjectTracker {
- objects: [...root.sinks, ...root.sources]
+ objects: [...root.sinks, ...root.sources, ...root.streams]
}
CavaProvider {
diff --git a/services/IdleInhibitor.qml b/services/IdleInhibitor.qml
deleted file mode 100644
index 29409ab..0000000
--- a/services/IdleInhibitor.qml
+++ /dev/null
@@ -1,56 +0,0 @@
-pragma Singleton
-
-import Quickshell
-import Quickshell.Io
-import Quickshell.Wayland
-
-Singleton {
- id: root
-
- property alias enabled: props.enabled
- readonly property alias enabledSince: props.enabledSince
-
- onEnabledChanged: {
- if (enabled)
- props.enabledSince = new Date();
- }
-
- PersistentProperties {
- id: props
-
- property bool enabled
- property date enabledSince
-
- reloadableId: "idleInhibitor"
- }
-
- IdleInhibitor {
- enabled: props.enabled
- window: PanelWindow {
- implicitWidth: 0
- implicitHeight: 0
- color: "transparent"
- mask: Region {}
- }
- }
-
- IpcHandler {
- target: "idleInhibitor"
-
- function isEnabled(): bool {
- return props.enabled;
- }
-
- function toggle(): void {
- props.enabled = !props.enabled;
- }
-
- function enable(): void {
- props.enabled = true;
- }
-
- function disable(): void {
- props.enabled = false;
- }
- }
-}
diff --git a/services/Notifs.qml b/services/Notifs.qml
index 4a89c7f..2ebc32d 100644
--- a/services/Notifs.qml
+++ b/services/Notifs.qml
@@ -195,15 +195,8 @@ Singleton {
mask: Region {}
Image {
- anchors.fill: parent
- source: Qt.resolvedUrl(notif.image)
- fillMode: Image.PreserveAspectCrop
- cache: false
- asynchronous: true
- opacity: 0
-
- onStatusChanged: {
- if (status !== Image.Ready)
+ function tryCache(): void {
+ if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
@@ -225,6 +218,17 @@ Singleton {
notif.dummyImageLoader.active = false;
});
}
+
+ anchors.fill: parent
+ source: Qt.resolvedUrl(notif.image)
+ fillMode: Image.PreserveAspectCrop
+ cache: false
+ asynchronous: true
+ opacity: 0
+
+ onStatusChanged: tryCache()
+ onWidthChanged: tryCache()
+ onHeightChanged: tryCache()
}
}
}
diff --git a/services/Time.qml b/services/Time.qml
index c4b3913..a07d9ef 100644
--- a/services/Time.qml
+++ b/services/Time.qml
@@ -1,6 +1,8 @@
pragma Singleton
+import qs.config
import Quickshell
+import QtQuick
Singleton {
property alias enabled: clock.enabled
@@ -9,6 +11,12 @@ Singleton {
readonly property int minutes: clock.minutes
readonly property int seconds: clock.seconds
+ readonly property string timeStr: format(Config.services.useTwelveHourClock ? "hh:mm:A" : "hh:mm")
+ readonly property list<string> timeComponents: timeStr.split(":")
+ readonly property string hourStr: timeComponents[0] ?? ""
+ readonly property string minuteStr: timeComponents[1] ?? ""
+ readonly property string amPmStr: timeComponents[2] ?? ""
+
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
diff --git a/services/VPN.qml b/services/VPN.qml
index 412bda4..431c8ec 100644
--- a/services/VPN.qml
+++ b/services/VPN.qml
@@ -12,8 +12,11 @@ Singleton {
property bool connected: false
readonly property bool connecting: connectProc.running || disconnectProc.running
- readonly property bool enabled: Config.utilities.vpn.enabled
- readonly property var providerInput: (Config.utilities.vpn.provider && Config.utilities.vpn.provider.length > 0) ? Config.utilities.vpn.provider[0] : "wireguard"
+ readonly property bool enabled: Config.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false)
+ readonly property var providerInput: {
+ const enabledProvider = Config.utilities.vpn.provider.find(p => typeof p === "object" ? (p.enabled === true) : false);
+ return enabledProvider || "wireguard";
+ }
readonly property bool isCustomProvider: typeof providerInput === "object"
readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput)
readonly property string interfaceName: isCustomProvider ? (providerInput.interface || "") : ""
diff --git a/shell.qml b/shell.qml
index aa23e0a..94ab824 100644
--- a/shell.qml
+++ b/shell.qml
@@ -5,19 +5,12 @@
import "modules"
import "modules/drawers"
import "modules/background"
-import "modules/lock"
import Quickshell
ShellRoot {
Background {}
Drawers {}
- Lock {
- id: lock
- }
Shortcuts {}
BatteryMonitor {}
- IdleMonitors {
- lock: lock
- }
}
diff --git a/utils/Paths.qml b/utils/Paths.qml
index 3b7e84f..8350fb1 100644
--- a/utils/Paths.qml
+++ b/utils/Paths.qml
@@ -19,7 +19,6 @@ Singleton {
readonly property string imagecache: `${cache}/imagecache`
readonly property string notifimagecache: `${imagecache}/notifs`
readonly property string wallpaper: Quickshell.env("CAELESTIA_WALLPAPER") || absolutePath(Config.paths.wallpaper)
- readonly property string face: Quickshell.env("CAELESTIA_FACE") || absolutePath(Config.paths.face)
readonly property string libdir: Quickshell.env("CAELESTIA_LIB_DIR") || "/usr/lib/caelestia"
function toLocalFile(path: url): string {
@@ -28,7 +27,7 @@ Singleton {
}
function absolutePath(path: string): string {
- return toLocalFile(path.replace("~", home));
+ return toLocalFile(path.replace(/~|(\$({?)HOME(}?))+/, home));
}
function shortenHome(path: string): string {
diff --git a/utils/SysInfo.qml b/utils/SysInfo.qml
index ab2699d..19aa4a7 100644
--- a/utils/SysInfo.qml
+++ b/utils/SysInfo.qml
@@ -1,5 +1,7 @@
pragma Singleton
+import qs.config
+import qs.utils
import Quickshell
import Quickshell.Io
import QtQuick
@@ -34,13 +36,27 @@ Singleton {
root.osIdLike = fd("ID_LIKE").split(" ");
const logo = Quickshell.iconPath(fd("LOGO"), true);
- if (logo) {
+ if (Config.general.logo === "caelestia") {
+ root.osLogo = Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`);
+ root.isDefaultLogo = true;
+ } else if (Config.general.logo) {
+ root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo);
+ root.isDefaultLogo = false;
+ } else if (logo) {
root.osLogo = logo;
root.isDefaultLogo = false;
}
}
}
+ Connections {
+ target: Config.general
+
+ function onLogoChanged(): void {
+ osRelease.reload();
+ }
+ }
+
Timer {
running: true
repeat: true