aboutsummaryrefslogtreecommitdiff
path: root/quickshell
diff options
context:
space:
mode:
authorAargh Rai <aargh.rai+git@gmail.com>2025-12-08 18:38:07 +0530
committerAargh Rai <aargh.rai+git@gmail.com>2025-12-08 18:38:07 +0530
commitc8ed34b938b589a682287ea98f19b341abddba13 (patch)
tree1bdbaee6c46ee4272d50fdbb49c0c6ee396b3da0 /quickshell
parentbb1549a29921abdd75950b264333a80627e5b158 (diff)
added calendar and fixed app launcher bugs
Diffstat (limited to 'quickshell')
-rw-r--r--quickshell/AppLauncher.qml402
-rw-r--r--quickshell/Calendar.qml143
-rw-r--r--quickshell/shell.qml66
3 files changed, 373 insertions, 238 deletions
diff --git a/quickshell/AppLauncher.qml b/quickshell/AppLauncher.qml
index a1b5ab2..91deb1e 100644
--- a/quickshell/AppLauncher.qml
+++ b/quickshell/AppLauncher.qml
@@ -4,245 +4,207 @@ import Quickshell
import Quickshell.Io
Rectangle {
- id: appLauncher
-
- width: 400
- height: 600
- color: shellRoot.black
- radius: 16
- border.width: 3
- border.color: shellRoot.blue
- antialiasing: true
-
- property bool isVisible: false
- property int selectedIndex: -1
- property int hoverIndex: -1
-
- signal requestClose()
-
- focus: true
-
- Keys.onEscapePressed: requestClose()
-
- Keys.onUpPressed: {
- hoverIndex = -1
- if (selectedIndex === -1) {
- selectedIndex = appListModel.count - 1
- } else if (selectedIndex > 0) {
- selectedIndex--
- }
- listView.positionViewAtIndex(selectedIndex, ListView.Contain)
+ id: appLauncher
+
+ width: 400
+ height: 600
+ color: shellRoot.black
+ antialiasing: true
+
+ property bool isVisible: false
+ property int hoverIndex: 0
+ property bool cleared: true
+
+ signal requestClose()
+
+ focus: true
+
+ Keys.onEscapePressed: requestClose()
+
+ Keys.onUpPressed: {
+ hoverIndex--
+ if (hoverIndex < 0) hoverIndex += appListModel.count
+ listView.positionViewAtIndex(hoverIndex, ListView.Contain)
+ }
+
+ Keys.onDownPressed: {
+ hoverIndex = (hoverIndex + 1) % appListModel.count
+ listView.positionViewAtIndex(hoverIndex, ListView.Contain)
+ }
+
+ Keys.onReturnPressed: {
+ if (hoverIndex >= 0 && hoverIndex < appListModel.count) {
+ launchApp(appListModel.get(hoverIndex).appCommand)
}
-
- Keys.onDownPressed: {
- hoverIndex = -1
- if (selectedIndex === -1) {
- selectedIndex = 0
- } else if (selectedIndex < appListModel.count - 1) {
- selectedIndex++
- }
- listView.positionViewAtIndex(selectedIndex, ListView.Contain)
+ }
+
+ Keys.onEnterPressed: {
+ if (hoverIndex >= 0 && hoverIndex < appListModel.count) {
+ launchApp(appListModel.get(hoverIndex).appCommand)
}
+ }
+
+ onIsVisibleChanged: {
+ if (!isVisible) return;
+ hoverIndex = -1
+ appLauncher.forceActiveFocus()
+ loadApps()
+ }
+
+ Process {
+ id: appLoader
+ running: false
+ command: ["sh", "-c", "~/.config/quickshell/scripts/list-apps.sh"]
- Keys.onReturnPressed: {
- if (selectedIndex >= 0 && selectedIndex < appListModel.count) {
- launchApp(appListModel.get(selectedIndex).appCommand)
+ stdout: SplitParser {
+ onRead: data => {
+ if (!cleared) {
+ appListModel.clear()
+ cleared = true;
}
- }
-
- Keys.onEnterPressed: {
- if (selectedIndex >= 0 && selectedIndex < appListModel.count) {
- launchApp(appListModel.get(selectedIndex).appCommand)
+
+ const lines = data.split('\n')
+ for (const line of lines) {
+ if (line.trim().length === 0) continue
+
+ const parts = line.split('|')
+ if (parts.length < 4) continue;
+ appListModel.append({
+ appName: parts[0],
+ appDescription: parts[1],
+ appIcon: parts[2],
+ appCommand: parts[3]
+ })
}
+ }
}
- // Reset selection when widget becomes visible
- onIsVisibleChanged: {
- if (isVisible) {
- selectedIndex = -1 // Start with nothing selected
- hoverIndex = -1 // Reset hover on open
- appLauncher.forceActiveFocus()
- // Reload apps every time the launcher opens
- loadApps()
- }
+ onRunningChanged: {
+ if (running) return;
+ appLoader.running = false
}
+ }
+
+ function loadApps() {
+ cleared = false;
+ appLoader.running = true
+ }
+
+ Component.onCompleted: {
+ loadApps()
+ }
+
+ Column {
+ anchors.fill: parent
+ anchors.margins: 12
+ spacing: 8
- // Process to load apps
- Process {
- id: appLoader
- running: false // Don't auto-start, we'll trigger it manually
- command: ["sh", "-c", "~/.config/quickshell/scripts/list-apps.sh"]
+ Rectangle {
+ width: parent.width
+ height: 36
+ color: "transparent"
+
+ Text {
+ anchors.centerIn: parent
+ text: "Applications"
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 20
+ font.weight: Font.Medium
+ color: shellRoot.white
+ }
+
+ Rectangle {
+ width: 28
+ height: 28
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ radius: 6
+ color: closeMouseArea.containsMouse ? shellRoot.red : "transparent"
- stdout: SplitParser {
- onRead: data => {
- const lines = data.split('\n')
- for (const line of lines) {
- if (line.trim().length === 0) continue
-
- const parts = line.split('|')
- if (parts.length >= 4) {
- appListModel.append({
- appName: parts[0],
- appDescription: parts[1],
- appIcon: parts[2],
- appCommand: parts[3]
- })
- }
- }
- console.log("Loaded", appListModel.count, "applications")
- }
+ Text {
+ anchors.centerIn: parent
+ text: "✕"
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 16
+ font.weight: Font.Bold
+ color: closeMouseArea.containsMouse ? shellRoot.white : shellRoot.red
}
- onRunningChanged: {
- // When process finishes, reset it for next run
- if (!running) {
- appLoader.running = false
- }
+ MouseArea {
+ id: closeMouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: appLauncher.requestClose()
}
+ }
}
- // Function to load/reload applications
- function loadApps() {
- // Clear existing apps
- appListModel.clear()
- // Start the process to reload
- appLoader.running = true
- }
-
- // Load apps initially when component is created
- Component.onCompleted: {
- loadApps()
- }
-
- Column {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 8
-
- // Header
- Rectangle {
- width: parent.width
- height: 36
- color: "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "Applications"
- font.family: shellRoot.fontFamily
- font.pixelSize: 14
- font.weight: Font.Medium
- color: shellRoot.white
- }
-
- // Close button
- Rectangle {
- width: 28
- height: 28
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- radius: 6
- color: closeMouseArea.containsMouse ? shellRoot.red : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "✕"
- font.family: shellRoot.fontFamily
- font.pixelSize: 16
- font.weight: Font.Bold
- color: closeMouseArea.containsMouse ? shellRoot.red : shellRoot.orange
- }
-
- MouseArea {
- id: closeMouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: appLauncher.requestClose()
- }
- }
+ ListView {
+ id: listView
+ width: parent.width
+ height: parent.height - 44
+ spacing: 8
+ clip: true
+
+ model: ListModel {
+ id: appListModel
+ }
+
+ delegate: Rectangle {
+ width: listView.width
+ height: 30
+ color: {
+ if (appLauncher.hoverIndex === index) return shellRoot.lightgray
+ return shellRoot.gray
}
- // Apps list
- ListView {
- id: listView
- width: parent.width
- height: parent.height - 44
- spacing: 8
- clip: true
-
- model: ListModel {
- id: appListModel
- // Apps will be loaded dynamically from the script
- }
-
- delegate: Rectangle {
- width: listView.width
- height: 30
- color: {
- if (appLauncher.hoverIndex === index) return shellRoot.lightgray
- if (appLauncher.hoverIndex === -1 && index === shellRoot.selectedIndex) return shellRoot.lightgray
- return shellRoot.gray
- }
- radius: 12
- border.width: 2
- border.color: {
- if (appLauncher.hoverIndex === index || (shellRoot.hoverIndex === -1 && index === shellRoot.selectedIndex)) return shellRoot.blue
- return "transparent"
- }
-
- Row {
- anchors.fill: parent
- anchors.leftMargin: 12
- anchors.rightMargin: 12
- anchors.topMargin: 8
- anchors.bottomMargin: 8
- spacing: 12
-
- Text {
- width: parent.width - 62
- anchors.centerIn: parent
- text: model.appName
- font.family: shellRoot.fontFamily
- font.pixelSize: 14
- font.weight: Font.Medium
- color: {
- if (appLauncher.hoverIndex === index) return shellRoot.blue
- if (appLauncher.hoverIndex === -1 && index === shellRoot.selectedIndex) return shellRoot.blue
- return shellRoot.purple
- }
- elide: Text.ElideRight
- }
- }
-
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
-
- onEntered: {
- appLauncher.hoverIndex = index
- }
- onExited: {
- appLauncher.hoverIndex = -1
- }
-
- onClicked: {
- launchApp(model.appCommand)
- }
- }
+ Row {
+ anchors.fill: parent
+ anchors.leftMargin: 12
+ anchors.rightMargin: 12
+ anchors.topMargin: 8
+ anchors.bottomMargin: 8
+ spacing: 12
+
+ Text {
+ width: parent.width - 62
+ anchors.centerIn: parent
+ text: model.appName
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 14
+ font.weight: Font.Medium
+ color: {
+ return shellRoot.white;
}
+ elide: Text.ElideRight
+ }
}
- }
-
- function launchApp(command) {
- console.log("Launching app:", command)
- // Close the launcher first
- appLauncher.requestClose()
- // Use a small delay before launching to ensure the window closes properly
- Qt.callLater(() => {
- Quickshell.execDetached(["sh", "-c", command + " &"])
- })
+ MouseArea {
+ id:ouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+
+ onEntered: {
+ appLauncher.hoverIndex = index
+ }
+
+ onClicked: {
+ launchApp(model.appCommand)
+ }
+ }
+ }
}
+ }
+
+ function launchApp(command) {
+ appLauncher.requestClose()
+
+ // Use a small delay before launching to ensure the window closes properly
+ Qt.callLater(() => {
+ Quickshell.execDetached(["sh", "-c", command + " &"])
+ })
+ }
}
diff --git a/quickshell/Calendar.qml b/quickshell/Calendar.qml
new file mode 100644
index 0000000..0b79553
--- /dev/null
+++ b/quickshell/Calendar.qml
@@ -0,0 +1,143 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+
+Rectangle {
+ id: calendarRoot
+
+ color: shellRoot.black
+ antialiasing: true
+ focus: true
+
+ property bool isVisible: false
+ property int monthOffset: 0
+
+ signal requestClose()
+ Keys.onEscapePressed: {
+ monthOffset = 0;
+ requestClose()
+ }
+
+ Column {
+ anchors.fill: parent
+ spacing: 1
+ Rectangle {
+ width: parent.width
+ height: parent.height
+ color: shellRoot.black
+
+ Column {
+ anchors.fill: parent
+ anchors.margins: 5
+ spacing: 6
+
+ RowLayout {
+ width: parent.width
+ Text {
+ text: " <"
+ font.pixelSize: 20
+ font.family: shellRoot.fontFamily
+ color: shellRoot.white
+ MouseArea {
+ anchors.fill: parent
+ onClicked: monthOffset--;
+ }
+ }
+ Item { Layout.fillWidth: true }
+ Text {
+ text: {
+ const now = new Date()
+ const monthNames = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"]
+ let shiftedMonth = now.getMonth() + monthOffset;
+ let year = now.getFullYear();
+ year += Math.floor(shiftedMonth / 12);
+ shiftedMonth %= 12;
+ return monthNames[shiftedMonth] + " " + year
+ }
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 20
+ font.weight: Font.Medium
+ color: shellRoot.white
+ horizontalAlignment: Text.AlignHCenter
+ }
+ Item { Layout.fillWidth: true }
+ Text {
+ text: "> "
+ font.pixelSize: 20
+ font.family: shellRoot.fontFamily
+ color: shellRoot.white
+ MouseArea {
+ anchors.fill: parent
+ onClicked: monthOffset++;
+ }
+ }
+ }
+ Grid {
+ width: parent.width
+ columns: 7
+ columnSpacing: 1
+ rowSpacing: 1
+
+ Repeater {
+ model: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+
+ Text {
+ text: modelData
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 15
+ font.weight: Font.Medium
+ color: shellRoot.white
+ width: 50
+ height: 20
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+ Repeater {
+ model: 35
+
+ Rectangle {
+ width: 60
+ height: 60
+
+ property var now: new Date()
+ property int currentDay: now.getDate()
+ property int currentMonth: (now.getMonth() + monthOffset) % 12
+ property int currentYear: now.getFullYear() + Math.floor((now.getMonth() + monthOffset) / 12)
+
+ // Calculate what day this cell represents
+ property var firstDay: new Date(currentYear, currentMonth, 1)
+ property int startOffset: firstDay.getDay() // 0 = Sunday
+ property int dayNumber: index - startOffset + 1
+ property var lastDay: new Date(currentYear, currentMonth + 1, 0)
+ property int daysInMonth: lastDay.getDate()
+ property bool isCurrentDay: dayNumber === currentDay
+ property bool isValidDay: dayNumber >= 1 && dayNumber <= daysInMonth
+
+ color: {
+ if (!isValidDay) return "transparent"
+ if (monthOffset == 0 && isCurrentDay) return shellRoot.green
+ return shellRoot.gray
+ }
+
+ Text {
+ anchors.centerIn: parent
+ text: parent.isValidDay ? parent.dayNumber : ""
+ font.family: shellRoot.fontFamily
+ font.pixelSize: 20
+ color: {
+ if (parent.isValidDay && parent.isCurrentDay) return shellRoot.white
+ if (!parent.isValidDay) return shellRoot.white
+ return shellRoot.white
+ }
+ font.weight: parent.isValidDay && parent.isCurrentDay ? Font.DemiBold : Font.Normal
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/quickshell/shell.qml b/quickshell/shell.qml
index 307dece..f311380 100644
--- a/quickshell/shell.qml
+++ b/quickshell/shell.qml
@@ -25,25 +25,8 @@ ShellRoot {
property int fontSize: 18
property bool appLauncherVisible: false
+ property bool calendarVisible: false
- // focus: true
- // Keys.onPressed: (event) => {
- // if (event.key === Qt.Key_R && event.modifiers & Qt.MetaModifier) {
- // Quickshell.execDetached(["notify-send", "ok"])
- // appLauncherVisible = true;
- // }
- // }
- // Component.onCompleted: {
- // Hyprland.rawSignals = true;
- // }
- // Connections {
- // target: Hyprland
- //
- // function onEvent(sig) {
- // Quickshell.execDetached(["notify-send", "ok"])
- // console.log("Raw = ", sig);
- // }
- // }
GlobalShortcut {
id: launcherShortcut
name: "toggleLauncher"
@@ -51,6 +34,13 @@ ShellRoot {
appLauncherVisible = true
}
}
+ GlobalShortcut {
+ id: calendarShortcut
+ name: "toggleCalendar"
+ onPressed: {
+ calendarVisible = true
+ }
+ }
Variants {
model: Quickshell.screens
@@ -197,4 +187,44 @@ ShellRoot {
}
}
}
+
+ Variants {
+ model: Quickshell.screens
+
+ PanelWindow {
+ visible: shellRoot.calendarVisible
+
+ anchors {
+ top: true
+ left: true
+ }
+
+ margins {
+ top: 5
+ left: Screen.width - 435 - 40
+ }
+
+ implicitWidth: 435
+ implicitHeight: 370
+
+ color: "transparent"
+ exclusiveZone: 0
+
+ WlrLayershell.layer: WlrLayer.Overlay
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
+
+ Behavior on height {
+ NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
+ }
+
+ Calendar {
+ anchors.fill: parent
+ isVisible: shellRoot.calendarVisible
+
+ onRequestClose: {
+ shellRoot.calendarVisible = false
+ }
+ }
+ }
+ }
}