1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

feat(settings): Added Settings Tab Autostart App (XDG Autostart) (#2535)

* feat(Autostart): add Autostart tab and application selection popup

* fix(AutoStartTab): update systemdUserDir property to use XDG_CONFIG_HOME

* fix(AutoStartTab): update autostartDir and systemdUserDir to use StandardPaths for config home

* refactor(AutoStartTab): use FileView & FolderListModel

* refactor(AutoStartTab): implement systemd override generation for autostart applications using FileView

* feat(AutoStartTab): add systemd check to determine environment and update tray icon visibility

* feat(SettingsSidebar, AutoStartTab, DesktopService): add autostart functionality and systemd checks

* feat(AutoStartTab): add hidden property support for desktop entries and toggle functionality

* feat(AutoStartTab): add initialize autostart directory and add toast if writer failed

* add(AutoStartTab): logging for scoped log tracking

---------
This commit is contained in:
arfan
2026-06-03 09:52:58 +07:00
committed by GitHub
parent 1ee42506b6
commit d2905072c0
5 changed files with 1144 additions and 0 deletions
@@ -0,0 +1,332 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
FloatingWindow {
id: root
property bool disablePopupTransparency: true
property string searchQuery: ""
property var filteredApps: []
property int selectedIndex: -1
property bool keyboardNavigationActive: false
property var appsModel: []
property var parentModal: null
parentWindow: parentModal
signal appSelected(string appId)
objectName: "appBrowserPopup"
title: I18n.tr("Select Application")
minimumSize: Qt.size(400, 350)
implicitWidth: 500
implicitHeight: 550
color: "transparent"
visible: false
WindowBlur {
targetWindow: root
blurX: 0
blurY: 0
blurWidth: root.visible ? root.width : 0
blurHeight: root.visible ? root.height : 0
blurRadius: Theme.cornerRadius
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, 0.95)
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
}
FocusScope {
anchors.fill: parent
focus: true
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Escape:
root.hide();
event.accepted = true;
return;
case Qt.Key_Down:
root.selectNext();
event.accepted = true;
return;
case Qt.Key_Up:
root.selectPrevious();
event.accepted = true;
return;
case Qt.Key_Return:
case Qt.Key_Enter:
if (root.keyboardNavigationActive) {
root.selectApp();
} else if (root.filteredApps.length > 0) {
root.selectAppByIndex(0);
}
event.accepted = true;
return;
}
}
Column {
anchors.fill: parent
spacing: 0
Item {
width: parent.width
height: 48
MouseArea {
anchors.fill: parent
onPressed: windowControls.tryStartMove()
}
Rectangle {
anchors.fill: parent
color: Theme.withAlpha(Theme.surfaceContainerHigh, 0.5)
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingL
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "add_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Select Application")
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hide()
}
}
}
Item {
width: parent.width
height: parent.height - 48
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
DankTextField {
id: searchField
width: parent.width
height: 48
cornerRadius: Theme.cornerRadius
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, 0.8)
normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary
leftIconName: "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
placeholderText: I18n.tr("Search applications...")
text: root.searchQuery
onTextEdited: {
root.searchQuery = text;
root.updateFilteredApps();
}
}
DankListView {
id: appList
width: parent.width
height: parent.height - searchField.height - Theme.spacingM
spacing: Theme.spacingS
model: root.filteredApps
clip: true
delegate: Rectangle {
width: appList.width
height: 60
radius: Theme.cornerRadius
required property int index
required property var modelData
readonly property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
color: isSelected ? Theme.withAlpha(Theme.primary, 0.16) : appArea.containsMouse ? Theme.withAlpha(Theme.primary, 0.08) : Theme.withAlpha(Theme.surfaceVariant, 0.3)
border.color: isSelected ? Theme.primary : Theme.outlineMedium
border.width: isSelected ? 2 : Theme.layerOutlineWidth
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Image {
width: 28
height: 28
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
sourceSize.width: 28
sourceSize.height: 28
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
onStatusChanged: {
if (status === Image.Error)
source = "image://icon/application-x-executable";
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - 28 - Theme.spacingM * 3 - 24
StyledText {
text: modelData.name || modelData.id || ""
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.comment || modelData.genericName || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
elide: Text.ElideRight
width: parent.width
}
}
DankIcon {
name: "add"
size: Theme.iconSize - 4
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: appArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const appId = modelData.id || modelData.execString || "";
root.appSelected(appId);
root.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
FloatingWindowControls {
id: windowControls
targetWindow: root
}
}
function updateFilteredApps() {
const allApps = root.appsModel || [];
var filtered = [];
if (!searchQuery || searchQuery.length === 0) {
filtered = allApps.slice();
} else {
var query = searchQuery.toLowerCase();
for (var i = 0; i < allApps.length; i++) {
var app = allApps[i];
var name = (app.name || "").toLowerCase();
var id = (app.id || "").toLowerCase();
var comment = (app.comment || app.genericName || "").toLowerCase();
if (name.indexOf(query) !== -1 || id.indexOf(query) !== -1 || comment.indexOf(query) !== -1)
filtered.push(app);
}
}
filteredApps = filtered;
selectedIndex = -1;
keyboardNavigationActive = false;
}
function selectNext() {
if (filteredApps.length === 0) return;
keyboardNavigationActive = true;
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1);
}
function selectPrevious() {
if (filteredApps.length === 0) return;
keyboardNavigationActive = true;
selectedIndex = Math.max(selectedIndex - 1, -1);
if (selectedIndex === -1) keyboardNavigationActive = false;
}
function selectApp() {
if (selectedIndex < 0 || selectedIndex >= filteredApps.length) return;
selectAppByIndex(selectedIndex);
}
function selectAppByIndex(idx) {
const app = filteredApps[idx];
if (!app) return;
root.appSelected(app.id || app.execString || "");
hide();
}
function show() {
updateFilteredApps();
visible = true;
Qt.callLater(() => searchField.forceActiveFocus());
}
function hide() {
visible = false;
searchQuery = "";
filteredApps = [];
selectedIndex = -1;
keyboardNavigationActive = false;
}
onVisibleChanged: {
if (!visible) {
searchQuery = "";
filteredApps = [];
selectedIndex = -1;
keyboardNavigationActive = false;
}
}
}