Merge pull request #44 from JPratama7/main
feat: Environment variables, taskbar, and IPC enhancements
This commit is contained in:
commit
5080c648ec
11 changed files with 328 additions and 27 deletions
|
|
@ -63,6 +63,10 @@ Scope {
|
|||
Media {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Taskbar {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
ActiveWindow {
|
||||
|
|
|
|||
|
|
@ -61,17 +61,17 @@ Item {
|
|||
id: batteryTooltip
|
||||
text: {
|
||||
let lines = [];
|
||||
if (isReady) {
|
||||
lines.push(charging ? "Charging" : "Discharging");
|
||||
lines.push(Math.round(percent) + "%");
|
||||
if (battery.changeRate !== undefined)
|
||||
lines.push("Rate: " + battery.changeRate.toFixed(2) + " W");
|
||||
if (battery.timeToEmpty > 0)
|
||||
lines.push("Time left: " + Math.floor(battery.timeToEmpty / 60) + " min");
|
||||
if (battery.timeToFull > 0)
|
||||
lines.push("Time to full: " + Math.floor(battery.timeToFull / 60) + " min");
|
||||
if (battery.healthPercentage !== undefined)
|
||||
lines.push("Health: " + Math.round(battery.healthPercentage) + "%");
|
||||
if (batteryWidget.isReady) {
|
||||
lines.push(batteryWidget.charging ? "Charging" : "Discharging");
|
||||
lines.push(Math.round(batteryWidget.percent) + "%");
|
||||
if (batteryWidget.battery.changeRate !== undefined)
|
||||
lines.push("Rate: " + batteryWidget.battery.changeRate.toFixed(2) + " W");
|
||||
if (batteryWidget.battery.timeToEmpty > 0)
|
||||
lines.push("Time left: " + Math.floor(batteryWidget.battery.timeToEmpty / 60) + " min");
|
||||
if (batteryWidget.battery.timeToFull > 0)
|
||||
lines.push("Time to full: " + Math.floor(batteryWidget.battery.timeToFull / 60) + " min");
|
||||
if (batteryWidget.battery.healthPercentage !== undefined)
|
||||
lines.push("Health: " + Math.round(batteryWidget.battery.healthPercentage) + "%");
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
|
|
|||
185
Bar/Modules/Taskbar.qml
Normal file
185
Bar/Modules/Taskbar.qml
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Settings
|
||||
import qs.Components
|
||||
|
||||
Item {
|
||||
id: taskbar
|
||||
width: runningAppsRow.width
|
||||
height: Settings.settings.taskbarIconSize
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
return "";
|
||||
|
||||
// Try different icon resolution strategies
|
||||
let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true);
|
||||
if (!icon) {
|
||||
icon = Quickshell.iconPath(toplevel.appId, true);
|
||||
}
|
||||
if (!icon) {
|
||||
icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true);
|
||||
}
|
||||
if (!icon) {
|
||||
icon = Quickshell.iconPath(toplevel.title, true);
|
||||
}
|
||||
if (!icon) {
|
||||
icon = Quickshell.iconPath("application-x-executable", true);
|
||||
}
|
||||
|
||||
return icon || "";
|
||||
}
|
||||
|
||||
Row {
|
||||
id: runningAppsRow
|
||||
spacing: 8
|
||||
height: parent.height
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
|
||||
delegate: Rectangle {
|
||||
|
||||
id: appButton
|
||||
width: Settings.settings.taskbarIconSize
|
||||
height: Settings.settings.taskbarIconSize
|
||||
radius: Math.max(4, Settings.settings.taskbarIconSize * 0.25)
|
||||
color: isActive ? Theme.accentPrimary : (hovered ? Theme.surfaceVariant : "transparent")
|
||||
border.color: isActive ? Qt.darker(Theme.accentPrimary, 1.2) : "transparent"
|
||||
border.width: 1
|
||||
|
||||
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
|
||||
// App icon
|
||||
IconImage {
|
||||
id: appIcon
|
||||
width: Math.max(12, Settings.settings.taskbarIconSize * 0.625) // 62.5% of button size (20/32 = 0.625)
|
||||
height: Math.max(12, Settings.settings.taskbarIconSize * 0.625)
|
||||
anchors.centerIn: parent
|
||||
source: getAppIcon(modelData)
|
||||
smooth: true
|
||||
|
||||
// Fallback to first letter if no icon
|
||||
visible: source.toString() !== ""
|
||||
}
|
||||
|
||||
// Fallback text if no icon available
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !appIcon.visible
|
||||
text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Math.max(10, Settings.settings.taskbarIconSize * 0.4375) // 43.75% of button size (14/32 = 0.4375)
|
||||
font.bold: true
|
||||
color: appButton.isActive ? Theme.onAccent : Theme.textPrimary
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
ToolTip {
|
||||
id: tooltip
|
||||
visible: mouseArea.containsMouse && !mouseArea.pressed
|
||||
delay: 800
|
||||
text: appTitle || appId
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.backgroundPrimary
|
||||
border.color: Theme.outline
|
||||
border.width: 1
|
||||
radius: 8
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: tooltip.text
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeCaption
|
||||
color: Theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: function(mouse) {
|
||||
console.log("[Taskbar] Clicked on", appButton.appId, "- Active:", appButton.isActive);
|
||||
|
||||
if (mouse.button === Qt.MiddleButton) {
|
||||
console.log("[Taskbar] Middle-clicked on", appButton.appId);
|
||||
|
||||
// Example: Close the window with middle click
|
||||
if (modelData && modelData.close) {
|
||||
modelData.close();
|
||||
} else {
|
||||
console.log("[Taskbar] No close method available for:", modelData);
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Left click: Focus/activate the window
|
||||
if (modelData && modelData.activate) {
|
||||
modelData.activate();
|
||||
} else {
|
||||
console.log("[Taskbar] No activate method available for:", modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right-click for additional actions
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
console.log("[Taskbar] Right-clicked on", appButton.appId);
|
||||
|
||||
// Example actions you can add:
|
||||
// 1. Close window
|
||||
// if (modelData && modelData.close) {
|
||||
// modelData.close();
|
||||
// }
|
||||
|
||||
// 2. Minimize window
|
||||
// if (modelData && modelData.minimize) {
|
||||
// modelData.minimize();
|
||||
// }
|
||||
|
||||
// 3. Show context menu (needs Menu component)
|
||||
// contextMenu.popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active indicator dot
|
||||
Rectangle {
|
||||
visible: isActive
|
||||
width: 4
|
||||
height: 4
|
||||
radius: 2
|
||||
color: Theme.onAccent
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: -6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
import Quickshell.Io
|
||||
|
||||
import "./IdleInhibitor.qml"
|
||||
|
||||
IpcHandler {
|
||||
property var appLauncherPanel
|
||||
property var lockScreen
|
||||
property IdleInhibitor idleInhibitor
|
||||
property var notificationPopup
|
||||
|
||||
target: "globalIPC"
|
||||
|
||||
function toggleIdleInhibitor(): void {
|
||||
idleInhibitor.toggle()
|
||||
}
|
||||
|
||||
|
||||
function toggleNotificationPopup(): void {
|
||||
console.log("[IPC] NotificationPopup toggle() called")
|
||||
notificationPopup.togglePopup();
|
||||
}
|
||||
|
||||
// Toggle Applauncher visibility
|
||||
function toggleLauncher(): void {
|
||||
if (!appLauncherPanel) {
|
||||
|
|
|
|||
43
Helpers/IdleInhibitor.qml
Normal file
43
Helpers/IdleInhibitor.qml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import Quickshell.Io
|
||||
|
||||
Process {
|
||||
id: idleRoot
|
||||
|
||||
// Example: systemd-inhibit to prevent idle/sleep
|
||||
command: ["systemd-inhibit", "--what=idle:sleep", "--who=noctalia", "--why=User requested", "sleep", "infinity"]
|
||||
|
||||
// Keep process running in background
|
||||
property bool isRunning: running
|
||||
|
||||
onStarted: {
|
||||
console.log("[IdleInhibitor] Process started - idle inhibited")
|
||||
}
|
||||
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
console.log("[IdleInhibitor] Process finished:", exitCode)
|
||||
}
|
||||
|
||||
// Control functions
|
||||
function start() {
|
||||
if (!running) {
|
||||
console.log("[IdleInhibitor] Starting idle inhibitor...")
|
||||
running = true
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (running) {
|
||||
// Force stop the process by setting running to false
|
||||
running = false
|
||||
console.log("[IdleInhibitor] Stopping idle inhibitor...")
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (running) {
|
||||
stop()
|
||||
} else {
|
||||
start()
|
||||
}
|
||||
}
|
||||
}
|
||||
15
README.md
15
README.md
|
|
@ -153,14 +153,23 @@ To make the weather widget, wallpaper manager and record button work you will ha
|
|||
qs ipc call globalIPC toggleLauncher
|
||||
```
|
||||
|
||||
### Toggle Lockscreen:
|
||||
### Toggle Notification Popup:
|
||||
|
||||
```
|
||||
qs ipc call globalIPC toggleLock
|
||||
qs ipc call globalIPC toggleNotificationPopup
|
||||
```
|
||||
|
||||
You can keybind it however you want in your niri setup.
|
||||
### Toggle Idle Inhibitor:
|
||||
|
||||
```
|
||||
qs ipc call globalIPC toggleIdleInhibitor
|
||||
```
|
||||
|
||||
### Toggle Fullscreen:
|
||||
|
||||
```
|
||||
qs ipc call globalIPC toggleFullscreen
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import qs.Services
|
|||
Singleton {
|
||||
|
||||
property string shellName: "Noctalia"
|
||||
property string settingsDir: (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|
||||
property string settingsFile: settingsDir + "Settings.json"
|
||||
property string settingsDir: Quickshell.env("NOCTALIA_SETTINGS_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
|
||||
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (settingsDir + "Settings.json")
|
||||
property string themeFile: Quickshell.env("NOCTALIA_THEME_FILE") || (settingsDir + "Theme.json")
|
||||
property var settings: settingAdapter
|
||||
|
||||
Item {
|
||||
|
|
@ -60,6 +61,8 @@ Singleton {
|
|||
property bool reverseDayMonth: false
|
||||
property bool use12HourClock: false
|
||||
property bool dimPanels: true
|
||||
property real fontSizeMultiplier: 1.0 // Font size multiplier (1.0 = normal, 1.2 = 20% larger, 0.8 = 20% smaller)
|
||||
property int taskbarIconSize: 24 // Taskbar icon button size in pixels (default: 32, smaller: 24, larger: 40)
|
||||
property var pinnedExecs: [] // Added for AppLauncher pinned apps
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ Singleton {
|
|||
// FileView to load theme data from JSON file
|
||||
FileView {
|
||||
id: themeFile
|
||||
path: Settings.settingsDir + "Theme.json"
|
||||
path: Settings.themeFile
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
onLoadFailed: function(error) {
|
||||
if (error.includes("No such file")) {
|
||||
themeData = {}
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter()
|
||||
}
|
||||
}
|
||||
|
|
@ -102,9 +102,13 @@ Singleton {
|
|||
|
||||
// Font Properties
|
||||
property string fontFamily: "Roboto" // Family for all text
|
||||
|
||||
property int fontSizeHeader: 32 // Headers and titles
|
||||
property int fontSizeBody: 16 // Body text and general content
|
||||
property int fontSizeSmall: 14 // Small text like clock, labels
|
||||
property int fontSizeCaption: 12 // Captions and fine print
|
||||
|
||||
// Font size multiplier - adjust this in Settings.json to scale all fonts
|
||||
property real fontSizeMultiplier: Settings.settings.fontSizeMultiplier || 1.0
|
||||
|
||||
// Base font sizes (multiplied by fontSizeMultiplier)
|
||||
property int fontSizeHeader: Math.round(32 * fontSizeMultiplier) // Headers and titles
|
||||
property int fontSizeBody: Math.round(16 * fontSizeMultiplier) // Body text and general content
|
||||
property int fontSizeSmall: Math.round(14 * fontSizeMultiplier) // Small text like clock, labels
|
||||
property int fontSizeCaption: Math.round(12 * fontSizeMultiplier) // Captions and fine print
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@ import qs.Components
|
|||
Item {
|
||||
id: root
|
||||
width: 22; height: 22
|
||||
property bool isSilence: false
|
||||
|
||||
// Process for executing CLI commands
|
||||
Process {
|
||||
id: rightClickProcess
|
||||
command: ["qs","ipc", "call", "globalIPC", "toggleNotificationPopup"]
|
||||
}
|
||||
|
||||
// Bell icon/button
|
||||
Item {
|
||||
id: bell
|
||||
width: 22; height: 22
|
||||
Text {
|
||||
id: bellText
|
||||
anchors.centerIn: parent
|
||||
text: notificationHistoryWin.hasUnread ? "notifications_unread" : "notifications"
|
||||
font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
|
||||
|
|
@ -25,7 +33,20 @@ Item {
|
|||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: notificationHistoryWin.visible = !notificationHistoryWin.visible
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: function(mouse): void {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
root.isSilence = !root.isSilence;
|
||||
rightClickProcess.running = true;
|
||||
bellText.text = root.isSilence ? "notifications_off" : "notifications"
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton){
|
||||
notificationHistoryWin.visible = !notificationHistoryWin.visible
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
onEntered: notificationTooltip.tooltipVisible = true
|
||||
onExited: notificationTooltip.tooltipVisible = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ PanelWindow {
|
|||
implicitWidth: 350
|
||||
implicitHeight: notificationColumn.implicitHeight
|
||||
color: "transparent"
|
||||
visible: notificationModel.count > 0
|
||||
visible: notificationsVisible && notificationModel.count > 0
|
||||
screen: Quickshell.primaryScreen !== undefined ? Quickshell.primaryScreen : null
|
||||
focusable: false
|
||||
|
||||
property bool barVisible: true
|
||||
property bool notificationsVisible: true
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
|
|
@ -26,6 +27,12 @@ PanelWindow {
|
|||
property int maxVisible: 5
|
||||
property int spacing: 5
|
||||
|
||||
function togglePopup(): void {
|
||||
console.log("[NotificationPopup] Current state: " + notificationsVisible);
|
||||
notificationsVisible = !notificationsVisible;
|
||||
console.log("[NotificationPopup] New state: " + notificationsVisible);
|
||||
}
|
||||
|
||||
function addNotification(notification) {
|
||||
notificationModel.insert(0, {
|
||||
id: notification.id,
|
||||
|
|
|
|||
13
shell.qml
13
shell.qml
|
|
@ -11,6 +11,9 @@ import qs.Widgets.Notification
|
|||
import qs.Settings
|
||||
import qs.Helpers
|
||||
|
||||
import "./Helpers/IdleInhibitor.qml"
|
||||
import "./Helpers/IPCHandlers.qml"
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
|
|
@ -50,12 +53,18 @@ Scope {
|
|||
}
|
||||
}
|
||||
|
||||
IdleInhibitor {
|
||||
id: idleInhibitor
|
||||
}
|
||||
|
||||
NotificationServer {
|
||||
id: notificationServer
|
||||
onNotification: function (notification) {
|
||||
console.log("Notification received:", notification.appName);
|
||||
notification.tracked = true;
|
||||
notificationPopup.addNotification(notification);
|
||||
if (notificationPopup.notificationsVisible) {
|
||||
notificationPopup.addNotification(notification);
|
||||
}
|
||||
if (notificationHistoryWin) {
|
||||
notificationHistoryWin.addToHistory({
|
||||
id: notification.id,
|
||||
|
|
@ -89,6 +98,8 @@ Scope {
|
|||
IPCHandlers {
|
||||
appLauncherPanel: appLauncherPanel
|
||||
lockScreen: lockScreen
|
||||
idleInhibitor: idleInhibitor
|
||||
notificationPopup: notificationPopup
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue