9 Commits

Author SHA1 Message Date
zach ad23da4eda initial commit, primitive loading of QML code outside of shell directory 2026-05-25 13:32:06 +02:00
zach 06ebc4ffbf remove irrelevant settings options plus bugfixes 2026-05-25 11:51:16 +02:00
zach f2f9fa1302 fix opening links on non-uwsm and disable file watcher when shell is installed 2026-05-25 11:01:01 +02:00
zach b4020438f9 Merge pull request 'Tune transparency scale impact on luminance scaling' (#98) from fix/transparency-luminance-scaling into main
Reviewed-on: #98
Reviewed-by: AramJonghu <2+aramjonghu@noreply.git.zach-dev.cc>
2026-05-24 22:37:03 +02:00
zach 184ab20d11 Merge branch 'main' into fix/transparency-luminance-scaling
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 26s
Python / test (pull_request) Successful in 42s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-05-24 22:36:52 +02:00
zach 5097e30a77 fix anchors used in Layouts 2026-05-24 19:33:06 +02:00
zach ef71ae8afd button to install colorscheme and wallpaper to greeter in settings 2026-05-24 19:28:02 +02:00
AramJonghu 6533533936 Merge branch 'main' into fix/transparency-luminance-scaling
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 12s
Python / lint-format (pull_request) Successful in 23s
Python / test (pull_request) Successful in 51s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m47s
2026-05-24 18:53:39 +02:00
zach ba9926af18 Increased floor and decreased ceiling of offset for brightening darker colors in dark mode/darkening brighter colors in light mode
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 13s
Python / lint-format (pull_request) Successful in 28s
Python / test (pull_request) Successful in 43s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
2026-05-24 11:40:44 +02:00
24 changed files with 984 additions and 737 deletions
+8 -1
View File
@@ -31,6 +31,13 @@ if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach()
install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}")
# Disable watching for changes
file(READ shell.qml SHELL_QML)
string(REPLACE "settings.watchFiles: true" "settings.watchFiles: false" SHELL_QML "${SHELL_QML}")
file(WRITE "${CMAKE_BINARY_DIR}/qml/shell.qml" "${SHELL_QML}")
install(FILES "${CMAKE_BINARY_DIR}/qml/shell.qml" DESTINATION "${INSTALL_QSCONFDIR}")
# Greeter
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
endif()
+1
View File
@@ -15,6 +15,7 @@ Text {
color: DynamicColors.palette.m3onSurface
font.family: Appearance.font.family.sans
font.pointSize: Appearance.font.size.normal
linkColor: DynamicColors.palette.m3onPrimaryFixedVariant
renderType: Text.NativeRendering
textFormat: Text.PlainText
+12 -1
View File
@@ -22,6 +22,7 @@ Singleton {
property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias overview: adapter.overview
property alias plugins: adapter.plugins
property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services
@@ -140,7 +141,8 @@ Singleton {
launcher: serializeLauncher(),
colors: serializeColors(),
dock: serializeDock(),
screenshot: serializeScreenshot()
screenshot: serializeScreenshot(),
plugins: serializePlugins()
};
}
@@ -289,6 +291,13 @@ Singleton {
};
}
function serializePlugins(): var {
return {
enabled: plugins.enabled,
entries: plugins.entries
};
}
function serializeScreenshot(): var {
return {
enable_pp: screenshot.enable_pp,
@@ -458,6 +467,8 @@ Singleton {
}
property Overview overview: Overview {
}
property PluginConfig plugins: PluginConfig {
}
property Screenshot screenshot: Screenshot {
}
property Services services: Services {
+3 -2
View File
@@ -30,9 +30,10 @@ Singleton {
readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color {
const luminance = getLuminance(c);
const initLuminance = getLuminance(c);
const luminance = Math.max(initLuminance, 0.001);
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (0.2 + 0.3 * (1 - transparency.base)) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale));
+11
View File
@@ -0,0 +1,11 @@
import Quickshell.Io
JsonObject {
property bool enabled: false
property list<var> entries: [
{
id: "Plugin",
enabled: false
},
]
}
+18
View File
@@ -0,0 +1,18 @@
pragma Singleton
import Quickshell
import ZShell.Models
Singleton {
id: root
property alias plugins: plugins.entries
FileSystemModel {
id: plugins
nameFilters: ["*.qml"]
path: Quickshell.env("HOME") + "/.config/zshell"
recursive: false
}
}
+17
View File
@@ -0,0 +1,17 @@
import Quickshell
import QtQuick
import ZShell.Models
import qs.Config
Repeater {
model: FetchPlugins.plugins
LazyLoader {
required property FileSystemEntry modelData
activeAsync: Config.plugins.entries.some(p => {
return p.id === modelData.baseName && p.enabled;
})
source: modelData.path
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import Quickshell
import "../scripts/fzf.js" as Fzf
import "../scripts/fuzzysort.js" as Fuzzy
import QtQuick
import Quickshell
Singleton {
property var extraOpts: ({})
+4 -1
View File
@@ -136,7 +136,10 @@ CustomRect {
wrapMode: Text.WordWrap
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
if (Config.launcher.uwsm)
Quickshell.execDetached(["app2unit", "-O", "--", link]);
else
Quickshell.execDetached(["xdg-open", link]);
root.visibilities.sidebar = false;
}
}
+8 -6
View File
@@ -100,12 +100,14 @@ Item {
icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.brightness
onMoved: {
if (Config.osd.allMonBrightness) {
root.monitor?.setBrightness(value);
} else {
for (const mon of Brightness.monitors) {
mon.setBrightness(value);
onPressedChanged: {
if (!pressed) {
if (Config.osd.allMonBrightness) {
for (const mon of Brightness.monitors) {
mon.setBrightness(value);
}
} else {
root.monitor?.setBrightness(value);
}
}
}
+6
View File
@@ -116,6 +116,12 @@ Item {
key: "updates"
name: "Updates"
}
ListElement {
icon: "extension"
key: "plugins"
name: "Extensions"
}
}
CustomClippingRect {
+111 -111
View File
@@ -19,8 +19,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Media update interval"
min: 0
name: "Media update interval"
object: Config.dashboard
setting: "mediaUpdateInterval"
step: 50
@@ -30,8 +30,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Resource update interval"
min: 0
name: "Resource update interval"
object: Config.dashboard
setting: "resourceUpdateInterval"
step: 50
@@ -41,8 +41,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Drag threshold"
min: 0
name: "Drag threshold"
object: Config.dashboard
setting: "dragThreshold"
}
@@ -107,112 +107,112 @@ SettingsPage {
}
}
SettingsSection {
sectionId: "Layout Sizes"
SettingsHeader {
name: "Layout Sizes"
}
SettingReadOnly {
name: "Tab indicator height"
value: String(Config.dashboard.sizes.tabIndicatorHeight)
}
Separator {
}
SettingReadOnly {
name: "Tab indicator spacing"
value: String(Config.dashboard.sizes.tabIndicatorSpacing)
}
Separator {
}
SettingReadOnly {
name: "Info width"
value: String(Config.dashboard.sizes.infoWidth)
}
Separator {
}
SettingReadOnly {
name: "Info icon size"
value: String(Config.dashboard.sizes.infoIconSize)
}
Separator {
}
SettingReadOnly {
name: "Date time width"
value: String(Config.dashboard.sizes.dateTimeWidth)
}
Separator {
}
SettingReadOnly {
name: "Media width"
value: String(Config.dashboard.sizes.mediaWidth)
}
Separator {
}
SettingReadOnly {
name: "Media progress sweep"
value: String(Config.dashboard.sizes.mediaProgressSweep)
}
Separator {
}
SettingReadOnly {
name: "Media progress thickness"
value: String(Config.dashboard.sizes.mediaProgressThickness)
}
Separator {
}
SettingReadOnly {
name: "Resource progress thickness"
value: String(Config.dashboard.sizes.resourceProgessThickness)
}
Separator {
}
SettingReadOnly {
name: "Weather width"
value: String(Config.dashboard.sizes.weatherWidth)
}
Separator {
}
SettingReadOnly {
name: "Media cover art size"
value: String(Config.dashboard.sizes.mediaCoverArtSize)
}
Separator {
}
SettingReadOnly {
name: "Media visualiser size"
value: String(Config.dashboard.sizes.mediaVisualiserSize)
}
Separator {
}
SettingReadOnly {
name: "Resource size"
value: String(Config.dashboard.sizes.resourceSize)
}
}
// SettingsSection {
// sectionId: "Layout Sizes"
//
// SettingsHeader {
// name: "Layout Sizes"
// }
//
// SettingReadOnly {
// name: "Tab indicator height"
// value: String(Config.dashboard.sizes.tabIndicatorHeight)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Tab indicator spacing"
// value: String(Config.dashboard.sizes.tabIndicatorSpacing)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Info width"
// value: String(Config.dashboard.sizes.infoWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Info icon size"
// value: String(Config.dashboard.sizes.infoIconSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Date time width"
// value: String(Config.dashboard.sizes.dateTimeWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media width"
// value: String(Config.dashboard.sizes.mediaWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media progress sweep"
// value: String(Config.dashboard.sizes.mediaProgressSweep)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media progress thickness"
// value: String(Config.dashboard.sizes.mediaProgressThickness)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Resource progress thickness"
// value: String(Config.dashboard.sizes.resourceProgessThickness)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Weather width"
// value: String(Config.dashboard.sizes.weatherWidth)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media cover art size"
// value: String(Config.dashboard.sizes.mediaCoverArtSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Media visualiser size"
// value: String(Config.dashboard.sizes.mediaVisualiserSize)
// }
//
// Separator {
// }
//
// SettingReadOnly {
// name: "Resource size"
// value: String(Config.dashboard.sizes.resourceSize)
// }
// }
}
@@ -103,6 +103,18 @@ SettingsPage {
}
}
SettingsSection {
sectionId: "Greeter"
SettingsHeader {
name: "Greeter"
}
SettingsIconButton {
name: "Install wallpaper and color scheme to greeter"
}
}
SettingsSection {
sectionId: "Idle"
@@ -9,6 +9,8 @@ import qs.Modules.Settings.Controls
ColumnLayout {
id: root
property bool shouldBeActive: true
function addTimeoutEntry() {
let list = [...Config.general.idle.timeouts];
@@ -40,8 +42,26 @@ ColumnLayout {
Config.save();
}
Layout.fillWidth: true
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Settings {
name: "Idle Monitors"
@@ -52,6 +72,8 @@ ColumnLayout {
SettingList {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", "");
+18
View File
@@ -0,0 +1,18 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Plugins"
SettingsHeader {
name: "Plugins"
}
SettingBarEntryList {
name: "Enable or disable plugins"
object: Config.plugins
setting: "entries"
}
}
}
+97 -97
View File
@@ -19,8 +19,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Max toasts"
min: 1
name: "Max toasts"
object: Config.utilities
setting: "maxToasts"
}
@@ -29,8 +29,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Panel width"
min: 1
name: "Panel width"
object: Config.utilities.sizes
setting: "width"
}
@@ -39,8 +39,8 @@ SettingsPage {
}
SettingSpinBox {
name: "Toast width"
min: 1
name: "Toast width"
object: Config.utilities.sizes
setting: "toastWidth"
}
@@ -77,100 +77,100 @@ SettingsPage {
setting: "gameModeChanged"
}
Separator {
}
SettingSwitch {
name: "Do not disturb changed"
object: Config.utilities.toasts
setting: "dndChanged"
}
Separator {
}
SettingSwitch {
name: "Audio output changed"
object: Config.utilities.toasts
setting: "audioOutputChanged"
}
Separator {
}
SettingSwitch {
name: "Audio input changed"
object: Config.utilities.toasts
setting: "audioInputChanged"
}
Separator {
}
SettingSwitch {
name: "Caps lock changed"
object: Config.utilities.toasts
setting: "capsLockChanged"
}
Separator {
}
SettingSwitch {
name: "Num lock changed"
object: Config.utilities.toasts
setting: "numLockChanged"
}
Separator {
}
SettingSwitch {
name: "Keyboard layout changed"
object: Config.utilities.toasts
setting: "kbLayoutChanged"
}
Separator {
}
SettingSwitch {
name: "VPN changed"
object: Config.utilities.toasts
setting: "vpnChanged"
}
Separator {
}
SettingSwitch {
name: "Now playing"
object: Config.utilities.toasts
setting: "nowPlaying"
}
// Separator {
// }
//
// SettingSwitch {
// name: "Do not disturb changed"
// object: Config.utilities.toasts
// setting: "dndChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Audio output changed"
// object: Config.utilities.toasts
// setting: "audioOutputChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Audio input changed"
// object: Config.utilities.toasts
// setting: "audioInputChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Caps lock changed"
// object: Config.utilities.toasts
// setting: "capsLockChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Num lock changed"
// object: Config.utilities.toasts
// setting: "numLockChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Keyboard layout changed"
// object: Config.utilities.toasts
// setting: "kbLayoutChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "VPN changed"
// object: Config.utilities.toasts
// setting: "vpnChanged"
// }
//
// Separator {
// }
//
// SettingSwitch {
// name: "Now playing"
// object: Config.utilities.toasts
// setting: "nowPlaying"
// }
}
SettingsSection {
sectionId: "VPN"
SettingsHeader {
name: "VPN"
}
SettingSwitch {
name: "Enable VPN integration"
object: Config.utilities.vpn
setting: "enabled"
}
Separator {
}
SettingStringList {
name: "Provider"
addLabel: qsTr("Add VPN provider")
object: Config.utilities.vpn
setting: "provider"
}
}
// SettingsSection {
// sectionId: "VPN"
//
// SettingsHeader {
// name: "VPN"
// }
//
// SettingSwitch {
// name: "Enable VPN integration"
// object: Config.utilities.vpn
// setting: "enabled"
// }
//
// Separator {
// }
//
// SettingStringList {
// name: "Provider"
// addLabel: qsTr("Add VPN provider")
// object: Config.utilities.vpn
// setting: "provider"
// }
// }
}
+10 -1
View File
@@ -79,6 +79,8 @@ Item {
stack.push(screenshot);
else if (currentCategory === "updates")
stack.push(updates);
else if (currentCategory === "plugins")
stack.push(plugins);
}
target: root
@@ -134,7 +136,7 @@ Item {
anchors.right: parent.right
anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller
color: DynamicColors.tPalette.m3surfaceContainer
color: DynamicColors.tPalette.m3surfaceContainerLowest
radius: Appearance.rounding.normal
StackView {
@@ -245,4 +247,11 @@ Item {
Cat.SystemUpdates {
}
}
Component {
id: plugins
Cat.Plugins {
}
}
}
@@ -127,6 +127,9 @@ ColumnLayout {
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
@@ -207,6 +210,8 @@ ColumnLayout {
StringListEditor {
Layout.fillWidth: true
addLabel: qsTr("Add command argument")
anchors.left: undefined
anchors.right: undefined
values: [...(modelData.command ?? [])]
onListEdited: function (values) {
@@ -215,6 +220,9 @@ ColumnLayout {
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
@@ -233,6 +241,9 @@ ColumnLayout {
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
+120 -108
View File
@@ -6,7 +6,7 @@ import qs.Components
import qs.Config
import qs.Helpers
ColumnLayout {
CustomRect {
id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
@@ -43,10 +43,9 @@ ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
height: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
@@ -77,115 +76,128 @@ ColumnLayout {
}
}
CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
ColumnLayout {
id: layout
Repeater {
model: [...root.object[root.setting]]
Item {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("From")
}
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: 33
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: modelData.from ?? ""
onEditingFinished: root.updateAlias(index, "from", text)
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAlias(index)
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("To")
}
CustomRect {
Layout.fillWidth: true
Layout.preferredHeight: 33
color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.small
CustomTextField {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
anchors.rightMargin: Appearance.padding.normal
text: modelData.to ?? ""
onEditingFinished: root.updateAlias(index, "to", text)
}
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addAlias()
}
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.smaller
CustomText {
Layout.fillWidth: true
text: qsTr("Add alias")
font.pointSize: Appearance.font.size.larger
text: root.name
}
Repeater {
model: [...root.object[root.setting]]
Item {
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("From")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: fromTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.from ?? ""
onEditingFinished: root.updateAlias(index, "from", text)
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAlias(index)
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
text: qsTr("To")
}
CustomRect {
Layout.preferredHeight: 33
Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50)
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomTextField {
id: toTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.to ?? ""
onEditingFinished: root.updateAlias(index, "to", text)
}
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addAlias()
}
CustomText {
Layout.fillWidth: true
text: qsTr("Add alias")
}
}
}
}
@@ -194,6 +194,9 @@ Item {
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
RowLayout {
@@ -225,6 +228,9 @@ Item {
}
Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
}
Item {
@@ -0,0 +1,83 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Paths
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property alias button: iButton
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
IconButton {
id: iButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
icon: "download"
onClicked: {
const lockBg = `${Paths.state}/lockscreen_bg.png`;
const scheme = `${Paths.state}/scheme.json`;
const face = `${Paths.home}/.face`;
const destination = "/etc/zshell-greeter/images";
Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]);
}
}
}
}
+302 -311
View File
@@ -7,473 +7,464 @@
namespace ZShell::models {
FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent)
: QObject(parent)
, m_fileInfo(path)
, m_path(path)
, m_relativePath(relativePath)
, m_isImageInitialised(false)
, m_mimeTypeInitialised(false) {}
: QObject(parent)
, m_fileInfo(path)
, m_path(path)
, m_relativePath(relativePath)
, m_isImageInitialised(false)
, m_mimeTypeInitialised(false) {
}
QString FileSystemEntry::path() const {
return m_path;
return m_path;
};
QString FileSystemEntry::relativePath() const {
return m_relativePath;
return m_relativePath;
};
QString FileSystemEntry::name() const {
return m_fileInfo.fileName();
return m_fileInfo.fileName();
};
QString FileSystemEntry::baseName() const {
return m_fileInfo.baseName();
return m_fileInfo.baseName();
};
QString FileSystemEntry::parentDir() const {
return m_fileInfo.absolutePath();
return m_fileInfo.absolutePath();
};
QString FileSystemEntry::suffix() const {
return m_fileInfo.completeSuffix();
return m_fileInfo.completeSuffix();
};
qint64 FileSystemEntry::size() const {
return m_fileInfo.size();
return m_fileInfo.size();
};
bool FileSystemEntry::isDir() const {
return m_fileInfo.isDir();
return m_fileInfo.isDir();
};
bool FileSystemEntry::isImage() const {
if (!m_isImageInitialised) {
QImageReader reader(m_path);
m_isImage = reader.canRead();
m_isImageInitialised = true;
}
return m_isImage;
if (!m_isImageInitialised) {
QImageReader reader(m_path);
m_isImage = reader.canRead();
m_isImageInitialised = true;
}
return m_isImage;
}
QString FileSystemEntry::mimeType() const {
if (!m_mimeTypeInitialised) {
const QMimeDatabase db;
m_mimeType = db.mimeTypeForFile(m_path).name();
m_mimeTypeInitialised = true;
}
return m_mimeType;
if (!m_mimeTypeInitialised) {
static const QMimeDatabase s_db;
m_mimeType = s_db.mimeTypeForFile(m_path).name();
m_mimeTypeInitialised = true;
}
return m_mimeType;
}
void FileSystemEntry::updateRelativePath(const QDir& dir) {
const auto relPath = dir.relativeFilePath(m_path);
if (m_relativePath != relPath) {
m_relativePath = relPath;
emit relativePathChanged();
}
const auto relPath = dir.relativeFilePath(m_path);
if (m_relativePath != relPath) {
m_relativePath = relPath;
emit relativePathChanged();
}
}
FileSystemModel::FileSystemModel(QObject* parent)
: QAbstractListModel(parent)
, m_recursive(false)
, m_watchChanges(true)
, m_showHidden(false)
, m_filter(NoFilter) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
: QAbstractListModel(parent)
, m_recursive(false)
, m_watchChanges(true)
, m_showHidden(false)
, m_filter(NoFilter) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
}
int FileSystemModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) {
return 0;
}
return static_cast<int>(m_entries.size());
if (parent != QModelIndex()) {
return 0;
}
return static_cast<int>(m_entries.size());
}
QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
return QVariant();
}
return QVariant::fromValue(m_entries.at(index.row()));
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
return QVariant();
}
return QVariant::fromValue(m_entries.at(index.row()));
}
QHash<int, QByteArray> FileSystemModel::roleNames() const {
return { { Qt::UserRole, "modelData" } };
return { { Qt::UserRole, "modelData" } };
}
QString FileSystemModel::path() const {
return m_path;
return m_path;
}
void FileSystemModel::setPath(const QString& path) {
if (m_path == path) {
return;
}
if (m_path == path) {
return;
}
m_path = path;
emit pathChanged();
m_path = path;
emit pathChanged();
m_dir.setPath(m_path);
m_dir.setPath(m_path);
for (const auto& entry : std::as_const(m_entries)) {
entry->updateRelativePath(m_dir);
}
for (const auto& entry : std::as_const(m_entries)) {
entry->updateRelativePath(m_dir);
}
update();
update();
}
bool FileSystemModel::recursive() const {
return m_recursive;
return m_recursive;
}
void FileSystemModel::setRecursive(bool recursive) {
if (m_recursive == recursive) {
return;
}
if (m_recursive == recursive) {
return;
}
m_recursive = recursive;
emit recursiveChanged();
m_recursive = recursive;
emit recursiveChanged();
update();
update();
}
bool FileSystemModel::watchChanges() const {
return m_watchChanges;
return m_watchChanges;
}
void FileSystemModel::setWatchChanges(bool watchChanges) {
if (m_watchChanges == watchChanges) {
return;
}
if (m_watchChanges == watchChanges) {
return;
}
m_watchChanges = watchChanges;
emit watchChangesChanged();
m_watchChanges = watchChanges;
emit watchChangesChanged();
update();
update();
}
bool FileSystemModel::showHidden() const {
return m_showHidden;
return m_showHidden;
}
void FileSystemModel::setShowHidden(bool showHidden) {
if (m_showHidden == showHidden) {
return;
}
if (m_showHidden == showHidden) {
return;
}
m_showHidden = showHidden;
emit showHiddenChanged();
m_showHidden = showHidden;
emit showHiddenChanged();
update();
update();
}
bool FileSystemModel::sortReverse() const {
return m_sortReverse;
return m_sortReverse;
}
void FileSystemModel::setSortReverse(bool sortReverse) {
if (m_sortReverse == sortReverse) {
return;
}
if (m_sortReverse == sortReverse) {
return;
}
m_sortReverse = sortReverse;
emit sortReverseChanged();
m_sortReverse = sortReverse;
emit sortReverseChanged();
update();
update();
}
FileSystemModel::Filter FileSystemModel::filter() const {
return m_filter;
return m_filter;
}
void FileSystemModel::setFilter(Filter filter) {
if (m_filter == filter) {
return;
}
if (m_filter == filter) {
return;
}
m_filter = filter;
emit filterChanged();
m_filter = filter;
emit filterChanged();
update();
update();
}
QStringList FileSystemModel::nameFilters() const {
return m_nameFilters;
return m_nameFilters;
}
void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
if (m_nameFilters == nameFilters) {
return;
}
if (m_nameFilters == nameFilters) {
return;
}
m_nameFilters = nameFilters;
emit nameFiltersChanged();
m_nameFilters = nameFilters;
emit nameFiltersChanged();
update();
update();
}
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
}
void FileSystemModel::watchDirIfRecursive(const QString& path) {
if (m_recursive && m_watchChanges) {
const auto currentDir = m_dir;
const bool showHidden = m_showHidden;
const auto future = QtConcurrent::run([showHidden, path]() {
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
if (showHidden) {
filters |= QDir::Hidden;
}
if (m_recursive && m_watchChanges) {
const auto currentDir = m_dir;
const bool showHidden = m_showHidden;
auto future = QtConcurrent::run([showHidden, path]() {
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
if (showHidden) {
filters |= QDir::Hidden;
}
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
QStringList dirs;
while (iter.hasNext()) {
dirs << iter.next();
}
return dirs;
});
const auto watcher = new QFutureWatcher<QStringList>(this);
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() {
const auto paths = watcher->result();
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
// Ignore if dir or showHidden has changed
m_watcher.addPaths(paths);
}
watcher->deleteLater();
});
watcher->setFuture(future);
}
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
QStringList dirs;
while (iter.hasNext()) {
dirs << iter.next();
}
return dirs;
});
future.then(this, [currentDir, showHidden, this](const QStringList& paths) {
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
// Ignore if dir or showHidden has changed
m_watcher.addPaths(paths);
}
});
}
}
void FileSystemModel::update() {
updateWatcher();
updateEntries();
updateWatcher();
updateEntries();
}
void FileSystemModel::updateWatcher() {
if (!m_watcher.directories().isEmpty()) {
m_watcher.removePaths(m_watcher.directories());
}
if (!m_watcher.directories().isEmpty()) {
m_watcher.removePaths(m_watcher.directories());
}
if (!m_watchChanges || m_path.isEmpty()) {
return;
}
if (!m_watchChanges || m_path.isEmpty()) {
return;
}
m_watcher.addPath(m_path);
watchDirIfRecursive(m_path);
m_watcher.addPath(m_path);
watchDirIfRecursive(m_path);
}
void FileSystemModel::updateEntries() {
if (m_path.isEmpty()) {
if (!m_entries.isEmpty()) {
beginResetModel();
qDeleteAll(m_entries);
m_entries.clear();
endResetModel();
emit entriesChanged();
}
if (m_path.isEmpty()) {
if (!m_entries.isEmpty()) {
beginResetModel();
qDeleteAll(m_entries);
m_entries.clear();
endResetModel();
emit entriesChanged();
}
return;
}
return;
}
for (auto& future : m_futures) {
future.cancel();
}
m_futures.clear();
for (auto& future : m_futures) {
future.cancel();
}
m_futures.clear();
updateEntriesForDir(m_path);
updateEntriesForDir(m_path);
}
void FileSystemModel::updateEntriesForDir(const QString& dir) {
const auto recursive = m_recursive;
const auto showHidden = m_showHidden;
const auto filter = m_filter;
const auto nameFilters = m_nameFilters;
const auto recursive = m_recursive;
const auto showHidden = m_showHidden;
const auto filter = m_filter;
const auto nameFilters = m_nameFilters;
QSet<QString> oldPaths;
for (const auto& entry : std::as_const(m_entries)) {
oldPaths << entry->path();
}
QSet<QString> oldPaths;
for (const auto& entry : std::as_const(m_entries)) {
oldPaths << entry->path();
}
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString> > >& promise) {
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
std::optional<QDirIterator> iter;
std::optional<QDirIterator> iter;
if (filter == Images) {
QStringList extraNameFilters = nameFilters;
const auto formats = QImageReader::supportedImageFormats();
for (const auto& format : formats) {
extraNameFilters << "*." + format;
}
if (filter == Images) {
QStringList extraNameFilters = nameFilters;
const auto formats = QImageReader::supportedImageFormats();
for (const auto& format : formats) {
extraNameFilters << "*." + format;
}
QDir::Filters filters = QDir::Files;
if (showHidden) {
filters |= QDir::Hidden;
}
QDir::Filters filters = QDir::Files;
if (showHidden) {
filters |= QDir::Hidden;
}
iter.emplace(dir, extraNameFilters, filters, flags);
} else {
QDir::Filters filters;
iter.emplace(dir, extraNameFilters, filters, flags);
} else {
QDir::Filters filters;
if (filter == Files) {
filters = QDir::Files;
} else if (filter == Dirs) {
filters = QDir::Dirs | QDir::NoDotAndDotDot;
} else {
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
}
if (filter == Files) {
filters = QDir::Files;
} else if (filter == Dirs) {
filters = QDir::Dirs | QDir::NoDotAndDotDot;
} else {
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
}
if (showHidden) {
filters |= QDir::Hidden;
}
if (showHidden) {
filters |= QDir::Hidden;
}
if (nameFilters.isEmpty()) {
iter.emplace(dir, filters, flags);
} else {
iter.emplace(dir, nameFilters, filters, flags);
}
}
if (nameFilters.isEmpty()) {
iter.emplace(dir, filters, flags);
} else {
iter.emplace(dir, nameFilters, filters, flags);
}
}
QSet<QString> newPaths;
while (iter->hasNext()) {
if (promise.isCanceled()) {
return;
}
QSet<QString> newPaths;
while (iter->hasNext()) {
if (promise.isCanceled()) {
return;
}
QString path = iter->next();
QString path = iter->next();
if (filter == Images) {
QImageReader reader(path);
if (!reader.canRead()) {
continue;
}
}
if (filter == Images) {
QImageReader reader(path);
if (!reader.canRead()) {
continue;
}
}
newPaths.insert(path);
}
newPaths.insert(path);
}
if (promise.isCanceled() || newPaths == oldPaths) {
return;
}
if (promise.isCanceled()) {
return;
}
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
});
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
});
if (m_futures.contains(dir)) {
m_futures[dir].cancel();
}
m_futures.insert(dir, future);
if (m_futures.contains(dir)) {
m_futures[dir].cancel();
}
m_futures.insert(dir, future);
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() {
m_futures.remove(dir);
if (!watcher->future().isResultReadyAt(0)) {
watcher->deleteLater();
return;
}
const auto result = watcher->result();
applyChanges(result.first, result.second);
watcher->deleteLater();
});
watcher->setFuture(future);
future
.then(this,
[dir, this](QPair<QSet<QString>, QSet<QString> > result) {
m_futures.remove(dir);
if (!result.first.isEmpty() || !result.second.isEmpty()) {
applyChanges(result.first, result.second);
}
})
.onCanceled(this, [dir, this]() {
m_futures.remove(dir);
});
}
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
QList<int> removedIndices;
for (int i = 0; i < m_entries.size(); ++i) {
if (removedPaths.contains(m_entries[i]->path())) {
removedIndices << i;
}
}
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
QList<int> removedIndices;
for (int i = 0; i < m_entries.size(); ++i) {
if (removedPaths.contains(m_entries[i]->path())) {
removedIndices << i;
}
}
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
// Batch remove old entries
int start = -1;
int end = -1;
for (int idx : std::as_const(removedIndices)) {
if (start == -1) {
start = idx;
end = idx;
} else if (idx == end - 1) {
end = idx;
} else {
beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater();
}
endRemoveRows();
// Batch remove old entries
int start = -1;
int end = -1;
for (int idx : std::as_const(removedIndices)) {
if (start == -1) {
start = idx;
end = idx;
} else if (idx == end - 1) {
end = idx;
} else {
beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater();
}
endRemoveRows();
start = idx;
end = idx;
}
}
if (start != -1) {
beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater();
}
endRemoveRows();
}
start = idx;
end = idx;
}
}
if (start != -1) {
beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater();
}
endRemoveRows();
}
// Create new entries
QList<FileSystemEntry*> newEntries;
for (const auto& path : addedPaths) {
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
}
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
// Create new entries
QList<FileSystemEntry*> newEntries;
for (const auto& path : addedPaths) {
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
}
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
// Batch insert new entries
int insertStart = -1;
QList<FileSystemEntry*> batchItems;
for (const auto& entry : std::as_const(newEntries)) {
const auto it = std::lower_bound(
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
const auto row = static_cast<int>(it - m_entries.begin());
// Batch insert new entries
int insertStart = -1;
QList<FileSystemEntry*> batchItems;
for (const auto& entry : std::as_const(newEntries)) {
const auto it = std::lower_bound(
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b);
});
const auto row = static_cast<int>(it - m_entries.begin());
if (insertStart == -1) {
insertStart = row;
batchItems << entry;
} else if (row == insertStart + batchItems.size()) {
batchItems << entry;
} else {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
if (insertStart == -1) {
insertStart = row;
batchItems << entry;
} else if (row == insertStart + batchItems.size()) {
batchItems << entry;
} else {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
insertStart = row;
batchItems.clear();
batchItems << entry;
}
}
if (!batchItems.isEmpty()) {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
}
insertStart = row;
batchItems.clear();
batchItems << entry;
}
}
if (!batchItems.isEmpty()) {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]);
}
endInsertRows();
}
emit entriesChanged();
emit entriesChanged();
}
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
if (a->isDir() != b->isDir()) {
return m_sortReverse ^ a->isDir();
}
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
return m_sortReverse ? cmp > 0 : cmp < 0;
if (a->isDir() != b->isDir()) {
return m_sortReverse ^ a->isDir();
}
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
return m_sortReverse ? cmp > 0 : cmp < 0;
}
} // namespace ZShell::models
+95 -95
View File
@@ -13,136 +13,136 @@
namespace ZShell::models {
class FileSystemEntry : public QObject {
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT)
Q_PROPERTY(bool isDir READ isDir CONSTANT)
Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT)
Q_PROPERTY(bool isDir READ isDir CONSTANT)
Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
[[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const;
[[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const;
[[nodiscard]] bool isDir() const;
[[nodiscard]] bool isImage() const;
[[nodiscard]] QString mimeType() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const;
[[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const;
[[nodiscard]] bool isDir() const;
[[nodiscard]] bool isImage() const;
[[nodiscard]] QString mimeType() const;
void updateRelativePath(const QDir& dir);
void updateRelativePath(const QDir& dir);
signals:
void relativePathChanged();
void relativePathChanged();
private:
const QFileInfo m_fileInfo;
const QFileInfo m_fileInfo;
const QString m_path;
QString m_relativePath;
const QString m_path;
QString m_relativePath;
mutable bool m_isImage;
mutable bool m_isImageInitialised;
mutable bool m_isImage;
mutable bool m_isImageInitialised;
mutable QString m_mimeType;
mutable bool m_mimeTypeInitialised;
mutable QString m_mimeType;
mutable bool m_mimeTypeInitialised;
};
class FileSystemModel : public QAbstractListModel {
Q_OBJECT
QML_ELEMENT
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
public:
enum Filter {
NoFilter,
Images,
Files,
Dirs
};
Q_ENUM(Filter)
enum Filter {
NoFilter,
Images,
Files,
Dirs
};
Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr);
explicit FileSystemModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QString path() const;
void setPath(const QString& path);
[[nodiscard]] QString path() const;
void setPath(const QString& path);
[[nodiscard]] bool recursive() const;
void setRecursive(bool recursive);
[[nodiscard]] bool recursive() const;
void setRecursive(bool recursive);
[[nodiscard]] bool watchChanges() const;
void setWatchChanges(bool watchChanges);
[[nodiscard]] bool watchChanges() const;
void setWatchChanges(bool watchChanges);
[[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden);
[[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden);
[[nodiscard]] bool sortReverse() const;
void setSortReverse(bool sortReverse);
[[nodiscard]] bool sortReverse() const;
void setSortReverse(bool sortReverse);
[[nodiscard]] Filter filter() const;
void setFilter(Filter filter);
[[nodiscard]] Filter filter() const;
void setFilter(Filter filter);
[[nodiscard]] QStringList nameFilters() const;
void setNameFilters(const QStringList& nameFilters);
[[nodiscard]] QStringList nameFilters() const;
void setNameFilters(const QStringList& nameFilters);
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
signals:
void pathChanged();
void recursiveChanged();
void watchChangesChanged();
void showHiddenChanged();
void sortReverseChanged();
void filterChanged();
void nameFiltersChanged();
void entriesChanged();
void pathChanged();
void recursiveChanged();
void watchChangesChanged();
void showHiddenChanged();
void sortReverseChanged();
void filterChanged();
void nameFiltersChanged();
void entriesChanged();
private:
QDir m_dir;
QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_entries;
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
QDir m_dir;
QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_entries;
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
QString m_path;
bool m_recursive;
bool m_watchChanges;
bool m_showHidden;
bool m_sortReverse;
Filter m_filter;
QStringList m_nameFilters;
QString m_path;
bool m_recursive;
bool m_watchChanges;
bool m_showHidden;
bool m_sortReverse = false;
Filter m_filter;
QStringList m_nameFilters;
void watchDirIfRecursive(const QString& path);
void update();
void updateWatcher();
void updateEntries();
void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
void watchDirIfRecursive(const QString& path);
void update();
void updateWatcher();
void updateEntries();
void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
};
} // namespace ZShell::models
+7 -1
View File
@@ -1,11 +1,12 @@
//@ pragma UseQApplication
//@ pragma Env QSG_RENDER_LOOP=threaded
// @ pragma Env QSG_RHI_BACKEND=vulkan
//@ pragma Env QSG_RHI_BACKEND=vulkan
//@ pragma Env QSG_NO_VSYNC=1
//@ pragma Env QS_NO_RELOAD_POPUP=1
//@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round
//@ pragma DropExpensiveFonts
import Quickshell
import qs.Extensions
import qs.Modules
import qs.Modules.Wallpaper
import qs.Modules.Lock
@@ -14,6 +15,8 @@ import qs.Helpers
import qs.Modules.Polkit
ShellRoot {
settings.watchFiles: true
Windows {
}
@@ -36,4 +39,7 @@ ShellRoot {
Polkit {
}
LoadExtensions {
}
}