From 9cdeca7ef838bf581ae1493506df37802b951c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 04:42:57 +0300 Subject: [PATCH 01/15] feat: add retro-themed hyprlock configuration - ASCII box-drawing frame for retro aesthetic - Time and date display with shadow effect - User info and password input field - Battery and hostname indicators - Matches Linux Retroism 80-90s UI theme --- configs/hypr/hyprlock.conf | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 configs/hypr/hyprlock.conf diff --git a/configs/hypr/hyprlock.conf b/configs/hypr/hyprlock.conf new file mode 100644 index 0000000..a121e92 --- /dev/null +++ b/configs/hypr/hyprlock.conf @@ -0,0 +1,178 @@ +# Linux Retroism - Hyprlock Theme +# 1980-1990's Retro UI Aesthetic + +background { + monitor = + path = screenshot + blur_passes = 4 + blur_size = 8 + brightness = 0.5 +} + +# ═══════════ TOP FRAME ═══════════ +label { + monitor = + text = ╔══════════════════════════════════════╗ + color = rgb(f0e2d3) + font_size = 16 + font_family = monospace + position = 0, 200 + halign = center + valign = center +} + +# Title bar +label { + monitor = + text = ║═══════ 🔒 SYSTEM LOCKED ═══════║ + color = rgb(f0e2d3) + font_size = 16 + font_family = monospace + position = 0, 175 + halign = center + valign = center +} + +label { + monitor = + text = ╠══════════════════════════════════════╣ + color = rgb(f0e2d3) + font_size = 16 + font_family = monospace + position = 0, 150 + halign = center + valign = center +} + +# Time display +label { + monitor = + text = $TIME + color = rgb(626335) + font_size = 56 + font_family = monospace + shadow_passes = 2 + shadow_size = 3 + shadow_color = rgb(3d3d39) + position = 0, 100 + halign = center + valign = center +} + +# Date +label { + monitor = + text = cmd[update:60000] date '+%A, %d %B %Y' + color = rgb(baafa1) + font_size = 14 + font_family = monospace + position = 0, 55 + halign = center + valign = center +} + +# Separator +label { + monitor = + text = ──────────────────────── + color = rgb(626335) + font_size = 14 + font_family = monospace + position = 0, 30 + halign = center + valign = center +} + +# Username +label { + monitor = + text = $USER + color = rgb(f0e2d3) + font_size = 20 + font_family = monospace + position = 0, 0 + halign = center + valign = center +} + +# Input field +input-field { + monitor = + size = 280, 45 + outline_thickness = 3 + dots_size = 0.3 + dots_spacing = 0.15 + dots_center = true + outer_color = rgb(626335) + inner_color = rgb(3d3d39) + font_color = rgb(f0e2d3) + fade_on_empty = false + placeholder_text = ▌Enter Password... + hide_input = false + rounding = 0 + check_color = rgb(626335) + fail_color = rgb(ff854c) + capslock_color = rgb(ff854c) + position = 0, -50 + halign = center + valign = center +} + +# Hint +label { + monitor = + text = [ Press ENTER to unlock ] + color = rgb(baafa1) + font_size = 11 + font_family = monospace + position = 0, -95 + halign = center + valign = center +} + +# Separator +label { + monitor = + text = ──────────────────────── + color = rgb(626335) + font_size = 14 + font_family = monospace + position = 0, -120 + halign = center + valign = center +} + +# System info +label { + monitor = + text = cmd[update:5000] echo "🔋 $(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -1)%" 2>/dev/null || echo "⚡ AC" + color = rgb(baafa1) + font_size = 11 + font_family = monospace + position = -100, -145 + halign = center + valign = center +} + +label { + monitor = + text = cmd[update:0] hostname + color = rgb(baafa1) + font_size = 11 + font_family = monospace + position = 100, -145 + halign = center + valign = center +} + +# ═══════════ BOTTOM FRAME ═══════════ +label { + monitor = + text = ╚══════════════════════════════════════╝ + color = rgb(f0e2d3) + font_size = 16 + font_family = monospace + position = 0, -170 + halign = center + valign = center +} From ced300b8bc0b73a4d272fbf0c74ad379fe7c92e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 04:44:10 +0300 Subject: [PATCH 02/15] feat: add Settings Menu and Power Menu widgets Settings Menu: - Font size and tray icon size sliders - Monochrome tray icons toggle - 24-hour clock format toggle - Theme selector with all available themes - Terminal and File Manager settings (reads/writes hyprland.conf) - System information display Power Menu: - Lock, Sleep, Logout, Restart, Shutdown buttons - Confirmation dialog before actions - Opens as centered overlay widget - Only appears on active monitor Other changes: - Config.qml: Added hyprland.conf integration functions - Time.qml: Added 24-hour format support - PopupWindowFrame.qml: Added retro close button - StartMenu.qml: Updated power button to open new widget --- configs/quickshell/Config.qml | 50 ++ configs/quickshell/Time.qml | 4 +- .../quickshell/popups/PopupWindowFrame.qml | 48 ++ configs/quickshell/popups/StartMenu.qml | 5 +- configs/quickshell/settings.json | 46 +- configs/quickshell/shell.qml | 545 ++++++++++++++++-- 6 files changed, 629 insertions(+), 69 deletions(-) diff --git a/configs/quickshell/Config.qml b/configs/quickshell/Config.qml index 5437591..ccfd23f 100644 --- a/configs/quickshell/Config.qml +++ b/configs/quickshell/Config.qml @@ -6,6 +6,55 @@ import Quickshell.Io Singleton { id: root + // Hyprland config values + property string hyprlandTerminal: "kitty" + property string hyprlandFileManager: "nautilus" + property string hyprlandConfigPath: "/home/ozhan/.config/hypr/hyprland.conf" + + // Read hyprland.conf on startup + Process { + id: readTerminal + command: ["grep", "^\\$terminal", hyprlandConfigPath] + running: true + onExited: { + var match = stdout.match(/\$terminal\s*=\s*(.+)/); + if (match) hyprlandTerminal = match[1].trim(); + } + } + + Process { + id: readFileManager + command: ["grep", "^\\$fileManager", hyprlandConfigPath] + running: true + onExited: { + var match = stdout.match(/\$fileManager\s*=\s*(.+)/); + if (match) hyprlandFileManager = match[1].trim(); + } + } + + // Functions to update hyprland.conf + function setHyprlandTerminal(value) { + hyprlandTerminal = value; + updateTerminalProc.command = ["sed", "-i", "s/^\\$terminal = .*/$terminal = " + value + "/", hyprlandConfigPath]; + updateTerminalProc.running = true; + } + + function setHyprlandFileManager(value) { + hyprlandFileManager = value; + updateFileManagerProc.command = ["sed", "-i", "s/^\\$fileManager = .*/$fileManager = " + value + "/", hyprlandConfigPath]; + updateFileManagerProc.running = true; + } + + Process { + id: updateTerminalProc + onExited: console.log("Hyprland terminal updated") + } + + Process { + id: updateFileManagerProc + onExited: console.log("Hyprland fileManager updated") + } + //*=======================================================================*/ // READ THIS NOTE: // Simply add to this list in order to create your @@ -77,6 +126,7 @@ Singleton { } property bool openSettingsWindow: false + property bool openPowerMenu: false property alias settings: settingsJsonAdapter.settings FileView { diff --git a/configs/quickshell/Time.qml b/configs/quickshell/Time.qml index abee869..11338c5 100644 --- a/configs/quickshell/Time.qml +++ b/configs/quickshell/Time.qml @@ -6,7 +6,9 @@ import QtQuick Singleton { id: root readonly property string time: { - Qt.formatDateTime(clock.date, " MMM d yyyy | hh:mm"); + Config.settings.militaryTimeClockFormat + ? Qt.formatDateTime(clock.date, " MMM d yyyy | HH:mm") + : Qt.formatDateTime(clock.date, " MMM d yyyy | h:mm AP"); } SystemClock { diff --git a/configs/quickshell/popups/PopupWindowFrame.qml b/configs/quickshell/popups/PopupWindowFrame.qml index e8bda5e..34eae58 100644 --- a/configs/quickshell/popups/PopupWindowFrame.qml +++ b/configs/quickshell/popups/PopupWindowFrame.qml @@ -21,12 +21,60 @@ Rectangle { property int windowTitleDecorationWidth: 100 property string windowTitle: "Window Title" property string windowTitleIcon: "\uf088" + property bool showCloseButton: false + signal closeClicked() /*=== Top Bar Styling (name and bars) ===*/ Item { anchors.left: parent.left anchors.right: parent.right implicitHeight: 25 + + // Close Button (retro style) + Rectangle { + visible: root.showCloseButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + width: 16 + height: 16 + color: closeButtonArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1 + border.color: Config.colors.outline + + // 3D effect - top/left highlight + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 1 + anchors.rightMargin: closeButtonArea.pressed ? 1 : 0 + anchors.bottomMargin: closeButtonArea.pressed ? 1 : 0 + color: "transparent" + border.width: 1 + border.color: closeButtonArea.pressed ? Config.colors.shadow : Config.colors.base + } + + // X icon + Text { + anchors.centerIn: parent + anchors.verticalCenterOffset: closeButtonArea.pressed ? 1 : 0 + text: "×" + font.family: fontCharcoal.name + font.pixelSize: 14 + font.bold: true + color: Config.colors.text + } + + MouseArea { + id: closeButtonArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.closeClicked() + } + } + RowLayout { id: panelName anchors.centerIn: parent diff --git a/configs/quickshell/popups/StartMenu.qml b/configs/quickshell/popups/StartMenu.qml index 026b322..b5e50ec 100644 --- a/configs/quickshell/popups/StartMenu.qml +++ b/configs/quickshell/popups/StartMenu.qml @@ -191,7 +191,7 @@ PopupWindow { implicitWidth: 60 onClicked: () => { - Quickshell.execDetached(Config.settings.execCommands.files); + Quickshell.execDetached(Config.hyprlandFileManager); root.closeCallback(); } @@ -242,7 +242,7 @@ PopupWindow { implicitWidth: 60 onClicked: () => { - Quickshell.execDetached(Config.settings.execCommands.terminal); + Quickshell.execDetached(Config.hyprlandTerminal); root.closeCallback(); } @@ -344,6 +344,7 @@ PopupWindow { implicitWidth: 60 onClicked: () => { + Config.openPowerMenu = true; root.closeCallback(); } diff --git a/configs/quickshell/settings.json b/configs/quickshell/settings.json index 5b63e39..18ed843 100644 --- a/configs/quickshell/settings.json +++ b/configs/quickshell/settings.json @@ -1,25 +1,25 @@ { - "settings": { - "bar": { - "fontSize": 12, - "monochromeTrayIcons": true, - "trayIconSize": 16 - }, - "currentTheme": "default", - "execCommands": { - "files": "nemo", - "terminal": "kitty" - }, - "militaryTimeClockFormat": true, - "setWallpaperToThemeWallpaper": true, - "systemDetails": { - "cpu": "CPU Name", - "gpu": "GPU Name", - "osName": "Linux Distro", - "osVersion": "Distro Version", - "ram": "Ram" - }, - "systemProfileImageSource": "/home/username/Pictures/system_profile_picture.png", - "version": "0.1" - } + "settings": { + "bar": { + "fontSize": 10, + "monochromeTrayIcons": false, + "trayIconSize": 14 + }, + "currentTheme": "yorha", + "execCommands": { + "files": "nautilus", + "terminal": "zshrc" + }, + "militaryTimeClockFormat": true, + "setWallpaperToThemeWallpaper": true, + "systemDetails": { + "cpu": "Intel i5-12450H", + "gpu": "RTX 3050 Ti 4GB", + "osName": "Arch Linux", + "osVersion": "Rolling Release", + "ram": "32GB" + }, + "systemProfileImageSource": "/home/ozhan/Masaüstü/Danni _ bg3.jpg", + "version": "1.0" + } } diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index f3ab069..f98ccb9 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -9,6 +9,8 @@ import QtQuick import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland import "taskbar" as Taskbar import "popups" as Popups @@ -29,55 +31,512 @@ Scope { } Taskbar.Bar {} - FloatingWindow { - id: settingsWindow - title: "RetroismSettingsWindow" - reloadableId: "RetroismSettingsWindow" - visible: Config.openSettingsWindow - Popups.PopupWindowFrame { - id: settingsWindowFrame - windowTitle: "Settings" - windowTitleIcon: "\ue8b8" - windowTitleDecorationWidth: (settingsWindow.width / 2) - 70 - - anchors.leftMargin: -1 - anchors.bottomMargin: -1 - anchors.rightMargin: -1 - Item { - id: content - anchors.fill: settingsWindowFrame - anchors.margins: 18 - anchors.topMargin: 20 + 18 - clip: true - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - color: Config.colors.highlight - border.width: 1 - border.color: Config.colors.outline - height: 148 - Text { + // ============ SETTINGS WIDGET (Overlay) ============ + Variants { + model: Quickshell.screens + PanelWindow { + id: settingsWindow + required property var modelData + screen: modelData + visible: Config.openSettingsWindow && Hyprland.focusedMonitor.name === modelData.name + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + anchors { + top: true + bottom: true + left: true + right: true + } + color: "transparent" + + property int currentTab: 0 + + Rectangle { + width: 420 + height: 380 + x: (settingsWindow.width - width) / 2 + y: (settingsWindow.height - height) / 2 + color: Config.colors.base + + Popups.PopupWindowFrame { + id: settingsWindowFrame + anchors.fill: parent + windowTitle: "Settings" + windowTitleIcon: "\ue8b8" + windowTitleDecorationWidth: 100 + showCloseButton: true + onCloseClicked: Config.openSettingsWindow = false + + Item { anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: fontMonaco.name - font.pixelSize: 28 - text: "Linux Retroism " + Config.settings.version + anchors.margins: 12 + anchors.topMargin: 32 + clip: true + + // Tab Bar + Row { + id: tabBar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: 2 + + Repeater { + model: ["Appearance", "System", "About"] + Rectangle { + width: (parent.width - 4) / 3 + height: 28 + color: settingsWindow.currentTab === index ? Config.colors.highlight : Config.colors.shadow + border.width: 1 + border.color: Config.colors.outline + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + anchors.bottomMargin: settingsWindow.currentTab === index ? 0 : 1 + color: "transparent" + border.width: 1 + border.color: settingsWindow.currentTab === index ? Config.colors.highlight : Config.colors.base + } + + Text { + anchors.centerIn: parent + text: modelData + font.family: fontCharcoal.name + font.pixelSize: 11 + color: Config.colors.text + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: settingsWindow.currentTab = index + } + } + } + } + + // Tab Content + Rectangle { + anchors.top: tabBar.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.topMargin: -1 + color: Config.colors.highlight + border.width: 1 + border.color: Config.colors.outline + + Rectangle { + anchors.fill: parent + anchors.margins: 8 + color: Config.colors.base + border.width: 1 + border.color: Config.colors.shadow + + // === APPEARANCE TAB === + Item { + anchors.fill: parent + anchors.margins: 12 + visible: settingsWindow.currentTab === 0 + + Column { + anchors.fill: parent + spacing: 14 + + // Font Size + Column { + width: parent.width + spacing: 4 + Text { text: "Font Size"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + Row { + spacing: 10 + Rectangle { + width: 180; height: 20 + color: Config.colors.shadow + border.width: 1; border.color: Config.colors.outline + Rectangle { + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 2 + width: (Config.settings.bar.fontSize - 8) / 12 * (parent.width - 4) + color: Config.colors.accent + } + MouseArea { + anchors.fill: parent + onClicked: function(mouse) { + Config.settings.bar.fontSize = Math.max(8, Math.min(20, Math.round(8 + (mouse.x / width) * 12))); + } + } + } + Text { text: Config.settings.bar.fontSize + "px"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + } + } + + // Tray Icon Size + Column { + width: parent.width + spacing: 4 + Text { text: "Tray Icon Size"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + Row { + spacing: 10 + Rectangle { + width: 180; height: 20 + color: Config.colors.shadow + border.width: 1; border.color: Config.colors.outline + Rectangle { + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 2 + width: (Config.settings.bar.trayIconSize - 12) / 12 * (parent.width - 4) + color: Config.colors.accent + } + MouseArea { + anchors.fill: parent + onClicked: function(mouse) { + Config.settings.bar.trayIconSize = Math.max(12, Math.min(24, Math.round(12 + (mouse.x / width) * 12))); + } + } + } + Text { text: Config.settings.bar.trayIconSize + "px"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + } + } + + // Checkboxes + Row { + spacing: 8 + Rectangle { + width: 16; height: 16 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: Config.settings.bar.monochromeTrayIcons ? "✓" : ""; font.pixelSize: 12; font.bold: true; color: Config.colors.text } + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.bar.monochromeTrayIcons = !Config.settings.bar.monochromeTrayIcons } + } + Text { text: "Monochrome Tray Icons"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + } + + Row { + spacing: 8 + Rectangle { + width: 16; height: 16 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: Config.settings.militaryTimeClockFormat ? "✓" : ""; font.pixelSize: 12; font.bold: true; color: Config.colors.text } + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.militaryTimeClockFormat = !Config.settings.militaryTimeClockFormat } + } + Text { text: "24-Hour Clock Format"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + } + + // Theme Selector + Column { + width: parent.width + spacing: 6 + Text { text: "Theme"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + Flow { + width: parent.width + spacing: 6 + Repeater { + model: Object.keys(Config.themes) + Rectangle { + property string themeName: modelData + width: 70; height: 50 + color: Config.settings.currentTheme === themeName ? Config.colors.highlight : Config.colors.shadow + border.width: Config.settings.currentTheme === themeName ? 2 : 1 + border.color: Config.settings.currentTheme === themeName ? Config.themes[themeName].accent : Config.colors.outline + Column { + anchors.centerIn: parent + spacing: 3 + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 2 + Rectangle { width: 10; height: 10; color: Config.themes[themeName].base; border.width: 1; border.color: Config.themes[themeName].outline } + Rectangle { width: 10; height: 10; color: Config.themes[themeName].accent; border.width: 1; border.color: Config.themes[themeName].outline } + Rectangle { width: 10; height: 10; color: Config.themes[themeName].shadow; border.width: 1; border.color: Config.themes[themeName].outline } + } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: themeName; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text } + } + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.currentTheme = themeName } + } + } + } + } + } + } + + // === SYSTEM TAB === + Item { + anchors.fill: parent + anchors.margins: 12 + visible: settingsWindow.currentTab === 1 + + Column { + anchors.fill: parent + spacing: 14 + + // Terminal + Column { + width: parent.width + spacing: 4 + Text { text: "Terminal Application"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + Row { + spacing: 6 + Rectangle { + width: 180; height: 24 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + TextInput { + id: terminalInput + anchors.fill: parent; anchors.margins: 4 + text: Config.hyprlandTerminal + font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text + clip: true; selectByMouse: true; activeFocusOnPress: true + } + } + Rectangle { + width: 45; height: 24 + color: termSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + MouseArea { id: termSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandTerminal(terminalInput.text) } + } + } + Text { text: "→ hyprland.conf: $terminal"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.5 } + } + + // File Manager + Column { + width: parent.width + spacing: 4 + Text { text: "File Manager Application"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + Row { + spacing: 6 + Rectangle { + width: 180; height: 24 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + TextInput { + id: filesInput + anchors.fill: parent; anchors.margins: 4 + text: Config.hyprlandFileManager + font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text + clip: true; selectByMouse: true; activeFocusOnPress: true + } + } + Rectangle { + width: 45; height: 24 + color: filesSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + MouseArea { id: filesSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandFileManager(filesInput.text) } + } + } + Text { text: "→ hyprland.conf: $fileManager"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.5 } + } + + Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + + Text { text: "System Information"; font.family: fontCharcoal.name; font.pixelSize: 11; font.bold: true; color: Config.colors.text } + + Grid { + columns: 2; columnSpacing: 10; rowSpacing: 6 + Text { text: "OS:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: Config.settings.systemDetails.osName; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: "Version:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: Config.settings.systemDetails.osVersion; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: "CPU:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: Config.settings.systemDetails.cpu; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: "GPU:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: Config.settings.systemDetails.gpu; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: "RAM:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + Text { text: Config.settings.systemDetails.ram; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + } + } + } + + // === ABOUT TAB === + Item { + anchors.fill: parent + anchors.margins: 12 + visible: settingsWindow.currentTab === 2 + + Column { + anchors.centerIn: parent + spacing: 12 + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Linux Retroism"; font.family: fontCharcoal.name; font.pixelSize: 20; color: Config.colors.text } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Version " + Config.settings.version; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } + Rectangle { width: 200; height: 1; color: Config.colors.outline; anchors.horizontalCenter: parent.horizontalCenter } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "A 1980-1990's retro UI aesthetic\nfor Hyprland & Sway"; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; horizontalAlignment: Text.AlignHCenter } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Created by diinki"; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.accent } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "github.com/diinki/linux-retroism"; font.family: fontMonaco.name; font.pixelSize: 9; color: Config.colors.text; opacity: 0.6 } + } + } + } + } } - Text { + } + } + } + } + + // ============ POWER MENU WIDGET (Overlay) ============ + Variants { + model: Quickshell.screens + PanelWindow { + id: powerMenu + required property var modelData + screen: modelData + visible: Config.openPowerMenu && Hyprland.focusedMonitor.name === modelData.name + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + anchors { + top: true + bottom: true + left: true + right: true + } + color: "transparent" + + property string powerMenuState: "" + + function updateHyprlockTheme() { + var c = Config.colors; + var cmd = "sed -i " + + "-e 's/outer_color = rgb([^)]*)/outer_color = rgb(" + c.outline.slice(1) + ")/g' " + + "-e 's/inner_color = rgb([^)]*)/inner_color = rgb(" + c.base.slice(1) + ")/g' " + + "-e 's/font_color = rgb([^)]*)/font_color = rgb(" + c.text.slice(1) + ")/g' " + + "-e 's/check_color = rgb([^)]*)/check_color = rgb(" + c.accent.slice(1) + ")/g' " + + "-e 's/fail_color = rgb([^)]*)/fail_color = rgb(" + c.urgent.slice(1) + ")/g' " + + "-e 's/color = rgb(3e3d38)/color = rgb(" + c.text.slice(1) + ")/g' " + + "-e 's/color = rgb(626335)/color = rgb(" + c.accent.slice(1) + ")/g' " + + "/home/ozhan/.config/hypr/hyprlock.conf"; + Quickshell.execDetached(cmd); + } + + property var powerActions: { + "lock": { title: "Lock", icon: "\ue897", cmd: "hyprlock" }, + "sleep": { title: "Sleep", icon: "\ue91e", cmd: "systemctl suspend" }, + "logout": { title: "Logout", icon: "\ue9ba", cmd: "hyprctl dispatch exit" }, + "restart": { title: "Restart", icon: "\ue5d5", cmd: "systemctl reboot" }, + "shutdown": { title: "Shut Down", icon: "\ue8ac", cmd: "systemctl poweroff" } + } + + onVisibleChanged: { + if (!visible) powerMenuState = ""; + } + + Rectangle { + width: 320 + height: 180 + x: (powerMenu.width - width) / 2 + y: (powerMenu.height - height) / 2 + color: Config.colors.base + + Popups.PopupWindowFrame { + anchors.fill: parent + windowTitle: powerMenu.powerMenuState === "" ? "Power" : "Confirm" + windowTitleIcon: "\uf418" + windowTitleDecorationWidth: 60 + showCloseButton: true + onCloseClicked: { powerMenu.powerMenuState = ""; Config.openPowerMenu = false; } + + Item { anchors.fill: parent - anchors.bottomMargin: 16 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignBottom - font.family: fontMonaco.name - font.pixelSize: 12 - text: "Version 0.1 is very early and does not yet have a proper settings menu.\nPlease look forward for future releases on github ~ diinki" + anchors.margins: 12 + anchors.topMargin: 32 + + // Main Menu + Column { + anchors.centerIn: parent + spacing: 10 + visible: powerMenu.powerMenuState === "" + + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "What would you like to do?"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + + Grid { + anchors.horizontalCenter: parent.horizontalCenter + columns: 5 + spacing: 5 + + Repeater { + model: ["lock", "sleep", "logout", "restart", "shutdown"] + Rectangle { + property string actionKey: modelData + width: 50; height: 46 + property bool isShutdown: actionKey === "shutdown" + color: btnArea.pressed ? Config.colors.shadow : (isShutdown ? Config.colors.urgent : Config.colors.highlight) + border.width: 1; border.color: Config.colors.outline + + Column { + anchors.centerIn: parent + spacing: 2 + Text { anchors.horizontalCenter: parent.horizontalCenter; text: powerMenu.powerActions[actionKey].icon; font.family: iconFont.name; font.pixelSize: 18; color: Config.colors.text } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: powerMenu.powerActions[actionKey].title; font.family: fontMonaco.name; font.pixelSize: 7; color: Config.colors.text } + } + MouseArea { + id: btnArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (actionKey === "lock") { + powerMenu.updateHyprlockTheme(); + } + Quickshell.execDetached(powerMenu.powerActions[actionKey].cmd); + Config.openPowerMenu = false; + } + } + } + } + } + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + width: 60; height: 22 + color: cancelArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Cancel"; font.family: fontCharcoal.name; font.pixelSize: 9; color: Config.colors.text } + MouseArea { id: cancelArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.openPowerMenu = false } + } + } + + // Confirmation Screen + Column { + anchors.centerIn: parent + spacing: 12 + visible: powerMenu.powerMenuState !== "" + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: powerMenu.powerMenuState !== "" ? "Are you sure you want to\n" + powerMenu.powerActions[powerMenu.powerMenuState].title + "?" : "" + font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text + horizontalAlignment: Text.AlignHCenter + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 10 + + Rectangle { + width: 70; height: 24 + color: yesArea.pressed ? Config.colors.shadow : Config.colors.urgent + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Yes"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + MouseArea { + id: yesArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor + onClicked: { + if (powerMenu.powerMenuState !== "") Quickshell.execDetached(powerMenu.powerActions[powerMenu.powerMenuState].cmd); + powerMenu.powerMenuState = ""; Config.openPowerMenu = false; + } + } + } + + Rectangle { + width: 70; height: 24 + color: noArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "No"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + MouseArea { id: noArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: powerMenu.powerMenuState = "" } + } + } + } } } } } - onClosed: { - Config.openSettingsWindow = false; - } } } From 349c7a8c3b9014f017ae1ec9f28726882388b90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:23:16 +0300 Subject: [PATCH 03/15] feat: add Network (WiFi) and Bluetooth tabs to Settings Network Tab: - List available WiFi networks with signal strength - WiFi toggle switch (on/off) - Refresh button to rescan networks - Double-click to connect - Shows security status (locked icon) - Connected network highlighted Bluetooth Tab: - List paired Bluetooth devices - Bluetooth toggle switch (on/off) - Scan button to discover new devices - Refresh button to update device list - Double-click to connect/disconnect - Connected devices highlighted Uses nmcli for WiFi and bluetoothctl for Bluetooth. --- configs/quickshell/shell.qml | 286 ++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 5 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index f98ccb9..6a2ff05 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -54,8 +54,8 @@ Scope { property int currentTab: 0 Rectangle { - width: 420 - height: 380 + width: 480 + height: 420 x: (settingsWindow.width - width) / 2 y: (settingsWindow.height - height) / 2 color: Config.colors.base @@ -84,9 +84,9 @@ Scope { spacing: 2 Repeater { - model: ["Appearance", "System", "About"] + model: ["Appearance", "System", "Network", "Bluetooth", "About"] Rectangle { - width: (parent.width - 4) / 3 + width: (parent.width - 8) / 5 height: 28 color: settingsWindow.currentTab === index ? Config.colors.highlight : Config.colors.shadow border.width: 1 @@ -347,12 +347,288 @@ Scope { } } - // === ABOUT TAB === + // === NETWORK TAB === Item { + id: networkTab anchors.fill: parent anchors.margins: 12 visible: settingsWindow.currentTab === 2 + property var wifiList: [] + property string currentWifi: "" + property bool wifiEnabled: true + property bool scanning: false + + Component.onCompleted: refreshNetworks() + + function refreshNetworks() { + scanning = true; + Quickshell.exec("nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE device wifi list", function(result) { + var lines = result.stdout.trim().split('\n'); + var networks = []; + currentWifi = ""; + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].split(':'); + if (parts[0] && parts[0].trim() !== "") { + var net = { ssid: parts[0], signal: parseInt(parts[1]) || 0, security: parts[2] || "", active: parts[3] === "*" }; + networks.push(net); + if (net.active) currentWifi = net.ssid; + } + } + wifiList = networks; + scanning = false; + }); + Quickshell.exec("nmcli radio wifi", function(result) { + wifiEnabled = result.stdout.trim() === "enabled"; + }); + } + + function toggleWifi() { + var cmd = wifiEnabled ? "nmcli radio wifi off" : "nmcli radio wifi on"; + Quickshell.exec(cmd, function() { Qt.callLater(refreshNetworks); }); + } + + function connectToNetwork(ssid) { + Quickshell.execDetached("nmcli device wifi connect '" + ssid + "'"); + } + + Column { + anchors.fill: parent + spacing: 10 + + Row { + width: parent.width + spacing: 10 + Text { text: "WiFi"; font.family: fontCharcoal.name; font.pixelSize: 12; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Item { width: parent.width - 150; height: 1 } + Rectangle { + width: 40; height: 20 + color: networkTab.wifiEnabled ? Config.colors.accent : Config.colors.shadow + border.width: 1; border.color: Config.colors.outline + Rectangle { + width: 16; height: 16; y: 2 + x: networkTab.wifiEnabled ? 22 : 2 + color: Config.colors.highlight; border.width: 1; border.color: Config.colors.outline + Behavior on x { NumberAnimation { duration: 150 } } + } + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: networkTab.toggleWifi() } + } + Rectangle { + width: 24; height: 24 + color: refreshArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text; rotation: networkTab.scanning ? 360 : 0; Behavior on rotation { RotationAnimation { duration: 500 } } } + MouseArea { id: refreshArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: networkTab.refreshNetworks() } + } + } + + Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + + Rectangle { + width: parent.width; height: parent.height - 50 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + clip: true + + ListView { + id: wifiListView + anchors.fill: parent; anchors.margins: 4 + model: networkTab.wifiList + spacing: 2 + + delegate: Rectangle { + width: wifiListView.width + height: 28 + color: modelData.active ? Config.colors.accent : (wifiItemArea.containsMouse ? Config.colors.highlight : "transparent") + + Row { + anchors.fill: parent; anchors.margins: 4; spacing: 8 + Text { + text: modelData.signal > 75 ? "\ue1d8" : (modelData.signal > 50 ? "\uebe4" : (modelData.signal > 25 ? "\uebe1" : "\uf065")) + font.family: iconFont.name; font.pixelSize: 16; color: modelData.active ? Config.colors.base : Config.colors.text + anchors.verticalCenter: parent.verticalCenter + } + Text { text: modelData.ssid; font.family: fontMonaco.name; font.pixelSize: 10; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight; width: 180 } + Text { text: modelData.security !== "" ? "\ue897" : ""; font.family: iconFont.name; font.pixelSize: 12; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } + Item { width: 1; height: 1; Layout.fillWidth: true } + Text { text: modelData.signal + "%"; font.family: fontMonaco.name; font.pixelSize: 9; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } + } + MouseArea { id: wifiItemArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onDoubleClicked: networkTab.connectToNetwork(modelData.ssid) } + } + } + + Text { + anchors.centerIn: parent + visible: networkTab.wifiList.length === 0 + text: networkTab.scanning ? "Scanning..." : (networkTab.wifiEnabled ? "No networks found" : "WiFi disabled") + font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; opacity: 0.5 + } + } + + Text { text: "Double-click to connect"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.4 } + } + } + + // === BLUETOOTH TAB === + Item { + id: bluetoothTab + anchors.fill: parent + anchors.margins: 12 + visible: settingsWindow.currentTab === 3 + + property var deviceList: [] + property bool btEnabled: true + property bool scanning: false + + Component.onCompleted: refreshDevices() + + function refreshDevices() { + scanning = true; + Quickshell.exec("bluetoothctl show 2>/dev/null | grep 'Powered:' | awk '{print $2}'", function(result) { + btEnabled = result.stdout.trim() === "yes"; + }); + Quickshell.exec("bluetoothctl devices", function(result) { + var lines = result.stdout.trim().split('\n'); + var devices = []; + for (var i = 0; i < lines.length; i++) { + var match = lines[i].match(/Device ([A-F0-9:]+) (.+)/); + if (match) { + devices.push({ mac: match[1], name: match[2], connected: false }); + } + } + // Check connected devices + Quickshell.exec("bluetoothctl info 2>/dev/null | grep -E 'Device|Connected'", function(infoResult) { + var infoLines = infoResult.stdout.split('\n'); + var connectedMacs = []; + for (var j = 0; j < infoLines.length; j++) { + if (infoLines[j].includes("Connected: yes")) { + // Previous line has MAC + for (var k = j - 1; k >= 0; k--) { + var macMatch = infoLines[k].match(/Device ([A-F0-9:]+)/); + if (macMatch) { connectedMacs.push(macMatch[1]); break; } + } + } + } + for (var d = 0; d < devices.length; d++) { + devices[d].connected = connectedMacs.indexOf(devices[d].mac) !== -1; + } + deviceList = devices; + scanning = false; + }); + }); + } + + function toggleBluetooth() { + var cmd = btEnabled ? "bluetoothctl power off" : "bluetoothctl power on"; + Quickshell.exec(cmd, function() { Qt.callLater(refreshDevices); }); + } + + function connectDevice(mac) { + Quickshell.execDetached("bluetoothctl connect " + mac); + } + + function disconnectDevice(mac) { + Quickshell.execDetached("bluetoothctl disconnect " + mac); + } + + function startScan() { + Quickshell.exec("bluetoothctl --timeout 5 scan on", function() { refreshDevices(); }); + } + + Column { + anchors.fill: parent + spacing: 10 + + Row { + width: parent.width + spacing: 10 + Text { text: "Bluetooth"; font.family: fontCharcoal.name; font.pixelSize: 12; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Item { width: parent.width - 180; height: 1 } + Rectangle { + width: 40; height: 20 + color: bluetoothTab.btEnabled ? Config.colors.accent : Config.colors.shadow + border.width: 1; border.color: Config.colors.outline + Rectangle { + width: 16; height: 16; y: 2 + x: bluetoothTab.btEnabled ? 22 : 2 + color: Config.colors.highlight; border.width: 1; border.color: Config.colors.outline + Behavior on x { NumberAnimation { duration: 150 } } + } + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.toggleBluetooth() } + } + Rectangle { + width: 24; height: 24 + color: btScanArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue1a7"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text } + MouseArea { id: btScanArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.startScan() } + } + Rectangle { + width: 24; height: 24 + color: btRefreshArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text } + MouseArea { id: btRefreshArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.refreshDevices() } + } + } + + Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + + Rectangle { + width: parent.width; height: parent.height - 50 + color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + clip: true + + ListView { + id: btListView + anchors.fill: parent; anchors.margins: 4 + model: bluetoothTab.deviceList + spacing: 2 + + delegate: Rectangle { + width: btListView.width + height: 28 + color: modelData.connected ? Config.colors.accent : (btItemArea.containsMouse ? Config.colors.highlight : "transparent") + + Row { + anchors.fill: parent; anchors.margins: 4; spacing: 8 + Text { + text: "\ue1a7" + font.family: iconFont.name; font.pixelSize: 16; color: modelData.connected ? Config.colors.base : Config.colors.text + anchors.verticalCenter: parent.verticalCenter + } + Column { + anchors.verticalCenter: parent.verticalCenter; spacing: 0 + Text { text: modelData.name; font.family: fontMonaco.name; font.pixelSize: 10; color: modelData.connected ? Config.colors.base : Config.colors.text; elide: Text.ElideRight; width: 200 } + Text { text: modelData.mac; font.family: fontMonaco.name; font.pixelSize: 7; color: modelData.connected ? Config.colors.base : Config.colors.text; opacity: 0.5 } + } + Item { width: 1; height: 1; Layout.fillWidth: true } + Text { text: modelData.connected ? "Connected" : ""; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.base; anchors.verticalCenter: parent.verticalCenter } + } + MouseArea { + id: btItemArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor + onDoubleClicked: modelData.connected ? bluetoothTab.disconnectDevice(modelData.mac) : bluetoothTab.connectDevice(modelData.mac) + } + } + } + + Text { + anchors.centerIn: parent + visible: bluetoothTab.deviceList.length === 0 + text: bluetoothTab.scanning ? "Scanning..." : (bluetoothTab.btEnabled ? "No devices found\nClick scan to discover" : "Bluetooth disabled") + font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; opacity: 0.5; horizontalAlignment: Text.AlignHCenter + } + } + + Text { text: "Double-click to connect/disconnect"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.4 } + } + } + + // === ABOUT TAB === + Item { + anchors.fill: parent + anchors.margins: 12 + visible: settingsWindow.currentTab === 4 + Column { anchors.centerIn: parent spacing: 12 From 6eee9b0a64e26c9a70c83c183864950004ef744e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:28:48 +0300 Subject: [PATCH 04/15] fix: increase Settings window size and font sizes for better visibility - Window size: 700x560 (was 480x420) - Tab height: 36px (was 28px) - Tab font: 14px (was 11px) - Content fonts: 14-16px (was 10-11px) - Sliders: 280x28 (was 180x20) - Checkboxes: 24x24 (was 16x16) - Theme cards: 100x70 (was 70x50) - Toggle switches: 56x28 (was 40x20) - List items: 40-48px height (was 28px) - Button heights: 32-36px (was 24px) All tabs updated: Appearance, System, Network, Bluetooth, About --- configs/quickshell/shell.qml | 310 ++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 154 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 6a2ff05..41ca4aa 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -54,8 +54,8 @@ Scope { property int currentTab: 0 Rectangle { - width: 480 - height: 420 + width: 700 + height: 560 x: (settingsWindow.width - width) / 2 y: (settingsWindow.height - height) / 2 color: Config.colors.base @@ -65,14 +65,14 @@ Scope { anchors.fill: parent windowTitle: "Settings" windowTitleIcon: "\ue8b8" - windowTitleDecorationWidth: 100 + windowTitleDecorationWidth: 120 showCloseButton: true onCloseClicked: Config.openSettingsWindow = false Item { anchors.fill: parent - anchors.margins: 12 - anchors.topMargin: 32 + anchors.margins: 16 + anchors.topMargin: 40 clip: true // Tab Bar @@ -81,21 +81,21 @@ Scope { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - spacing: 2 + spacing: 3 Repeater { model: ["Appearance", "System", "Network", "Bluetooth", "About"] Rectangle { - width: (parent.width - 8) / 5 - height: 28 + width: (parent.width - 12) / 5 + height: 36 color: settingsWindow.currentTab === index ? Config.colors.highlight : Config.colors.shadow - border.width: 1 + border.width: 2 border.color: Config.colors.outline Rectangle { anchors.fill: parent - anchors.margins: 1 - anchors.bottomMargin: settingsWindow.currentTab === index ? 0 : 1 + anchors.margins: 2 + anchors.bottomMargin: settingsWindow.currentTab === index ? 0 : 2 color: "transparent" border.width: 1 border.color: settingsWindow.currentTab === index ? Config.colors.highlight : Config.colors.base @@ -105,7 +105,7 @@ Scope { anchors.centerIn: parent text: modelData font.family: fontCharcoal.name - font.pixelSize: 11 + font.pixelSize: 14 color: Config.colors.text } @@ -131,35 +131,35 @@ Scope { Rectangle { anchors.fill: parent - anchors.margins: 8 + anchors.margins: 12 color: Config.colors.base - border.width: 1 + border.width: 2 border.color: Config.colors.shadow // === APPEARANCE TAB === Item { anchors.fill: parent - anchors.margins: 12 + anchors.margins: 20 visible: settingsWindow.currentTab === 0 Column { anchors.fill: parent - spacing: 14 + spacing: 20 // Font Size Column { width: parent.width - spacing: 4 - Text { text: "Font Size"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + spacing: 8 + Text { text: "Font Size"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } Row { - spacing: 10 + spacing: 16 Rectangle { - width: 180; height: 20 + width: 280; height: 28 color: Config.colors.shadow - border.width: 1; border.color: Config.colors.outline + border.width: 2; border.color: Config.colors.outline Rectangle { - anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 2 - width: (Config.settings.bar.fontSize - 8) / 12 * (parent.width - 4) + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 3 + width: (Config.settings.bar.fontSize - 8) / 12 * (parent.width - 6) color: Config.colors.accent } MouseArea { @@ -169,24 +169,24 @@ Scope { } } } - Text { text: Config.settings.bar.fontSize + "px"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: Config.settings.bar.fontSize + "px"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } } } // Tray Icon Size Column { width: parent.width - spacing: 4 - Text { text: "Tray Icon Size"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + spacing: 8 + Text { text: "Tray Icon Size"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } Row { - spacing: 10 + spacing: 16 Rectangle { - width: 180; height: 20 + width: 280; height: 28 color: Config.colors.shadow - border.width: 1; border.color: Config.colors.outline + border.width: 2; border.color: Config.colors.outline Rectangle { - anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 2 - width: (Config.settings.bar.trayIconSize - 12) / 12 * (parent.width - 4) + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 3 + width: (Config.settings.bar.trayIconSize - 12) / 12 * (parent.width - 6) color: Config.colors.accent } MouseArea { @@ -196,60 +196,60 @@ Scope { } } } - Text { text: Config.settings.bar.trayIconSize + "px"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: Config.settings.bar.trayIconSize + "px"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } } } // Checkboxes Row { - spacing: 8 + spacing: 12 Rectangle { - width: 16; height: 16 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: Config.settings.bar.monochromeTrayIcons ? "✓" : ""; font.pixelSize: 12; font.bold: true; color: Config.colors.text } + width: 24; height: 24 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: Config.settings.bar.monochromeTrayIcons ? "✓" : ""; font.pixelSize: 18; font.bold: true; color: Config.colors.text } MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.bar.monochromeTrayIcons = !Config.settings.bar.monochromeTrayIcons } } - Text { text: "Monochrome Tray Icons"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Monochrome Tray Icons"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } } Row { - spacing: 8 + spacing: 12 Rectangle { - width: 16; height: 16 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: Config.settings.militaryTimeClockFormat ? "✓" : ""; font.pixelSize: 12; font.bold: true; color: Config.colors.text } + width: 24; height: 24 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: Config.settings.militaryTimeClockFormat ? "✓" : ""; font.pixelSize: 18; font.bold: true; color: Config.colors.text } MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.militaryTimeClockFormat = !Config.settings.militaryTimeClockFormat } } - Text { text: "24-Hour Clock Format"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: "24-Hour Clock Format"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } } // Theme Selector Column { width: parent.width - spacing: 6 - Text { text: "Theme"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + spacing: 10 + Text { text: "Theme"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } Flow { width: parent.width - spacing: 6 + spacing: 10 Repeater { model: Object.keys(Config.themes) Rectangle { property string themeName: modelData - width: 70; height: 50 + width: 100; height: 70 color: Config.settings.currentTheme === themeName ? Config.colors.highlight : Config.colors.shadow - border.width: Config.settings.currentTheme === themeName ? 2 : 1 + border.width: Config.settings.currentTheme === themeName ? 3 : 2 border.color: Config.settings.currentTheme === themeName ? Config.themes[themeName].accent : Config.colors.outline Column { anchors.centerIn: parent - spacing: 3 + spacing: 6 Row { anchors.horizontalCenter: parent.horizontalCenter - spacing: 2 - Rectangle { width: 10; height: 10; color: Config.themes[themeName].base; border.width: 1; border.color: Config.themes[themeName].outline } - Rectangle { width: 10; height: 10; color: Config.themes[themeName].accent; border.width: 1; border.color: Config.themes[themeName].outline } - Rectangle { width: 10; height: 10; color: Config.themes[themeName].shadow; border.width: 1; border.color: Config.themes[themeName].outline } + spacing: 4 + Rectangle { width: 16; height: 16; color: Config.themes[themeName].base; border.width: 2; border.color: Config.themes[themeName].outline } + Rectangle { width: 16; height: 16; color: Config.themes[themeName].accent; border.width: 2; border.color: Config.themes[themeName].outline } + Rectangle { width: 16; height: 16; color: Config.themes[themeName].shadow; border.width: 2; border.color: Config.themes[themeName].outline } } - Text { anchors.horizontalCenter: parent.horizontalCenter; text: themeName; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: themeName; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text } } MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.settings.currentTheme = themeName } } @@ -262,87 +262,89 @@ Scope { // === SYSTEM TAB === Item { anchors.fill: parent - anchors.margins: 12 + anchors.margins: 20 visible: settingsWindow.currentTab === 1 Column { anchors.fill: parent - spacing: 14 + spacing: 20 // Terminal Column { width: parent.width - spacing: 4 - Text { text: "Terminal Application"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + spacing: 8 + Text { text: "Terminal Application"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } Row { - spacing: 6 + spacing: 12 Rectangle { - width: 180; height: 24 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + width: 280; height: 32 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline TextInput { id: terminalInput - anchors.fill: parent; anchors.margins: 4 + anchors.fill: parent; anchors.margins: 6 text: Config.hyprlandTerminal - font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text + font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text clip: true; selectByMouse: true; activeFocusOnPress: true + verticalAlignment: TextInput.AlignVCenter } } Rectangle { - width: 45; height: 24 + width: 70; height: 32 color: termSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 13; color: Config.colors.text } MouseArea { id: termSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandTerminal(terminalInput.text) } } } - Text { text: "→ hyprland.conf: $terminal"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.5 } + Text { text: "→ hyprland.conf: $terminal"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; opacity: 0.5 } } // File Manager Column { width: parent.width - spacing: 4 - Text { text: "File Manager Application"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + spacing: 8 + Text { text: "File Manager Application"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } Row { - spacing: 6 + spacing: 12 Rectangle { - width: 180; height: 24 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + width: 280; height: 32 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline TextInput { id: filesInput - anchors.fill: parent; anchors.margins: 4 + anchors.fill: parent; anchors.margins: 6 text: Config.hyprlandFileManager - font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text + font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text clip: true; selectByMouse: true; activeFocusOnPress: true + verticalAlignment: TextInput.AlignVCenter } } Rectangle { - width: 45; height: 24 + width: 70; height: 32 color: filesSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 13; color: Config.colors.text } MouseArea { id: filesSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandFileManager(filesInput.text) } } } - Text { text: "→ hyprland.conf: $fileManager"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.5 } + Text { text: "→ hyprland.conf: $fileManager"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; opacity: 0.5 } } - Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } - Text { text: "System Information"; font.family: fontCharcoal.name; font.pixelSize: 11; font.bold: true; color: Config.colors.text } + Text { text: "System Information"; font.family: fontCharcoal.name; font.pixelSize: 16; font.bold: true; color: Config.colors.text } Grid { - columns: 2; columnSpacing: 10; rowSpacing: 6 - Text { text: "OS:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: Config.settings.systemDetails.osName; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: "Version:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: Config.settings.systemDetails.osVersion; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: "CPU:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: Config.settings.systemDetails.cpu; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: "GPU:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: Config.settings.systemDetails.gpu; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: "RAM:"; font.family: fontCharcoal.name; font.pixelSize: 10; color: Config.colors.text } - Text { text: Config.settings.systemDetails.ram; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + columns: 2; columnSpacing: 20; rowSpacing: 12 + Text { text: "OS:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: Config.settings.systemDetails.osName; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: "Version:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: Config.settings.systemDetails.osVersion; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: "CPU:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: Config.settings.systemDetails.cpu; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: "GPU:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: Config.settings.systemDetails.gpu; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: "RAM:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: Config.settings.systemDetails.ram; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } } } } @@ -351,7 +353,7 @@ Scope { Item { id: networkTab anchors.fill: parent - anchors.margins: 12 + anchors.margins: 20 visible: settingsWindow.currentTab === 2 property var wifiList: [] @@ -394,63 +396,63 @@ Scope { Column { anchors.fill: parent - spacing: 10 + spacing: 16 Row { width: parent.width - spacing: 10 - Text { text: "WiFi"; font.family: fontCharcoal.name; font.pixelSize: 12; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } - Item { width: parent.width - 150; height: 1 } + spacing: 16 + Text { text: "WiFi"; font.family: fontCharcoal.name; font.pixelSize: 16; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Item { width: parent.width - 200; height: 1 } Rectangle { - width: 40; height: 20 + width: 56; height: 28 color: networkTab.wifiEnabled ? Config.colors.accent : Config.colors.shadow - border.width: 1; border.color: Config.colors.outline + border.width: 2; border.color: Config.colors.outline Rectangle { - width: 16; height: 16; y: 2 - x: networkTab.wifiEnabled ? 22 : 2 - color: Config.colors.highlight; border.width: 1; border.color: Config.colors.outline + width: 22; height: 22; y: 3 + x: networkTab.wifiEnabled ? 31 : 3 + color: Config.colors.highlight; border.width: 2; border.color: Config.colors.outline Behavior on x { NumberAnimation { duration: 150 } } } MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: networkTab.toggleWifi() } } Rectangle { - width: 24; height: 24 + width: 36; height: 36 color: refreshArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text; rotation: networkTab.scanning ? 360 : 0; Behavior on rotation { RotationAnimation { duration: 500 } } } + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 20; color: Config.colors.text; rotation: networkTab.scanning ? 360 : 0; Behavior on rotation { RotationAnimation { duration: 500 } } } MouseArea { id: refreshArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: networkTab.refreshNetworks() } } } - Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } Rectangle { - width: parent.width; height: parent.height - 50 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + width: parent.width; height: parent.height - 80 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline clip: true ListView { id: wifiListView - anchors.fill: parent; anchors.margins: 4 + anchors.fill: parent; anchors.margins: 6 model: networkTab.wifiList - spacing: 2 + spacing: 4 delegate: Rectangle { width: wifiListView.width - height: 28 + height: 40 color: modelData.active ? Config.colors.accent : (wifiItemArea.containsMouse ? Config.colors.highlight : "transparent") Row { - anchors.fill: parent; anchors.margins: 4; spacing: 8 + anchors.fill: parent; anchors.margins: 8; spacing: 12 Text { text: modelData.signal > 75 ? "\ue1d8" : (modelData.signal > 50 ? "\uebe4" : (modelData.signal > 25 ? "\uebe1" : "\uf065")) - font.family: iconFont.name; font.pixelSize: 16; color: modelData.active ? Config.colors.base : Config.colors.text + font.family: iconFont.name; font.pixelSize: 22; color: modelData.active ? Config.colors.base : Config.colors.text anchors.verticalCenter: parent.verticalCenter } - Text { text: modelData.ssid; font.family: fontMonaco.name; font.pixelSize: 10; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight; width: 180 } - Text { text: modelData.security !== "" ? "\ue897" : ""; font.family: iconFont.name; font.pixelSize: 12; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } + Text { text: modelData.ssid; font.family: fontMonaco.name; font.pixelSize: 14; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight; width: 320 } + Text { text: modelData.security !== "" ? "\ue897" : ""; font.family: iconFont.name; font.pixelSize: 18; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } Item { width: 1; height: 1; Layout.fillWidth: true } - Text { text: modelData.signal + "%"; font.family: fontMonaco.name; font.pixelSize: 9; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } + Text { text: modelData.signal + "%"; font.family: fontMonaco.name; font.pixelSize: 13; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } } MouseArea { id: wifiItemArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onDoubleClicked: networkTab.connectToNetwork(modelData.ssid) } } @@ -460,11 +462,11 @@ Scope { anchors.centerIn: parent visible: networkTab.wifiList.length === 0 text: networkTab.scanning ? "Scanning..." : (networkTab.wifiEnabled ? "No networks found" : "WiFi disabled") - font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; opacity: 0.5 + font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; opacity: 0.5 } } - Text { text: "Double-click to connect"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.4 } + Text { text: "Double-click to connect"; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text; opacity: 0.4 } } } @@ -472,7 +474,7 @@ Scope { Item { id: bluetoothTab anchors.fill: parent - anchors.margins: 12 + anchors.margins: 20 visible: settingsWindow.currentTab === 3 property var deviceList: [] @@ -536,73 +538,73 @@ Scope { Column { anchors.fill: parent - spacing: 10 + spacing: 16 Row { width: parent.width - spacing: 10 - Text { text: "Bluetooth"; font.family: fontCharcoal.name; font.pixelSize: 12; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } - Item { width: parent.width - 180; height: 1 } + spacing: 16 + Text { text: "Bluetooth"; font.family: fontCharcoal.name; font.pixelSize: 16; font.bold: true; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Item { width: parent.width - 240; height: 1 } Rectangle { - width: 40; height: 20 + width: 56; height: 28 color: bluetoothTab.btEnabled ? Config.colors.accent : Config.colors.shadow - border.width: 1; border.color: Config.colors.outline + border.width: 2; border.color: Config.colors.outline Rectangle { - width: 16; height: 16; y: 2 - x: bluetoothTab.btEnabled ? 22 : 2 - color: Config.colors.highlight; border.width: 1; border.color: Config.colors.outline + width: 22; height: 22; y: 3 + x: bluetoothTab.btEnabled ? 31 : 3 + color: Config.colors.highlight; border.width: 2; border.color: Config.colors.outline Behavior on x { NumberAnimation { duration: 150 } } } MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.toggleBluetooth() } } Rectangle { - width: 24; height: 24 + width: 36; height: 36 color: btScanArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "\ue1a7"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text } + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue1a7"; font.family: iconFont.name; font.pixelSize: 20; color: Config.colors.text } MouseArea { id: btScanArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.startScan() } } Rectangle { - width: 24; height: 24 + width: 36; height: 36 color: btRefreshArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 1; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 14; color: Config.colors.text } + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "\ue5d5"; font.family: iconFont.name; font.pixelSize: 20; color: Config.colors.text } MouseArea { id: btRefreshArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: bluetoothTab.refreshDevices() } } } - Rectangle { width: parent.width; height: 1; color: Config.colors.outline; opacity: 0.5 } + Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } Rectangle { - width: parent.width; height: parent.height - 50 - color: Config.colors.shadow; border.width: 1; border.color: Config.colors.outline + width: parent.width; height: parent.height - 80 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline clip: true ListView { id: btListView - anchors.fill: parent; anchors.margins: 4 + anchors.fill: parent; anchors.margins: 6 model: bluetoothTab.deviceList - spacing: 2 + spacing: 4 delegate: Rectangle { width: btListView.width - height: 28 + height: 48 color: modelData.connected ? Config.colors.accent : (btItemArea.containsMouse ? Config.colors.highlight : "transparent") Row { - anchors.fill: parent; anchors.margins: 4; spacing: 8 + anchors.fill: parent; anchors.margins: 8; spacing: 12 Text { text: "\ue1a7" - font.family: iconFont.name; font.pixelSize: 16; color: modelData.connected ? Config.colors.base : Config.colors.text + font.family: iconFont.name; font.pixelSize: 24; color: modelData.connected ? Config.colors.base : Config.colors.text anchors.verticalCenter: parent.verticalCenter } Column { - anchors.verticalCenter: parent.verticalCenter; spacing: 0 - Text { text: modelData.name; font.family: fontMonaco.name; font.pixelSize: 10; color: modelData.connected ? Config.colors.base : Config.colors.text; elide: Text.ElideRight; width: 200 } - Text { text: modelData.mac; font.family: fontMonaco.name; font.pixelSize: 7; color: modelData.connected ? Config.colors.base : Config.colors.text; opacity: 0.5 } + anchors.verticalCenter: parent.verticalCenter; spacing: 2 + Text { text: modelData.name; font.family: fontMonaco.name; font.pixelSize: 14; color: modelData.connected ? Config.colors.base : Config.colors.text; elide: Text.ElideRight; width: 350 } + Text { text: modelData.mac; font.family: fontMonaco.name; font.pixelSize: 11; color: modelData.connected ? Config.colors.base : Config.colors.text; opacity: 0.5 } } Item { width: 1; height: 1; Layout.fillWidth: true } - Text { text: modelData.connected ? "Connected" : ""; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.base; anchors.verticalCenter: parent.verticalCenter } + Text { text: modelData.connected ? "Connected" : ""; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.base; anchors.verticalCenter: parent.verticalCenter } } MouseArea { id: btItemArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor @@ -615,29 +617,29 @@ Scope { anchors.centerIn: parent visible: bluetoothTab.deviceList.length === 0 text: bluetoothTab.scanning ? "Scanning..." : (bluetoothTab.btEnabled ? "No devices found\nClick scan to discover" : "Bluetooth disabled") - font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; opacity: 0.5; horizontalAlignment: Text.AlignHCenter + font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; opacity: 0.5; horizontalAlignment: Text.AlignHCenter } } - Text { text: "Double-click to connect/disconnect"; font.family: fontMonaco.name; font.pixelSize: 8; color: Config.colors.text; opacity: 0.4 } + Text { text: "Double-click to connect/disconnect"; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text; opacity: 0.4 } } } // === ABOUT TAB === Item { anchors.fill: parent - anchors.margins: 12 + anchors.margins: 20 visible: settingsWindow.currentTab === 4 Column { anchors.centerIn: parent - spacing: 12 - Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Linux Retroism"; font.family: fontCharcoal.name; font.pixelSize: 20; color: Config.colors.text } - Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Version " + Config.settings.version; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } - Rectangle { width: 200; height: 1; color: Config.colors.outline; anchors.horizontalCenter: parent.horizontalCenter } - Text { anchors.horizontalCenter: parent.horizontalCenter; text: "A 1980-1990's retro UI aesthetic\nfor Hyprland & Sway"; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text; horizontalAlignment: Text.AlignHCenter } - Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Created by diinki"; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.accent } - Text { anchors.horizontalCenter: parent.horizontalCenter; text: "github.com/diinki/linux-retroism"; font.family: fontMonaco.name; font.pixelSize: 9; color: Config.colors.text; opacity: 0.6 } + spacing: 18 + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Linux Retroism"; font.family: fontCharcoal.name; font.pixelSize: 28; color: Config.colors.text } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Version " + Config.settings.version; font.family: fontMonaco.name; font.pixelSize: 16; color: Config.colors.text } + Rectangle { width: 300; height: 2; color: Config.colors.outline; anchors.horizontalCenter: parent.horizontalCenter } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "A 1980-1990's retro UI aesthetic\nfor Hyprland & Sway"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; horizontalAlignment: Text.AlignHCenter } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "Created by diinki"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.accent } + Text { anchors.horizontalCenter: parent.horizontalCenter; text: "github.com/diinki/linux-retroism"; font.family: fontMonaco.name; font.pixelSize: 13; color: Config.colors.text; opacity: 0.6 } } } } From 17a60e49813e986e83a8087f9522b559afe524c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:34:06 +0300 Subject: [PATCH 05/15] fix: remove Layout.fillWidth from Row children (only works in RowLayout) --- configs/quickshell/shell.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 41ca4aa..dc7b15a 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -451,7 +451,6 @@ Scope { } Text { text: modelData.ssid; font.family: fontMonaco.name; font.pixelSize: 14; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight; width: 320 } Text { text: modelData.security !== "" ? "\ue897" : ""; font.family: iconFont.name; font.pixelSize: 18; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } - Item { width: 1; height: 1; Layout.fillWidth: true } Text { text: modelData.signal + "%"; font.family: fontMonaco.name; font.pixelSize: 13; color: modelData.active ? Config.colors.base : Config.colors.text; anchors.verticalCenter: parent.verticalCenter; opacity: 0.6 } } MouseArea { id: wifiItemArea; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor; onDoubleClicked: networkTab.connectToNetwork(modelData.ssid) } @@ -603,7 +602,6 @@ Scope { Text { text: modelData.name; font.family: fontMonaco.name; font.pixelSize: 14; color: modelData.connected ? Config.colors.base : Config.colors.text; elide: Text.ElideRight; width: 350 } Text { text: modelData.mac; font.family: fontMonaco.name; font.pixelSize: 11; color: modelData.connected ? Config.colors.base : Config.colors.text; opacity: 0.5 } } - Item { width: 1; height: 1; Layout.fillWidth: true } Text { text: modelData.connected ? "Connected" : ""; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.base; anchors.verticalCenter: parent.verticalCenter } } MouseArea { From a170743b2f26f24ac889962807fdaf12e32b32bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:42:11 +0300 Subject: [PATCH 06/15] fix: replace Quickshell.exec with Process components - Quickshell.exec/execDetached don't exist in Quickshell API - Use Process components from Quickshell.Io module instead - Network tab: wifiListProc, wifiStatusProc, wifiToggleProc, wifiConnectProc - Bluetooth tab: btStatusProc, btDevicesProc, btToggleProc, btConnectProc, btScanProc - Power menu: powerCmdProc, hyprlockThemeProc - Config.qml: Add null checks for stdout in grep processes --- configs/quickshell/Config.qml | 12 ++- configs/quickshell/shell.qml | 195 ++++++++++++++++++++++------------ 2 files changed, 133 insertions(+), 74 deletions(-) diff --git a/configs/quickshell/Config.qml b/configs/quickshell/Config.qml index ccfd23f..e382b98 100644 --- a/configs/quickshell/Config.qml +++ b/configs/quickshell/Config.qml @@ -17,8 +17,10 @@ Singleton { command: ["grep", "^\\$terminal", hyprlandConfigPath] running: true onExited: { - var match = stdout.match(/\$terminal\s*=\s*(.+)/); - if (match) hyprlandTerminal = match[1].trim(); + if (stdout && stdout.length > 0) { + var match = stdout.match(/\$terminal\s*=\s*(.+)/); + if (match) hyprlandTerminal = match[1].trim(); + } } } @@ -27,8 +29,10 @@ Singleton { command: ["grep", "^\\$fileManager", hyprlandConfigPath] running: true onExited: { - var match = stdout.match(/\$fileManager\s*=\s*(.+)/); - if (match) hyprlandFileManager = match[1].trim(); + if (stdout && stdout.length > 0) { + var match = stdout.match(/\$fileManager\s*=\s*(.+)/); + if (match) hyprlandFileManager = match[1].trim(); + } } } diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index dc7b15a..0612a05 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -9,6 +9,7 @@ import QtQuick import Quickshell +import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland @@ -363,35 +364,59 @@ Scope { Component.onCompleted: refreshNetworks() - function refreshNetworks() { - scanning = true; - Quickshell.exec("nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE device wifi list", function(result) { - var lines = result.stdout.trim().split('\n'); - var networks = []; - currentWifi = ""; - for (var i = 0; i < lines.length; i++) { - var parts = lines[i].split(':'); - if (parts[0] && parts[0].trim() !== "") { - var net = { ssid: parts[0], signal: parseInt(parts[1]) || 0, security: parts[2] || "", active: parts[3] === "*" }; - networks.push(net); - if (net.active) currentWifi = net.ssid; + Process { + id: wifiListProc + command: ["bash", "-c", "nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE device wifi list 2>/dev/null"] + onExited: { + if (stdout && stdout.length > 0) { + var lines = stdout.trim().split('\n'); + var networks = []; + networkTab.currentWifi = ""; + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].split(':'); + if (parts[0] && parts[0].trim() !== "") { + var net = { ssid: parts[0], signal: parseInt(parts[1]) || 0, security: parts[2] || "", active: parts[3] === "*" }; + networks.push(net); + if (net.active) networkTab.currentWifi = net.ssid; + } } + networkTab.wifiList = networks; } - wifiList = networks; - scanning = false; - }); - Quickshell.exec("nmcli radio wifi", function(result) { - wifiEnabled = result.stdout.trim() === "enabled"; - }); + networkTab.scanning = false; + } + } + + Process { + id: wifiStatusProc + command: ["nmcli", "radio", "wifi"] + onExited: { + if (stdout) networkTab.wifiEnabled = stdout.trim() === "enabled"; + } + } + + Process { + id: wifiToggleProc + onExited: { Qt.callLater(networkTab.refreshNetworks); } + } + + Process { + id: wifiConnectProc + } + + function refreshNetworks() { + scanning = true; + wifiListProc.running = true; + wifiStatusProc.running = true; } function toggleWifi() { - var cmd = wifiEnabled ? "nmcli radio wifi off" : "nmcli radio wifi on"; - Quickshell.exec(cmd, function() { Qt.callLater(refreshNetworks); }); + wifiToggleProc.command = ["nmcli", "radio", "wifi", wifiEnabled ? "off" : "on"]; + wifiToggleProc.running = true; } function connectToNetwork(ssid) { - Quickshell.execDetached("nmcli device wifi connect '" + ssid + "'"); + wifiConnectProc.command = ["nmcli", "device", "wifi", "connect", ssid]; + wifiConnectProc.running = true; } Column { @@ -479,60 +504,75 @@ Scope { property var deviceList: [] property bool btEnabled: true property bool scanning: false + property var pendingDevices: [] Component.onCompleted: refreshDevices() - function refreshDevices() { - scanning = true; - Quickshell.exec("bluetoothctl show 2>/dev/null | grep 'Powered:' | awk '{print $2}'", function(result) { - btEnabled = result.stdout.trim() === "yes"; - }); - Quickshell.exec("bluetoothctl devices", function(result) { - var lines = result.stdout.trim().split('\n'); - var devices = []; - for (var i = 0; i < lines.length; i++) { - var match = lines[i].match(/Device ([A-F0-9:]+) (.+)/); - if (match) { - devices.push({ mac: match[1], name: match[2], connected: false }); - } - } - // Check connected devices - Quickshell.exec("bluetoothctl info 2>/dev/null | grep -E 'Device|Connected'", function(infoResult) { - var infoLines = infoResult.stdout.split('\n'); - var connectedMacs = []; - for (var j = 0; j < infoLines.length; j++) { - if (infoLines[j].includes("Connected: yes")) { - // Previous line has MAC - for (var k = j - 1; k >= 0; k--) { - var macMatch = infoLines[k].match(/Device ([A-F0-9:]+)/); - if (macMatch) { connectedMacs.push(macMatch[1]); break; } - } + Process { + id: btStatusProc + command: ["bash", "-c", "bluetoothctl show 2>/dev/null | grep 'Powered:' | awk '{print $2}'"] + onExited: { + if (stdout) bluetoothTab.btEnabled = stdout.trim() === "yes"; + } + } + + Process { + id: btDevicesProc + command: ["bluetoothctl", "devices"] + onExited: { + if (stdout && stdout.length > 0) { + var lines = stdout.trim().split('\n'); + var devices = []; + for (var i = 0; i < lines.length; i++) { + var match = lines[i].match(/Device ([A-F0-9:]+) (.+)/); + if (match) { + devices.push({ mac: match[1], name: match[2], connected: false }); } } - for (var d = 0; d < devices.length; d++) { - devices[d].connected = connectedMacs.indexOf(devices[d].mac) !== -1; - } - deviceList = devices; - scanning = false; - }); - }); + bluetoothTab.deviceList = devices; + } + bluetoothTab.scanning = false; + } + } + + Process { + id: btToggleProc + onExited: { Qt.callLater(bluetoothTab.refreshDevices); } + } + + Process { + id: btConnectProc + } + + Process { + id: btScanProc + command: ["bluetoothctl", "--timeout", "5", "scan", "on"] + onExited: { bluetoothTab.refreshDevices(); } + } + + function refreshDevices() { + scanning = true; + btStatusProc.running = true; + btDevicesProc.running = true; } function toggleBluetooth() { - var cmd = btEnabled ? "bluetoothctl power off" : "bluetoothctl power on"; - Quickshell.exec(cmd, function() { Qt.callLater(refreshDevices); }); + btToggleProc.command = ["bluetoothctl", "power", btEnabled ? "off" : "on"]; + btToggleProc.running = true; } function connectDevice(mac) { - Quickshell.execDetached("bluetoothctl connect " + mac); + btConnectProc.command = ["bluetoothctl", "connect", mac]; + btConnectProc.running = true; } function disconnectDevice(mac) { - Quickshell.execDetached("bluetoothctl disconnect " + mac); + btConnectProc.command = ["bluetoothctl", "disconnect", mac]; + btConnectProc.running = true; } function startScan() { - Quickshell.exec("bluetoothctl --timeout 5 scan on", function() { refreshDevices(); }); + btScanProc.running = true; } Column { @@ -670,18 +710,33 @@ Scope { property string powerMenuState: "" + Process { + id: powerCmdProc + } + + Process { + id: hyprlockThemeProc + } + function updateHyprlockTheme() { var c = Config.colors; - var cmd = "sed -i " + - "-e 's/outer_color = rgb([^)]*)/outer_color = rgb(" + c.outline.slice(1) + ")/g' " + - "-e 's/inner_color = rgb([^)]*)/inner_color = rgb(" + c.base.slice(1) + ")/g' " + - "-e 's/font_color = rgb([^)]*)/font_color = rgb(" + c.text.slice(1) + ")/g' " + - "-e 's/check_color = rgb([^)]*)/check_color = rgb(" + c.accent.slice(1) + ")/g' " + - "-e 's/fail_color = rgb([^)]*)/fail_color = rgb(" + c.urgent.slice(1) + ")/g' " + - "-e 's/color = rgb(3e3d38)/color = rgb(" + c.text.slice(1) + ")/g' " + - "-e 's/color = rgb(626335)/color = rgb(" + c.accent.slice(1) + ")/g' " + - "/home/ozhan/.config/hypr/hyprlock.conf"; - Quickshell.execDetached(cmd); + var sedArgs = [ + "-i", + "-e", "s/outer_color = rgb([^)]*)/outer_color = rgb(" + c.outline.slice(1) + ")/g", + "-e", "s/inner_color = rgb([^)]*)/inner_color = rgb(" + c.base.slice(1) + ")/g", + "-e", "s/font_color = rgb([^)]*)/font_color = rgb(" + c.text.slice(1) + ")/g", + "-e", "s/check_color = rgb([^)]*)/check_color = rgb(" + c.accent.slice(1) + ")/g", + "-e", "s/fail_color = rgb([^)]*)/fail_color = rgb(" + c.urgent.slice(1) + ")/g", + "/home/ozhan/.config/hypr/hyprlock.conf" + ]; + hyprlockThemeProc.command = ["sed"].concat(sedArgs); + hyprlockThemeProc.running = true; + } + + function runPowerCommand(cmd) { + var parts = cmd.split(" "); + powerCmdProc.command = parts; + powerCmdProc.running = true; } property var powerActions: { @@ -752,7 +807,7 @@ Scope { if (actionKey === "lock") { powerMenu.updateHyprlockTheme(); } - Quickshell.execDetached(powerMenu.powerActions[actionKey].cmd); + powerMenu.runPowerCommand(powerMenu.powerActions[actionKey].cmd); Config.openPowerMenu = false; } } @@ -795,7 +850,7 @@ Scope { MouseArea { id: yesArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor onClicked: { - if (powerMenu.powerMenuState !== "") Quickshell.execDetached(powerMenu.powerActions[powerMenu.powerMenuState].cmd); + if (powerMenu.powerMenuState !== "") powerMenu.runPowerCommand(powerMenu.powerActions[powerMenu.powerMenuState].cmd); powerMenu.powerMenuState = ""; Config.openPowerMenu = false; } } From d94022d841163f8c636e899c867c680fc6726e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:46:48 +0300 Subject: [PATCH 07/15] fix: use SplitParser for Process stdout instead of direct property Quickshell Process doesn't have direct stdout property. Use SplitParser with onRead callback to read command output. - WiFi list and status parsing - Bluetooth devices and status parsing - Hyprland config reading - Fixed Bluetooth MAC regex to include lowercase (a-f) --- configs/quickshell/Config.qml | 12 ++++++------ configs/quickshell/shell.qml | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/configs/quickshell/Config.qml b/configs/quickshell/Config.qml index e382b98..ca69e1f 100644 --- a/configs/quickshell/Config.qml +++ b/configs/quickshell/Config.qml @@ -16,9 +16,9 @@ Singleton { id: readTerminal command: ["grep", "^\\$terminal", hyprlandConfigPath] running: true - onExited: { - if (stdout && stdout.length > 0) { - var match = stdout.match(/\$terminal\s*=\s*(.+)/); + stdout: SplitParser { + onRead: data => { + var match = data.match(/\$terminal\s*=\s*(.+)/); if (match) hyprlandTerminal = match[1].trim(); } } @@ -28,9 +28,9 @@ Singleton { id: readFileManager command: ["grep", "^\\$fileManager", hyprlandConfigPath] running: true - onExited: { - if (stdout && stdout.length > 0) { - var match = stdout.match(/\$fileManager\s*=\s*(.+)/); + stdout: SplitParser { + onRead: data => { + var match = data.match(/\$fileManager\s*=\s*(.+)/); if (match) hyprlandFileManager = match[1].trim(); } } diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 0612a05..6e29894 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -367,9 +367,9 @@ Scope { Process { id: wifiListProc command: ["bash", "-c", "nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE device wifi list 2>/dev/null"] - onExited: { - if (stdout && stdout.length > 0) { - var lines = stdout.trim().split('\n'); + stdout: SplitParser { + onRead: data => { + var lines = data.trim().split('\n'); var networks = []; networkTab.currentWifi = ""; for (var i = 0; i < lines.length; i++) { @@ -381,16 +381,16 @@ Scope { } } networkTab.wifiList = networks; + networkTab.scanning = false; } - networkTab.scanning = false; } } Process { id: wifiStatusProc command: ["nmcli", "radio", "wifi"] - onExited: { - if (stdout) networkTab.wifiEnabled = stdout.trim() === "enabled"; + stdout: SplitParser { + onRead: data => { networkTab.wifiEnabled = data.trim() === "enabled"; } } } @@ -511,27 +511,27 @@ Scope { Process { id: btStatusProc command: ["bash", "-c", "bluetoothctl show 2>/dev/null | grep 'Powered:' | awk '{print $2}'"] - onExited: { - if (stdout) bluetoothTab.btEnabled = stdout.trim() === "yes"; + stdout: SplitParser { + onRead: data => { bluetoothTab.btEnabled = data.trim() === "yes"; } } } Process { id: btDevicesProc command: ["bluetoothctl", "devices"] - onExited: { - if (stdout && stdout.length > 0) { - var lines = stdout.trim().split('\n'); + stdout: SplitParser { + onRead: data => { + var lines = data.trim().split('\n'); var devices = []; for (var i = 0; i < lines.length; i++) { - var match = lines[i].match(/Device ([A-F0-9:]+) (.+)/); + var match = lines[i].match(/Device ([A-Fa-f0-9:]+) (.+)/); if (match) { devices.push({ mac: match[1], name: match[2], connected: false }); } } bluetoothTab.deviceList = devices; + bluetoothTab.scanning = false; } - bluetoothTab.scanning = false; } } From ee84522ec7973149fc4ea2919b254d58273e1dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:50:33 +0300 Subject: [PATCH 08/15] fix: accumulate SplitParser output and process in onExited SplitParser calls onRead for each line separately. Accumulate output in property, process all data in onExited callback. --- configs/quickshell/Config.qml | 21 ++++++----- configs/quickshell/shell.qml | 66 ++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/configs/quickshell/Config.qml b/configs/quickshell/Config.qml index ca69e1f..a44fb93 100644 --- a/configs/quickshell/Config.qml +++ b/configs/quickshell/Config.qml @@ -12,15 +12,19 @@ Singleton { property string hyprlandConfigPath: "/home/ozhan/.config/hypr/hyprland.conf" // Read hyprland.conf on startup + property string terminalOutput: "" + property string fileManagerOutput: "" + Process { id: readTerminal command: ["grep", "^\\$terminal", hyprlandConfigPath] running: true stdout: SplitParser { - onRead: data => { - var match = data.match(/\$terminal\s*=\s*(.+)/); - if (match) hyprlandTerminal = match[1].trim(); - } + onRead: data => { root.terminalOutput = data; } + } + onExited: { + var match = root.terminalOutput.match(/\$terminal\s*=\s*(.+)/); + if (match) hyprlandTerminal = match[1].trim(); } } @@ -29,10 +33,11 @@ Singleton { command: ["grep", "^\\$fileManager", hyprlandConfigPath] running: true stdout: SplitParser { - onRead: data => { - var match = data.match(/\$fileManager\s*=\s*(.+)/); - if (match) hyprlandFileManager = match[1].trim(); - } + onRead: data => { root.fileManagerOutput = data; } + } + onExited: { + var match = root.fileManagerOutput.match(/\$fileManager\s*=\s*(.+)/); + if (match) hyprlandFileManager = match[1].trim(); } } diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 6e29894..9f0bf83 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -364,25 +364,30 @@ Scope { Component.onCompleted: refreshNetworks() + property string wifiOutput: "" + property string wifiStatusOutput: "" + Process { id: wifiListProc command: ["bash", "-c", "nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE device wifi list 2>/dev/null"] stdout: SplitParser { - onRead: data => { - var lines = data.trim().split('\n'); - var networks = []; - networkTab.currentWifi = ""; - for (var i = 0; i < lines.length; i++) { - var parts = lines[i].split(':'); - if (parts[0] && parts[0].trim() !== "") { - var net = { ssid: parts[0], signal: parseInt(parts[1]) || 0, security: parts[2] || "", active: parts[3] === "*" }; - networks.push(net); - if (net.active) networkTab.currentWifi = net.ssid; - } + onRead: data => { networkTab.wifiOutput += data + "\n"; } + } + onExited: { + var lines = networkTab.wifiOutput.trim().split('\n'); + var networks = []; + networkTab.currentWifi = ""; + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].split(':'); + if (parts[0] && parts[0].trim() !== "") { + var net = { ssid: parts[0], signal: parseInt(parts[1]) || 0, security: parts[2] || "", active: parts[3] === "*" }; + networks.push(net); + if (net.active) networkTab.currentWifi = net.ssid; } - networkTab.wifiList = networks; - networkTab.scanning = false; } + networkTab.wifiList = networks; + networkTab.scanning = false; + networkTab.wifiOutput = ""; } } @@ -390,7 +395,10 @@ Scope { id: wifiStatusProc command: ["nmcli", "radio", "wifi"] stdout: SplitParser { - onRead: data => { networkTab.wifiEnabled = data.trim() === "enabled"; } + onRead: data => { networkTab.wifiStatusOutput = data.trim(); } + } + onExited: { + networkTab.wifiEnabled = networkTab.wifiStatusOutput === "enabled"; } } @@ -508,11 +516,17 @@ Scope { Component.onCompleted: refreshDevices() + property string btStatusOutput: "" + property string btDevicesOutput: "" + Process { id: btStatusProc command: ["bash", "-c", "bluetoothctl show 2>/dev/null | grep 'Powered:' | awk '{print $2}'"] stdout: SplitParser { - onRead: data => { bluetoothTab.btEnabled = data.trim() === "yes"; } + onRead: data => { bluetoothTab.btStatusOutput = data.trim(); } + } + onExited: { + bluetoothTab.btEnabled = bluetoothTab.btStatusOutput === "yes"; } } @@ -520,18 +534,20 @@ Scope { id: btDevicesProc command: ["bluetoothctl", "devices"] stdout: SplitParser { - onRead: data => { - var lines = data.trim().split('\n'); - var devices = []; - for (var i = 0; i < lines.length; i++) { - var match = lines[i].match(/Device ([A-Fa-f0-9:]+) (.+)/); - if (match) { - devices.push({ mac: match[1], name: match[2], connected: false }); - } + onRead: data => { bluetoothTab.btDevicesOutput += data + "\n"; } + } + onExited: { + var lines = bluetoothTab.btDevicesOutput.trim().split('\n'); + var devices = []; + for (var i = 0; i < lines.length; i++) { + var match = lines[i].match(/Device ([A-Fa-f0-9:]+) (.+)/); + if (match) { + devices.push({ mac: match[1], name: match[2], connected: false }); } - bluetoothTab.deviceList = devices; - bluetoothTab.scanning = false; } + bluetoothTab.deviceList = devices; + bluetoothTab.scanning = false; + bluetoothTab.btDevicesOutput = ""; } } From 35872fd03dd985bbb07414ca0202909d3a38e1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 20:59:00 +0300 Subject: [PATCH 09/15] feat: add Brightness and Volume controls to System tab Brightness: - Uses brightnessctl for reading/setting - Slider with percentage display - Auto-detects current and max brightness Volume: - Uses wpctl (PipeWire) for audio control - Slider with percentage display - Mute/Unmute toggle button - Shows muted state with icon change - Supports volume > 100% System tab now has Flickable for scrolling content. --- configs/quickshell/shell.qml | 266 ++++++++++++++++++++++++++--------- 1 file changed, 201 insertions(+), 65 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 9f0bf83..a4c7e2a 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -262,90 +262,226 @@ Scope { // === SYSTEM TAB === Item { + id: systemTab anchors.fill: parent anchors.margins: 20 visible: settingsWindow.currentTab === 1 - Column { + property int brightness: 100 + property int maxBrightness: 100 + property real volume: 1.0 + property bool muted: false + property string brightnessOutput: "" + property string maxBrightnessOutput: "" + property string volumeOutput: "" + + Component.onCompleted: { + brightnessProc.running = true; + maxBrightnessProc.running = true; + volumeProc.running = true; + } + + Process { + id: brightnessProc + command: ["brightnessctl", "g"] + stdout: SplitParser { onRead: data => { systemTab.brightnessOutput = data.trim(); } } + onExited: { systemTab.brightness = parseInt(systemTab.brightnessOutput) || 0; } + } + + Process { + id: maxBrightnessProc + command: ["brightnessctl", "m"] + stdout: SplitParser { onRead: data => { systemTab.maxBrightnessOutput = data.trim(); } } + onExited: { systemTab.maxBrightness = parseInt(systemTab.maxBrightnessOutput) || 100; } + } + + Process { + id: setBrightnessProc + } + + Process { + id: volumeProc + command: ["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"] + stdout: SplitParser { onRead: data => { systemTab.volumeOutput = data.trim(); } } + onExited: { + var match = systemTab.volumeOutput.match(/Volume:\s*([\d.]+)/); + if (match) systemTab.volume = parseFloat(match[1]); + systemTab.muted = systemTab.volumeOutput.includes("[MUTED]"); + } + } + + Process { + id: setVolumeProc + onExited: { volumeProc.running = true; } + } + + function setBrightness(percent) { + setBrightnessProc.command = ["brightnessctl", "s", percent + "%"]; + setBrightnessProc.running = true; + brightness = Math.round(maxBrightness * percent / 100); + } + + function setVolume(percent) { + setVolumeProc.command = ["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", (percent / 100).toFixed(2)]; + setVolumeProc.running = true; + } + + function toggleMute() { + setVolumeProc.command = ["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]; + setVolumeProc.running = true; + } + + Flickable { anchors.fill: parent - spacing: 20 + contentHeight: systemContent.height + clip: true - // Terminal Column { + id: systemContent width: parent.width - spacing: 8 - Text { text: "Terminal Application"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } - Row { - spacing: 12 - Rectangle { - width: 280; height: 32 - color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline - TextInput { - id: terminalInput - anchors.fill: parent; anchors.margins: 6 - text: Config.hyprlandTerminal - font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text - clip: true; selectByMouse: true; activeFocusOnPress: true - verticalAlignment: TextInput.AlignVCenter + spacing: 16 + + // Brightness + Column { + width: parent.width + spacing: 8 + Row { + spacing: 10 + Text { text: "\ue518"; font.family: iconFont.name; font.pixelSize: 20; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Brightness"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + } + Row { + spacing: 16 + Rectangle { + width: 400; height: 28 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + Rectangle { + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 3 + width: (systemTab.maxBrightness > 0 ? systemTab.brightness / systemTab.maxBrightness : 0) * (parent.width - 6) + color: Config.colors.accent + } + MouseArea { + anchors.fill: parent + onClicked: mouse => { systemTab.setBrightness(Math.round(mouse.x / width * 100)); } + onPositionChanged: mouse => { if (pressed) systemTab.setBrightness(Math.round(mouse.x / width * 100)); } + } } + Text { text: Math.round(systemTab.maxBrightness > 0 ? systemTab.brightness / systemTab.maxBrightness * 100 : 0) + "%"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter; width: 50 } } - Rectangle { - width: 70; height: 32 - color: termSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 2; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 13; color: Config.colors.text } - MouseArea { id: termSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandTerminal(terminalInput.text) } + } + + // Volume + Column { + width: parent.width + spacing: 8 + Row { + spacing: 10 + Text { text: systemTab.muted ? "\ue04f" : "\ue050"; font.family: iconFont.name; font.pixelSize: 20; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Text { text: "Volume"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter } + Rectangle { + width: 60; height: 24 + color: muteArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: systemTab.muted ? "Unmute" : "Mute"; font.family: fontMonaco.name; font.pixelSize: 10; color: Config.colors.text } + MouseArea { id: muteArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: systemTab.toggleMute() } + } + } + Row { + spacing: 16 + Rectangle { + width: 400; height: 28 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + opacity: systemTab.muted ? 0.5 : 1 + Rectangle { + anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.margins: 3 + width: Math.min(1, systemTab.volume) * (parent.width - 6) + color: systemTab.volume > 1 ? Config.colors.urgent : Config.colors.accent + } + MouseArea { + anchors.fill: parent + onClicked: mouse => { systemTab.setVolume(Math.round(mouse.x / width * 100)); } + onPositionChanged: mouse => { if (pressed) systemTab.setVolume(Math.round(mouse.x / width * 100)); } + } + } + Text { text: Math.round(systemTab.volume * 100) + "%"; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text; anchors.verticalCenter: parent.verticalCenter; width: 50 } } } - Text { text: "→ hyprland.conf: $terminal"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; opacity: 0.5 } - } - // File Manager - Column { - width: parent.width - spacing: 8 - Text { text: "File Manager Application"; font.family: fontCharcoal.name; font.pixelSize: 15; color: Config.colors.text } - Row { - spacing: 12 - Rectangle { - width: 280; height: 32 - color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline - TextInput { - id: filesInput - anchors.fill: parent; anchors.margins: 6 - text: Config.hyprlandFileManager - font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text - clip: true; selectByMouse: true; activeFocusOnPress: true - verticalAlignment: TextInput.AlignVCenter + Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } + + // Terminal + Column { + width: parent.width + spacing: 6 + Text { text: "Terminal Application"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Row { + spacing: 12 + Rectangle { + width: 280; height: 28 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + TextInput { + id: terminalInput + anchors.fill: parent; anchors.margins: 5 + text: Config.hyprlandTerminal + font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text + clip: true; selectByMouse: true; activeFocusOnPress: true + verticalAlignment: TextInput.AlignVCenter + } + } + Rectangle { + width: 60; height: 28 + color: termSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + MouseArea { id: termSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandTerminal(terminalInput.text) } } } - Rectangle { - width: 70; height: 32 - color: filesSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight - border.width: 2; border.color: Config.colors.outline - Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 13; color: Config.colors.text } - MouseArea { id: filesSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandFileManager(filesInput.text) } + } + + // File Manager + Column { + width: parent.width + spacing: 6 + Text { text: "File Manager Application"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } + Row { + spacing: 12 + Rectangle { + width: 280; height: 28 + color: Config.colors.shadow; border.width: 2; border.color: Config.colors.outline + TextInput { + id: filesInput + anchors.fill: parent; anchors.margins: 5 + text: Config.hyprlandFileManager + font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text + clip: true; selectByMouse: true; activeFocusOnPress: true + verticalAlignment: TextInput.AlignVCenter + } + } + Rectangle { + width: 60; height: 28 + color: filesSaveArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 2; border.color: Config.colors.outline + Text { anchors.centerIn: parent; text: "Save"; font.family: fontCharcoal.name; font.pixelSize: 11; color: Config.colors.text } + MouseArea { id: filesSaveArea; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: Config.setHyprlandFileManager(filesInput.text) } + } } } - Text { text: "→ hyprland.conf: $fileManager"; font.family: fontMonaco.name; font.pixelSize: 11; color: Config.colors.text; opacity: 0.5 } - } - Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } + Rectangle { width: parent.width; height: 2; color: Config.colors.outline; opacity: 0.5 } - Text { text: "System Information"; font.family: fontCharcoal.name; font.pixelSize: 16; font.bold: true; color: Config.colors.text } - - Grid { - columns: 2; columnSpacing: 20; rowSpacing: 12 - Text { text: "OS:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: Config.settings.systemDetails.osName; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: "Version:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: Config.settings.systemDetails.osVersion; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: "CPU:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: Config.settings.systemDetails.cpu; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: "GPU:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: Config.settings.systemDetails.gpu; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: "RAM:"; font.family: fontCharcoal.name; font.pixelSize: 14; color: Config.colors.text } - Text { text: Config.settings.systemDetails.ram; font.family: fontMonaco.name; font.pixelSize: 14; color: Config.colors.text } + Text { text: "System Information"; font.family: fontCharcoal.name; font.pixelSize: 14; font.bold: true; color: Config.colors.text } + + Grid { + columns: 2; columnSpacing: 16; rowSpacing: 8 + Text { text: "OS:"; font.family: fontCharcoal.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: Config.settings.systemDetails.osName; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: "CPU:"; font.family: fontCharcoal.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: Config.settings.systemDetails.cpu; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: "GPU:"; font.family: fontCharcoal.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: Config.settings.systemDetails.gpu; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: "RAM:"; font.family: fontCharcoal.name; font.pixelSize: 12; color: Config.colors.text } + Text { text: Config.settings.systemDetails.ram; font.family: fontMonaco.name; font.pixelSize: 12; color: Config.colors.text } + } } } } From 2b86a6085c8f483992b3150cc85727cf79dda519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:03:01 +0300 Subject: [PATCH 10/15] fix: read maxBrightness before brightness to avoid division error maxBrightness was defaulting to 100 while brightness was already read as 13824, causing 13824/100 = 13824% display. Now maxBrightness is read first, then brightness is triggered. --- configs/quickshell/shell.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index a4c7e2a..c7b7141 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -267,8 +267,8 @@ Scope { anchors.margins: 20 visible: settingsWindow.currentTab === 1 - property int brightness: 100 - property int maxBrightness: 100 + property int brightness: 0 + property int maxBrightness: 1 property real volume: 1.0 property bool muted: false property string brightnessOutput: "" @@ -276,7 +276,6 @@ Scope { property string volumeOutput: "" Component.onCompleted: { - brightnessProc.running = true; maxBrightnessProc.running = true; volumeProc.running = true; } @@ -292,7 +291,10 @@ Scope { id: maxBrightnessProc command: ["brightnessctl", "m"] stdout: SplitParser { onRead: data => { systemTab.maxBrightnessOutput = data.trim(); } } - onExited: { systemTab.maxBrightness = parseInt(systemTab.maxBrightnessOutput) || 100; } + onExited: { + systemTab.maxBrightness = parseInt(systemTab.maxBrightnessOutput) || 1; + brightnessProc.running = true; + } } Process { From f226729fad01ab496b3135d1efbc203d77c89f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:17:46 +0300 Subject: [PATCH 11/15] feat: add notification system with retro theme Notification Server: - Uses Quickshell.Services.Notifications - Auto-dismiss after timeout - Persistence support for tracked notifications Notification Popup (Top Right): - Shows active notifications - 3D beveled border effect - Striped title bar matching theme - App name, summary, body display - Action buttons support - Urgency color coding (critical = red) - Click to dismiss Notification History Panel (Right Side): - Full-height side panel - Lists all tracked notifications - Clear All button - Urgency indicator bars - Dismiss individual notifications - Empty state message Taskbar Integration: - Notification bell button in taskbar - Opens/closes history panel Keybind: - SUPER+N to toggle notification history --- configs/hypr/hyprland.conf | 31 +- configs/quickshell/Config.qml | 1 + configs/quickshell/shell.qml | 531 +++++++++++++++++++++++++++++ configs/quickshell/taskbar/Bar.qml | 13 + 4 files changed, 564 insertions(+), 12 deletions(-) diff --git a/configs/hypr/hyprland.conf b/configs/hypr/hyprland.conf index c0d8631..1396358 100644 --- a/configs/hypr/hyprland.conf +++ b/configs/hypr/hyprland.conf @@ -11,8 +11,8 @@ # Make sure to customize monitors! This is just my setup so you get an idea. # run `hyprctl monitors all` to see all your monitors!!! -monitor=DP-2, 1920x1080@144.00, 0x250, 1 -monitor=DP-4, 2560x1440@164.84, 1920x0, 1 +monitor=eDP-1, 1920x1080@144.00, 0x0, 1 +monitor=HDMI-A-1, 1920x1080@119.00, 1920x0, 1 # Startup programs, these are mine, most likely you'll have these as well. exec-once = nm-applet @@ -25,7 +25,7 @@ exec-once = hyprctl setcursor Hackneyed-24px 24 # Default programs, these are what I prefer. $terminal = kitty -$fileManager = nemo +$fileManager = thunar # TODO: # This command calls the quickshell IPC to open the app launcher for the currently focused window. @@ -47,7 +47,7 @@ env = HYPRCURSOR_SIZE,24 # Example keyboard/mouse input settings. input { # Switch layout with pressing: alt + shift - kb_layout = us,se + kb_layout = us,tr kb_options = grp:alt_shift_toggle # Set as needed @@ -76,14 +76,14 @@ cursor { # !!DESIGN!! # animations { - enabled = false + enabled = true } # The gaps between windows, as well as border colors. # proportional to the taskbar values. general { # Inner and Outer gaps between windows. - gaps_in = 5 + gaps_in = 10 gaps_out = 10 # I prefer a thin border @@ -105,11 +105,12 @@ general { # Window Decorations! Shadow, Blur, etc. decoration { # 8px same as taskbar, change if wanted. - rounding = 0 + rounding = 10 + rounding_power = 10 # I want transparancy to not change, since we have the colored border. - active_opacity = 1.0 - inactive_opacity = 1.0 + active_opacity = 0.97 + inactive_opacity = 0.95 # Window Shadow shadow:enabled = true @@ -151,8 +152,8 @@ misc { # Windows key / Super key is the main modifier key. (abbreviated as mod) $mainMod = SUPER -# mod + enter = start terminal -bind = $mainMod, Return, exec, $terminal +# mod + t = start terminal +bind = $mainMod, T, exec, $terminal # mod + Q = kill current focused application bind = $mainMod, Q, killactive, # mod + shift + S = take screenshot of area, MUST HAVE HYPRSHOT INSTALLED! @@ -160,12 +161,16 @@ bind = $mainMod SHIFT, S, exec, hyprshot --mode region --output-folder /tmp # mod + E = open file manager (I prefer caja). bind = $mainMod, E, exec, $fileManager # mod + shift + space = Toggle focused window to be floating or tiled. -bind = $mainMod SHIFT, SPACE, togglefloating +bind = $mainMod ALT, SPACE, togglefloating # mod + F = toggle focused window to full-screen view. bind = $mainMod, F, fullscreen # mod + D = toggle application launcher (I prefer wofi). bind = $mainMod, D, exec, $menu +# mod + N = toggle notification history panel +bind = $mainMod, N, exec, quickshell ipc call notificationHistory toggleNotificationHistory + +bind = $mainMod, W, exec, zen-browser # Switch current workspaces with mainMod + [0-9] bind = $mainMod, 1, workspace, 1 @@ -224,6 +229,7 @@ windowrulev2 = minsize 250 500, class:^(Unity)$, title:^(Font Asset Creator)$ windowrulev2 = minsize 500 250, class:^(Unity)$, title:^(Background Tasks)$ + windowrulev2 = minsize 150 300, initialTitle:(UnityEditor.PopupWindow) windowrulev2 = minsize 230 200, initialTitle:(UnityEditor.AddComponent.AddComponentWindow) windowrulev2 = minsize 500 500, initialTitle:(UnityEngine.InputSystem.Editor.AdvancedDropdownWindow) @@ -243,3 +249,4 @@ windowrulev2 = move cursor, class:^(Unity)$, title:^(HDR Color)$ # Fix dragging issues with XWayland windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 + diff --git a/configs/quickshell/Config.qml b/configs/quickshell/Config.qml index a44fb93..d77567a 100644 --- a/configs/quickshell/Config.qml +++ b/configs/quickshell/Config.qml @@ -136,6 +136,7 @@ Singleton { property bool openSettingsWindow: false property bool openPowerMenu: false + property bool openNotificationHistory: false property alias settings: settingsJsonAdapter.settings FileView { diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index c7b7141..c0680f6 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -12,6 +12,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland +import Quickshell.Services.Notifications import "taskbar" as Taskbar import "popups" as Popups @@ -32,6 +33,14 @@ Scope { } Taskbar.Bar {} + // ============ NOTIFICATION HISTORY IPC ============ + IpcHandler { + target: "notificationHistory" + function toggleNotificationHistory() { + Config.openNotificationHistory = !Config.openNotificationHistory; + } + } + // ============ SETTINGS WIDGET (Overlay) ============ Variants { model: Quickshell.screens @@ -1024,4 +1033,526 @@ Scope { } } } + + // ============ NOTIFICATION SERVER ============ + NotificationServer { + id: notificationServer + keepOnReload: true + persistenceSupported: true + bodySupported: true + actionsSupported: true + imageSupported: true + + onNotification: notification => { + notification.tracked = true; + // Auto-dismiss after timeout (or 5 seconds default) + let timeout = notification.expireTimeout > 0 ? notification.expireTimeout : 5000; + if (!notification.resident) { + dismissTimer.setTimeout(notification, timeout); + } + } + } + + // Timer for auto-dismissing notifications + Timer { + id: dismissTimer + property var targetNotification: null + function setTimeout(notif, ms) { + targetNotification = notif; + interval = ms; + restart(); + } + onTriggered: { + if (targetNotification && !targetNotification.resident) { + targetNotification.expire(); + } + } + } + + // ============ NOTIFICATION POPUP (Top Right) ============ + Variants { + model: Quickshell.screens + PanelWindow { + id: notificationPopup + required property var modelData + screen: modelData + visible: notificationServer.trackedNotifications.count > 0 && Hyprland.focusedMonitor.name === modelData.name && !Config.openNotificationHistory + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "notification-popup" + exclusionMode: ExclusionMode.Ignore + + anchors { + top: true + right: true + } + width: 340 + height: Math.min(notificationServer.trackedNotifications.count * 90, 400) + margins.top: 50 + margins.right: 10 + color: "transparent" + + Column { + anchors.fill: parent + spacing: 8 + + Repeater { + model: notificationServer.trackedNotifications + + Rectangle { + id: notifCard + width: 320 + height: 80 + color: Config.colors.base + + // Outer border (3D effect) + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 2 + border.color: Config.colors.outline + } + + // Highlight edge (top-left) + Rectangle { + width: parent.width - 4; height: 2 + x: 2; y: 2 + color: Config.colors.highlight + } + Rectangle { + width: 2; height: parent.height - 4 + x: 2; y: 2 + color: Config.colors.highlight + } + + // Shadow edge (bottom-right) + Rectangle { + width: parent.width - 4; height: 2 + x: 2; y: parent.height - 4 + color: Config.colors.shadow + } + Rectangle { + width: 2; height: parent.height - 4 + x: parent.width - 4; y: 2 + color: Config.colors.shadow + } + + // Title bar + Rectangle { + id: notifTitleBar + x: 4; y: 4 + width: parent.width - 8; height: 18 + color: modelData.urgency === NotificationUrgency.Critical ? Config.colors.urgent : Config.colors.accent + + // Striped pattern + Repeater { + model: Math.floor((parent.width - 100) / 4) + Rectangle { + x: 4 + index * 4 + y: 0 + width: 2 + height: parent.height + color: Qt.rgba(0, 0, 0, 0.2) + } + } + + Row { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 4 + spacing: 4 + + Text { + text: modelData.appName || "Notification" + font.family: fontCharcoal.name + font.pixelSize: 10 + font.bold: true + color: Config.colors.text + } + + // Close button + Rectangle { + width: 14; height: 14 + color: closeArea.pressed ? Config.colors.shadow : Config.colors.base + border.width: 1 + border.color: Config.colors.outline + + Text { + anchors.centerIn: parent + text: "×" + font.pixelSize: 12 + font.bold: true + color: Config.colors.text + } + + MouseArea { + id: closeArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: modelData.dismiss() + } + } + } + } + + // Content + Column { + x: 8; y: 26 + width: parent.width - 16 + spacing: 2 + + Text { + width: parent.width + text: modelData.summary || "" + font.family: fontCharcoal.name + font.pixelSize: 11 + font.bold: true + color: Config.colors.text + elide: Text.ElideRight + } + + Text { + width: parent.width + text: modelData.body || "" + font.family: fontMonaco.name + font.pixelSize: 9 + color: Config.colors.subtext + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + // Actions + Row { + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.right: parent.right + anchors.rightMargin: 8 + spacing: 4 + visible: modelData.actions.length > 0 + + Repeater { + model: modelData.actions + Rectangle { + width: actionText.width + 12 + height: 16 + color: actionArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1 + border.color: Config.colors.outline + + Text { + id: actionText + anchors.centerIn: parent + text: modelData.text + font.family: fontCharcoal.name + font.pixelSize: 8 + color: Config.colors.text + } + + MouseArea { + id: actionArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: modelData.invoke() + } + } + } + } + + MouseArea { + anchors.fill: parent + z: -1 + onClicked: modelData.dismiss() + } + } + } + } + } + } + + // ============ NOTIFICATION HISTORY PANEL (Right Side) ============ + Variants { + model: Quickshell.screens + PanelWindow { + id: notificationHistoryPanel + required property var modelData + screen: modelData + visible: Config.openNotificationHistory && Hyprland.focusedMonitor.name === modelData.name + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.namespace: "notification-history" + + anchors { + top: true + bottom: true + right: true + } + width: 380 + color: "transparent" + + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Config.colors.base + + // Outer border + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 3 + border.color: Config.colors.outline + } + + // Highlight edge + Rectangle { + width: parent.width - 6; height: 3 + x: 3; y: 3 + color: Config.colors.highlight + } + Rectangle { + width: 3; height: parent.height - 6 + x: 3; y: 3 + color: Config.colors.highlight + } + + // Shadow edge + Rectangle { + width: parent.width - 6; height: 3 + x: 3; y: parent.height - 6 + color: Config.colors.shadow + } + Rectangle { + width: 3; height: parent.height - 6 + x: parent.width - 6; y: 3 + color: Config.colors.shadow + } + + // Title bar + Rectangle { + id: historyTitleBar + x: 6; y: 6 + width: parent.width - 12; height: 28 + color: Config.colors.accent + + // Striped pattern + Repeater { + model: Math.floor((parent.width - 150) / 4) + Rectangle { + x: 4 + index * 4 + y: 0 + width: 2 + height: parent.height + color: Qt.rgba(0, 0, 0, 0.2) + } + } + + Row { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 6 + spacing: 8 + + Text { + text: "\ue7f5" + font.family: iconFont.name + font.pixelSize: 16 + color: Config.colors.text + } + + Text { + text: "Notifications" + font.family: fontCharcoal.name + font.pixelSize: 14 + font.bold: true + color: Config.colors.text + } + + // Close button + Rectangle { + width: 20; height: 20 + color: historyCloseArea.pressed ? Config.colors.shadow : Config.colors.base + border.width: 1 + border.color: Config.colors.outline + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + anchors.bottomMargin: 2 + anchors.rightMargin: 2 + color: "transparent" + border.width: 1 + border.color: Config.colors.highlight + } + + Text { + anchors.centerIn: parent + text: "×" + font.pixelSize: 14 + font.bold: true + color: Config.colors.text + } + + MouseArea { + id: historyCloseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Config.openNotificationHistory = false + } + } + } + } + + // Clear All button + Rectangle { + x: 6; y: 40 + width: parent.width - 12; height: 24 + color: clearAllArea.pressed ? Config.colors.shadow : Config.colors.highlight + border.width: 1 + border.color: Config.colors.outline + visible: notificationServer.trackedNotifications.count > 0 + + Text { + anchors.centerIn: parent + text: "Clear All (" + notificationServer.trackedNotifications.count + ")" + font.family: fontCharcoal.name + font.pixelSize: 10 + color: Config.colors.text + } + + MouseArea { + id: clearAllArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + for (let i = notificationServer.trackedNotifications.count - 1; i >= 0; i--) { + notificationServer.trackedNotifications.values[i].dismiss(); + } + } + } + } + + // Notification list + Flickable { + x: 6; y: notificationServer.trackedNotifications.count > 0 ? 70 : 40 + width: parent.width - 12 + height: parent.height - y - 10 + contentHeight: historyColumn.height + clip: true + + Column { + id: historyColumn + width: parent.width + spacing: 8 + + // Empty state + Text { + width: parent.width + visible: notificationServer.trackedNotifications.count === 0 + text: "No notifications" + font.family: fontMonaco.name + font.pixelSize: 12 + color: Config.colors.subtext + horizontalAlignment: Text.AlignHCenter + topPadding: 40 + } + + Repeater { + model: notificationServer.trackedNotifications + + Rectangle { + width: parent.width + height: 70 + color: Config.colors.shadow + + // Border + Rectangle { + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: Config.colors.outline + } + + // Urgency indicator + Rectangle { + width: 4 + height: parent.height + color: modelData.urgency === NotificationUrgency.Critical ? Config.colors.urgent : + modelData.urgency === NotificationUrgency.Low ? Config.colors.highlight : Config.colors.accent + } + + // Content + Column { + x: 12; y: 8 + width: parent.width - 40 + spacing: 4 + + Row { + width: parent.width + spacing: 8 + + Text { + text: modelData.appName || "App" + font.family: fontCharcoal.name + font.pixelSize: 9 + font.bold: true + color: Config.colors.accent + } + } + + Text { + width: parent.width + text: modelData.summary || "" + font.family: fontCharcoal.name + font.pixelSize: 11 + font.bold: true + color: Config.colors.text + elide: Text.ElideRight + } + + Text { + width: parent.width + text: modelData.body || "" + font.family: fontMonaco.name + font.pixelSize: 9 + color: Config.colors.subtext + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + } + } + + // Dismiss button + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 6 + width: 18; height: 18 + color: historyDismissArea.pressed ? Config.colors.shadow : Config.colors.base + border.width: 1 + border.color: Config.colors.outline + + Text { + anchors.centerIn: parent + text: "×" + font.pixelSize: 12 + font.bold: true + color: Config.colors.text + } + + MouseArea { + id: historyDismissArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: modelData.dismiss() + } + } + } + } + } + } + } + } + } } diff --git a/configs/quickshell/taskbar/Bar.qml b/configs/quickshell/taskbar/Bar.qml index ff71d80..f3915ad 100644 --- a/configs/quickshell/taskbar/Bar.qml +++ b/configs/quickshell/taskbar/Bar.qml @@ -204,6 +204,19 @@ Scope { /*=== ============================= ===*/ + /*=== Notification Button ===*/ + TaskbarButton { + id: notificationButton + isToggled: Config.openNotificationHistory + iconFontValue: "\ue7f5" + anchors.right: test.left + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + onClicked: { + Config.openNotificationHistory = !Config.openNotificationHistory; + } + } + /*=== System Tray & Background for it ===*/ Item { id: test From b12e76e9cbb4ff37dd1f3ee7569a8114b8695e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:29:10 +0300 Subject: [PATCH 12/15] fix: notification popup visibility - Use root.notificationCount property instead of undefined trackedNotifications.count - Use Connections to listen for valuesChanged signal - Update count when values change - Fix Clear All to use slice() for safe iteration --- configs/quickshell/shell.qml | 39 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index c0680f6..7283256 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -1035,6 +1035,8 @@ Scope { } // ============ NOTIFICATION SERVER ============ + property int notificationCount: 0 + NotificationServer { id: notificationServer keepOnReload: true @@ -1045,6 +1047,8 @@ Scope { onNotification: notification => { notification.tracked = true; + root.notificationCount = trackedNotifications.values.length; + console.log("Notification added, count:", root.notificationCount); // Auto-dismiss after timeout (or 5 seconds default) let timeout = notification.expireTimeout > 0 ? notification.expireTimeout : 5000; if (!notification.resident) { @@ -1052,6 +1056,14 @@ Scope { } } } + + Connections { + target: notificationServer.trackedNotifications + function onValuesChanged() { + root.notificationCount = notificationServer.trackedNotifications.values.length; + console.log("Values changed, count:", root.notificationCount); + } + } // Timer for auto-dismissing notifications Timer { @@ -1076,7 +1088,7 @@ Scope { id: notificationPopup required property var modelData screen: modelData - visible: notificationServer.trackedNotifications.count > 0 && Hyprland.focusedMonitor.name === modelData.name && !Config.openNotificationHistory + visible: root.notificationCount > 0 && Hyprland.focusedMonitor.name === modelData.name && !Config.openNotificationHistory WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: "notification-popup" @@ -1086,8 +1098,8 @@ Scope { top: true right: true } - width: 340 - height: Math.min(notificationServer.trackedNotifications.count * 90, 400) + implicitWidth: 340 + implicitHeight: Math.max(root.notificationCount * 90, 10) margins.top: 50 margins.right: 10 color: "transparent" @@ -1216,7 +1228,7 @@ Scope { text: modelData.body || "" font.family: fontMonaco.name font.pixelSize: 9 - color: Config.colors.subtext + color: Config.colors.shadow wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight @@ -1289,7 +1301,7 @@ Scope { bottom: true right: true } - width: 380 + implicitWidth: 380 color: "transparent" Rectangle { @@ -1411,11 +1423,11 @@ Scope { color: clearAllArea.pressed ? Config.colors.shadow : Config.colors.highlight border.width: 1 border.color: Config.colors.outline - visible: notificationServer.trackedNotifications.count > 0 + visible: root.notificationCount > 0 Text { anchors.centerIn: parent - text: "Clear All (" + notificationServer.trackedNotifications.count + ")" + text: "Clear All (" + root.notificationCount + ")" font.family: fontCharcoal.name font.pixelSize: 10 color: Config.colors.text @@ -1426,8 +1438,9 @@ Scope { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - for (let i = notificationServer.trackedNotifications.count - 1; i >= 0; i--) { - notificationServer.trackedNotifications.values[i].dismiss(); + let notifications = notificationServer.trackedNotifications.values.slice(); + for (let notif of notifications) { + notif.dismiss(); } } } @@ -1435,7 +1448,7 @@ Scope { // Notification list Flickable { - x: 6; y: notificationServer.trackedNotifications.count > 0 ? 70 : 40 + x: 6; y: root.notificationCount > 0 ? 70 : 40 width: parent.width - 12 height: parent.height - y - 10 contentHeight: historyColumn.height @@ -1449,11 +1462,11 @@ Scope { // Empty state Text { width: parent.width - visible: notificationServer.trackedNotifications.count === 0 + visible: root.notificationCount === 0 text: "No notifications" font.family: fontMonaco.name font.pixelSize: 12 - color: Config.colors.subtext + color: Config.colors.shadow horizontalAlignment: Text.AlignHCenter topPadding: 40 } @@ -1516,7 +1529,7 @@ Scope { text: modelData.body || "" font.family: fontMonaco.name font.pixelSize: 9 - color: Config.colors.subtext + color: Config.colors.shadow wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight From 11b00a0fb8941d2e44ac090687bfbcfdc125c6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:30:04 +0300 Subject: [PATCH 13/15] style: increase notification body font size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Summary: 11px → 13px (popup), 12px (history) - Body: 9px → 11px with 0.8 opacity - Better spacing and padding - Use text color instead of shadow for body --- configs/quickshell/shell.qml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 7283256..0348b74 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -1209,15 +1209,15 @@ Scope { // Content Column { - x: 8; y: 26 - width: parent.width - 16 - spacing: 2 + x: 10; y: 28 + width: parent.width - 20 + spacing: 4 Text { width: parent.width text: modelData.summary || "" font.family: fontCharcoal.name - font.pixelSize: 11 + font.pixelSize: 13 font.bold: true color: Config.colors.text elide: Text.ElideRight @@ -1227,8 +1227,9 @@ Scope { width: parent.width text: modelData.body || "" font.family: fontMonaco.name - font.pixelSize: 9 - color: Config.colors.shadow + font.pixelSize: 11 + color: Config.colors.text + opacity: 0.8 wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight @@ -1518,7 +1519,7 @@ Scope { width: parent.width text: modelData.summary || "" font.family: fontCharcoal.name - font.pixelSize: 11 + font.pixelSize: 12 font.bold: true color: Config.colors.text elide: Text.ElideRight @@ -1528,8 +1529,9 @@ Scope { width: parent.width text: modelData.body || "" font.family: fontMonaco.name - font.pixelSize: 9 - color: Config.colors.shadow + font.pixelSize: 11 + color: Config.colors.text + opacity: 0.8 wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight From 44c71f3178de54a2e97313a8fc1a94dd5cc8f6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:32:55 +0300 Subject: [PATCH 14/15] fix: keep notifications in history until manually dismissed Remove auto-dismiss timer - notifications now stay until user clicks dismiss or Clear All --- configs/quickshell/shell.qml | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index 0348b74..c1f7923 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -1048,12 +1048,7 @@ Scope { onNotification: notification => { notification.tracked = true; root.notificationCount = trackedNotifications.values.length; - console.log("Notification added, count:", root.notificationCount); - // Auto-dismiss after timeout (or 5 seconds default) - let timeout = notification.expireTimeout > 0 ? notification.expireTimeout : 5000; - if (!notification.resident) { - dismissTimer.setTimeout(notification, timeout); - } + // Bildirimler manuel olarak kapatılana kadar kalır } } @@ -1061,23 +1056,6 @@ Scope { target: notificationServer.trackedNotifications function onValuesChanged() { root.notificationCount = notificationServer.trackedNotifications.values.length; - console.log("Values changed, count:", root.notificationCount); - } - } - - // Timer for auto-dismissing notifications - Timer { - id: dismissTimer - property var targetNotification: null - function setTimeout(notif, ms) { - targetNotification = notif; - interval = ms; - restart(); - } - onTriggered: { - if (targetNotification && !targetNotification.resident) { - targetNotification.expire(); - } } } From f70c7caff0e481736f47267020a03f0782886fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zhan=20Gebe=C5=9Fo=C4=9Flu?= Date: Sun, 18 Jan 2026 21:36:03 +0300 Subject: [PATCH 15/15] feat: separate notification history from active notifications - Notifications now persist in history even after popup dismissal - Added notificationHistory array with custom objects - History shows: appName, timestamp, summary, body - Dismiss from history removes only from history list - Clear All clears entire history - Active popup dismissals don't affect history --- configs/quickshell/shell.qml | 65 ++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/configs/quickshell/shell.qml b/configs/quickshell/shell.qml index c1f7923..9b6778c 100644 --- a/configs/quickshell/shell.qml +++ b/configs/quickshell/shell.qml @@ -1036,6 +1036,7 @@ Scope { // ============ NOTIFICATION SERVER ============ property int notificationCount: 0 + property var notificationHistory: [] NotificationServer { id: notificationServer @@ -1048,7 +1049,17 @@ Scope { onNotification: notification => { notification.tracked = true; root.notificationCount = trackedNotifications.values.length; - // Bildirimler manuel olarak kapatılana kadar kalır + + // Geçmiş listesine ekle + let historyItem = { + id: notification.id, + appName: notification.appName || "App", + summary: notification.summary || "", + body: notification.body || "", + urgency: notification.urgency, + timestamp: new Date().toLocaleTimeString() + }; + root.notificationHistory = [historyItem].concat(root.notificationHistory); } } @@ -1058,6 +1069,14 @@ Scope { root.notificationCount = notificationServer.trackedNotifications.values.length; } } + + function clearNotificationHistory() { + root.notificationHistory = []; + } + + function removeFromHistory(notifId) { + root.notificationHistory = root.notificationHistory.filter(n => n.id !== notifId); + } // ============ NOTIFICATION POPUP (Top Right) ============ Variants { @@ -1402,11 +1421,11 @@ Scope { color: clearAllArea.pressed ? Config.colors.shadow : Config.colors.highlight border.width: 1 border.color: Config.colors.outline - visible: root.notificationCount > 0 + visible: root.notificationHistory.length > 0 Text { anchors.centerIn: parent - text: "Clear All (" + root.notificationCount + ")" + text: "Clear All (" + root.notificationHistory.length + ")" font.family: fontCharcoal.name font.pixelSize: 10 color: Config.colors.text @@ -1417,17 +1436,14 @@ Scope { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - let notifications = notificationServer.trackedNotifications.values.slice(); - for (let notif of notifications) { - notif.dismiss(); - } + root.clearNotificationHistory(); } } } // Notification list Flickable { - x: 6; y: root.notificationCount > 0 ? 70 : 40 + x: 6; y: root.notificationHistory.length > 0 ? 70 : 40 width: parent.width - 12 height: parent.height - y - 10 contentHeight: historyColumn.height @@ -1441,7 +1457,7 @@ Scope { // Empty state Text { width: parent.width - visible: root.notificationCount === 0 + visible: root.notificationHistory.length === 0 text: "No notifications" font.family: fontMonaco.name font.pixelSize: 12 @@ -1451,11 +1467,11 @@ Scope { } Repeater { - model: notificationServer.trackedNotifications + model: root.notificationHistory Rectangle { width: parent.width - height: 70 + height: 80 color: Config.colors.shadow // Border @@ -1476,26 +1492,33 @@ Scope { // Content Column { - x: 12; y: 8 - width: parent.width - 40 - spacing: 4 + x: 12; y: 6 + width: parent.width - 50 + spacing: 2 Row { width: parent.width spacing: 8 Text { - text: modelData.appName || "App" + text: modelData.appName font.family: fontCharcoal.name font.pixelSize: 9 font.bold: true color: Config.colors.accent } + + Text { + text: modelData.timestamp + font.family: fontMonaco.name + font.pixelSize: 8 + color: Config.colors.shadow + } } Text { width: parent.width - text: modelData.summary || "" + text: modelData.summary font.family: fontCharcoal.name font.pixelSize: 12 font.bold: true @@ -1505,9 +1528,9 @@ Scope { Text { width: parent.width - text: modelData.body || "" + text: modelData.body font.family: fontMonaco.name - font.pixelSize: 11 + font.pixelSize: 10 color: Config.colors.text opacity: 0.8 wrapMode: Text.WordWrap @@ -1521,7 +1544,7 @@ Scope { anchors.right: parent.right anchors.top: parent.top anchors.margins: 6 - width: 18; height: 18 + width: 20; height: 20 color: historyDismissArea.pressed ? Config.colors.shadow : Config.colors.base border.width: 1 border.color: Config.colors.outline @@ -1529,7 +1552,7 @@ Scope { Text { anchors.centerIn: parent text: "×" - font.pixelSize: 12 + font.pixelSize: 14 font.bold: true color: Config.colors.text } @@ -1538,7 +1561,7 @@ Scope { id: historyDismissArea anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: modelData.dismiss() + onClicked: root.removeFromHistory(modelData.id) } } }