1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
import Quickshell
import QtQuick
import "../.."
import "../qtmaildir"
// deck-it's Windows page: the launcher buttons and the workspaces shown.
Column {
id: col
spacing: 6
readonly property var st: DeckIt.store
function def(section, key) { return col.st.fallback(section, key); }
// The launcher list, in button order.
readonly property var apps: col.st.value("launch", "apps") ?? []
property string query: ""
// Installed apps not yet listed, for the picker. Terminal apps are left
// out, as the Autostart page does: gtk-launch would need a terminal for them.
readonly property var candidates: {
const q = col.query.trim().toLowerCase();
return DesktopEntries.applications.values
.filter(a => !a.noDisplay && !a.runInTerminal && a.execString && !col.apps.includes(a.id)
&& (q === "" || a.name.toLowerCase().includes(q) || a.id.toLowerCase().includes(q)))
.sort((a, b) => a.name.localeCompare(b.name))
.slice(0, 8);
}
function setApps(list) { col.st.put("launch", "apps", list); }
Small { visible: DeckIt.defaultsError !== ""; color: Theme.red; text: DeckIt.defaultsError }
Heading { objectName: "launch/apps"; text: "Launchers" }
Small { text: "Buttons on the Windows tab, in this order. Each runs its app through gtk-launch." }
Repeater {
model: col.apps
delegate: Item {
id: row
required property string modelData
required property int index
readonly property var entry: DesktopEntries.byId(row.modelData)
width: col.width
height: 34
Image {
id: appIcon
anchors { left: parent.left; leftMargin: 8; verticalCenter: parent.verticalCenter }
width: 20; height: 20
sourceSize: Qt.size(20, 20)
source: row.entry ? Quickshell.iconPath(row.entry.icon, true) : ""
}
Text {
anchors { left: appIcon.right; leftMargin: 10; right: up.left; rightMargin: 8; verticalCenter: parent.verticalCenter }
elide: Text.ElideRight
text: row.entry ? row.entry.name + " " + row.modelData : row.modelData + " not installed"
color: row.entry ? Theme.text : Theme.red
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
}
Button {
id: up
anchors { right: del.left; rightMargin: 6; verticalCenter: parent.verticalCenter }
text: "Up"
enabled: row.index > 0
onClicked: {
const a = col.apps.slice();
a.splice(row.index - 1, 0, a.splice(row.index, 1)[0]);
col.setApps(a);
}
}
Button {
id: del
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
text: "Remove"
danger: true
onClicked: col.setApps(col.apps.filter(a => a !== row.modelData))
}
}
}
Rectangle {
width: col.width
height: 34
radius: 8
color: Theme.surface
TextInput {
id: search
activeFocusOnTab: true
anchors { fill: parent; leftMargin: 10; rightMargin: 10 }
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
color: Theme.text
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
onTextEdited: col.query = text
Text {
anchors.verticalCenter: parent.verticalCenter
visible: parent.text === ""
text: "add an app: search installed apps"
color: Theme.subtext
font: parent.font
}
}
}
Repeater {
model: col.candidates
delegate: Rectangle {
id: app
required property var modelData
width: col.width
height: 32
radius: 8
color: appArea.containsMouse ? Qt.alpha(Theme.text, 0.06) : "transparent"
Image {
id: candIcon
anchors { left: parent.left; leftMargin: 8; verticalCenter: parent.verticalCenter }
width: 20; height: 20
sourceSize: Qt.size(20, 20)
source: Quickshell.iconPath(app.modelData.icon, true)
}
Text {
anchors { left: candIcon.right; leftMargin: 10; right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter }
elide: Text.ElideRight
text: app.modelData.name + " " + app.modelData.id
color: Theme.text
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
}
MouseArea {
id: appArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
col.setApps(col.apps.concat([app.modelData.id]));
col.query = "";
search.text = "";
}
}
}
}
Heading { text: "Workspaces" }
Setting { store: col.st; section: "windows"; key: "workspaces"; label: "Workspaces"
placeholder: col.st.placeholder("windows", "workspaces")
hint: "Numbers on the Windows tab, separated by spaces, shown even when empty." }
Setting { store: col.st; section: "windows"; key: "peek"; label: "Show on new window"; kind: "int"; min: 0; max: 60
placeholder: col.st.placeholder("windows", "peek")
hint: "Seconds the tablet shows the Windows tab when a window opens, then goes back unless touched. 0 turns it off." }
}
|