diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9da0239..c510e62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,14 @@ jobs: - name: Create release archive run: | mkdir -p ../noctalia-release - rsync -av --exclude='.git' --exclude='.github' ./ ../noctalia-release/ + rsync -av \ + --exclude='.git' \ + --exclude='.github' \ + --exclude='nix' \ + --exclude='flake.nix' \ + --exclude='flake.lock' \ + --exclude='result*' \ + ./ ../noctalia-release/ cd .. tar -czf noctalia-${{ github.ref_name }}.tar.gz noctalia-release/ cp noctalia-${{ github.ref_name }}.tar.gz noctalia-latest.tar.gz @@ -58,4 +65,4 @@ jobs: noctalia-latest.tar.gz body_path: release_notes.md env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Assets/Matugen/matugen.toml b/Assets/Matugen/matugen.toml index c456eda..dea2b91 100644 --- a/Assets/Matugen/matugen.toml +++ b/Assets/Matugen/matugen.toml @@ -23,4 +23,8 @@ output_path = "~/.config/qt6ct/colors/noctalia.conf" [templates.qt5] input_path = "templates/qtct.conf" -output_path = "~/.config/qt5ct/colors/noctalia.conf" \ No newline at end of file +output_path = "~/.config/qt5ct/colors/noctalia.conf" + +[templates.kitty] +input_path = "templates/kitty.conf" +output_path = "~/.config/kitty/noctalia.conf" \ No newline at end of file diff --git a/Assets/Matugen/templates/kitty.conf b/Assets/Matugen/templates/kitty.conf new file mode 100644 index 0000000..cd86820 --- /dev/null +++ b/Assets/Matugen/templates/kitty.conf @@ -0,0 +1,23 @@ +background {{colors.surface.default.hex}} +foreground {{colors.on_surface.default.hex}} +cursor_color {{colors.primary.default.hex}} +selection_background {{colors.surface_container.default.hex}} +selection_foreground {{colors.on_surface.default.hex}} +url_color {{colors.primary.default.hex}} + +color0 {{colors.surface.default.hex}} +color1 {{colors.error.default.hex}} +color2 {{colors.tertiary.default.hex}} +color3 {{colors.secondary.default.hex}} +color4 {{colors.primary.default.hex}} +color5 {{colors.surface_container_highest.default.hex}} +color6 {{colors.secondary.default.hex}} +color7 {{colors.on_background.default.hex}} +color8 {{colors.outline.default.hex}} +color9 {{colors.error_container.default.hex}} +color10 {{colors.tertiary_container.default.hex}} +color11 {{colors.surface_container.default.hex}} +color12 {{colors.primary_container.default.hex}} +color13 {{colors.on_primary_container.default.hex}} +color14 {{colors.surface_variant.default.hex}} +color15 {{colors.on_background.default.hex}} diff --git a/Commons/Style.qml b/Commons/Style.qml index c8b42b3..ce743e2 100644 --- a/Commons/Style.qml +++ b/Commons/Style.qml @@ -13,6 +13,7 @@ Singleton { */ // Font size + property real fontSizeXXS: 8 property real fontSizeXS: 9 property real fontSizeS: 10 property real fontSizeM: 11 diff --git a/Commons/Time.qml b/Commons/Time.qml index f464a4b..062f5dc 100644 --- a/Commons/Time.qml +++ b/Commons/Time.qml @@ -18,7 +18,8 @@ Singleton { dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) let day = date.getDate() let month = date.toLocaleDateString(Qt.locale(), "MMM") - return timeString + " - " + dayName + ", " + day + " " + month + + return timeString + " - " + (Settings.data.location.reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`) } return timeString diff --git a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml index d89e511..7e0264e 100644 --- a/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml +++ b/Modules/ArchUpdaterPanel/ArchUpdaterPanel.qml @@ -13,19 +13,9 @@ NPanel { panelHeight: 500 * scaling panelAnchorRight: true - // Auto-refresh when service updates - Connections { - target: ArchUpdaterService - function onUpdatePackagesChanged() { - // Force UI update when packages change - if (root.visible) { - // Small delay to ensure data is fully updated - Qt.callLater(() => { - // Force a UI update by triggering a property change - ArchUpdaterService.updatePackages = ArchUpdaterService.updatePackages - }, 100) - } - } + // When the panel opens + onOpened: { + ArchUpdaterService.doPoll() } panelContent: Rectangle { diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index a3a24fd..ce57d5e 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -20,7 +20,7 @@ PopupWindow { implicitWidth: menuWidth * scaling // Use the content height of the Flickable for implicit height - implicitHeight: Math.min(Screen.height * 0.9, flickable.contentHeight + (Style.marginM * 2 * scaling)) + implicitHeight: Math.min(Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling)) visible: false color: Color.transparent anchor.item: anchorItem diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 46f5701..b6823c7 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -8,21 +8,59 @@ import qs.Widgets Item { id: root - property ShellScreen screen property real scaling: ScalingService.scale(screen) - implicitWidth: pill.width implicitHeight: pill.height + // Track if we've already notified to avoid spam + property bool hasNotifiedLowBattery: false + + // Helper to evaluate and possibly notify + function maybeNotify(percent, charging) { + const p = Math.round(percent) + // Only notify exactly at 15%, not at 0% or any other percentage + if (!charging && p === 15 && !root.hasNotifiedLowBattery) { + Quickshell.execDetached( + ["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`]) + root.hasNotifiedLowBattery = true + } + // Reset when charging starts or when battery recovers above 20% + if (charging || p > 20) { + root.hasNotifiedLowBattery = false + } + } + + // Watch for battery changes + Connections { + target: UPower.displayDevice + function onPercentageChanged() { + let battery = UPower.displayDevice + let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent + let percent = isReady ? (battery.percentage * 100) : 0 + let charging = isReady ? battery.state === UPowerDeviceState.Charging : false + + root.maybeNotify(percent, charging) + } + + function onStateChanged() { + let battery = UPower.displayDevice + let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent + let charging = isReady ? battery.state === UPowerDeviceState.Charging : false + + // Reset notification flag when charging starts + if (charging) { + root.hasNotifiedLowBattery = false + } + } + } + NPill { id: pill - // Test mode property bool testMode: false - property int testPercent: 49 + property int testPercent: 20 property bool testCharging: false - property var battery: UPower.displayDevice property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent) property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) @@ -30,16 +68,12 @@ Item { // Choose icon based on charge and charging state function batteryIcon() { - if (!isReady || !battery.isLaptopBattery) return "battery_android_alert" - if (charging) return "battery_android_bolt" - if (percent >= 95) return "battery_android_full" - // Hardcoded battery symbols if (percent >= 85) return "battery_android_6" @@ -60,28 +94,26 @@ Item { icon: batteryIcon() text: (isReady && battery.isLaptopBattery) ? Math.round(percent) + "%" : "-" textColor: charging ? Color.mPrimary : Color.mOnSurface - forceOpen: isReady && battery.isLaptopBattery && Settings.data.bar.alwaysShowBatteryPercentage - disableOpen: (!isReady || !battery.isLaptopBattery) + iconCircleColor: Color.mPrimary + collapsedIconColor: Color.mOnSurface + autoHide: false + forceOpen: isReady && (testMode || battery.isLaptopBattery) && Settings.data.bar.alwaysShowBatteryPercentage + disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) tooltipText: { let lines = [] - if (testMode) { lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(12345)) return lines.join("\n") } - if (!isReady || !battery.isLaptopBattery) { return "No Battery Detected" } - if (battery.timeToEmpty > 0) { lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(battery.timeToEmpty)) } - if (battery.timeToFull > 0) { lines.push("Time Until Full: " + Time.formatVagueHumanReadableDuration(battery.timeToFull)) } - if (battery.changeRate !== undefined) { const rate = battery.changeRate if (rate > 0) { @@ -95,7 +127,6 @@ Item { } else { lines.push(charging ? "Charging" : "Discharging") } - if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) { lines.push("Health: " + Math.round(battery.healthPercentage) + "%") } diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 99d79cb..6b5f4e0 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -275,7 +275,7 @@ NPanel { } // Defer selectedIndex reset to avoid binding loops Qt.callLater(() => selectedIndex = 0) - + // Reset cursor position if needed if (shouldResetCursor && text === "") { cursorPosition = 0 diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index a79d60d..2d968be 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -41,7 +41,7 @@ Variants { margins.right: Style.marginM * scaling implicitWidth: 360 * scaling implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling) - WlrLayershell.layer: WlrLayer.Overlay + //WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.exclusionMode: ExclusionMode.Ignore // Connect to animation signal from service @@ -202,6 +202,8 @@ Variants { maximumLineCount: 5 elide: Text.ElideRight } + + // Actions removed } NIconButton { diff --git a/Modules/SettingsPanel/Tabs/BrightnessTab.qml b/Modules/SettingsPanel/Tabs/BrightnessTab.qml index 3b61e7b..0cb3319 100644 --- a/Modules/SettingsPanel/Tabs/BrightnessTab.qml +++ b/Modules/SettingsPanel/Tabs/BrightnessTab.qml @@ -7,7 +7,7 @@ import qs.Services import qs.Widgets Item { - property real scaling: 1 + readonly property real scaling: ScalingService.scale(screen) readonly property string tabIcon: "brightness_6" readonly property string tabLabel: "Brightness" Layout.fillWidth: true diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index c7343b6..61b7159 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -128,10 +128,10 @@ ColumnLayout { } } - // GTK/QT theming + // App theming NToggle { - label: "Theme external apps (GTK & Qt)" - description: "Writes GTK (gtk.css) and Qt (qt6ct) themes based on your colors." + label: "Theme external apps (GTK, Qt & kitty)" + description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors." checked: Settings.data.colorSchemes.themeApps onToggled: checked => { Settings.data.colorSchemes.themeApps = checked diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/SettingsPanel/Tabs/DisplayTab.qml index b3687b1..406fd3c 100644 --- a/Modules/SettingsPanel/Tabs/DisplayTab.qml +++ b/Modules/SettingsPanel/Tabs/DisplayTab.qml @@ -35,7 +35,9 @@ Item { contentWidth: parent.width ColumnLayout { - width: parent.width + id: contentColumn + width: Math.max(parent.width, 300) // Minimum reasonable width without scaling + ColumnLayout { spacing: Style.marginL * scaling Layout.margins: Style.marginL * scaling @@ -58,6 +60,8 @@ Item { model: Quickshell.screens || [] delegate: Rectangle { Layout.fillWidth: true + // Remove the scaling-based minimum width that causes issues at low scaling + // Layout.minimumWidth: 400 * scaling radius: Style.radiusM * scaling color: Color.mSurface border.color: Color.mOutline @@ -69,6 +73,7 @@ Item { anchors.fill: parent anchors.margins: Style.marginL * scaling spacing: Style.marginXXS * scaling + Layout.minimumWidth: 0 NText { text: (modelData.name || "Unknown") @@ -85,8 +90,12 @@ Item { ColumnLayout { spacing: Style.marginL * scaling + Layout.minimumWidth: 0 + Layout.fillWidth: true NToggle { + Layout.fillWidth: true + Layout.minimumWidth: 0 label: "Bar" description: "Enable the bar on this monitor." checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 @@ -100,6 +109,8 @@ Item { } NToggle { + Layout.fillWidth: true + Layout.minimumWidth: 0 label: "Notifications" description: "Enable notifications on this monitor." checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 @@ -115,6 +126,8 @@ Item { } NToggle { + Layout.fillWidth: true + Layout.minimumWidth: 0 label: "Dock" description: "Enable the dock on this monitor." checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 @@ -130,11 +143,16 @@ Item { ColumnLayout { spacing: Style.marginL * scaling + Layout.fillWidth: true RowLayout { + Layout.fillWidth: true + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true + Layout.minimumWidth: 0 + NText { text: "Scale" font.pointSize: Style.fontSizeM * scaling @@ -149,14 +167,19 @@ Item { Layout.fillWidth: true } } + NText { text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: implicitWidth } } RowLayout { spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.minimumWidth: 0 + NSlider { id: scaleSlider from: 0.6 @@ -169,12 +192,15 @@ Item { Settings.data.monitorsScaling = data } Layout.fillWidth: true + Layout.minimumWidth: 50 // Ensure minimum slider width } NIconButton { icon: "refresh" tooltipText: "Reset Scaling" fontPointSize: Style.fontSizeL * scaling + Layout.preferredWidth: implicitWidth + Layout.minimumWidth: implicitWidth onClicked: { var data = Settings.data.monitorsScaling || {} data[modelData.name] = 1.0 diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index 79b7fa2..4768109 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -7,7 +7,7 @@ import qs.Services import qs.Widgets Item { - property real scaling: 1 + readonly property real scaling: ScalingService.scale(screen) readonly property string tabIcon: "photo_library" readonly property string tabLabel: "Wallpaper Selector" readonly property int tabIndex: 7 diff --git a/Modules/WiFiPanel/WiFiPanel.qml b/Modules/WiFiPanel/WiFiPanel.qml index 8d88cd9..b617b14 100644 --- a/Modules/WiFiPanel/WiFiPanel.qml +++ b/Modules/WiFiPanel/WiFiPanel.qml @@ -46,6 +46,7 @@ NPanel { font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true + Layout.leftMargin: Style.marginS * scaling } NIconButton { @@ -72,6 +73,16 @@ NPanel { Layout.fillWidth: true } + // Show errors at the very top + NText { + visible: NetworkService.connectStatus === "error" && NetworkService.connectError.length > 0 + text: NetworkService.connectError + color: Color.mError + font.pointSize: Style.fontSizeXS * scaling + wrapMode: Text.Wrap + Layout.fillWidth: true + } + Item { Layout.fillWidth: true Layout.fillHeight: true @@ -127,69 +138,65 @@ NPanel { // Network list ListView { - id: networkList anchors.fill: parent visible: Settings.data.network.wifiEnabled && !NetworkService.isLoading model: Object.values(NetworkService.networks) - spacing: Style.marginM * scaling + spacing: Style.marginS * scaling clip: true delegate: Item { width: parent ? parent.width : 0 height: modelData.ssid === passwordPromptSsid - && showPasswordPrompt ? 108 * scaling : Style.baseWidgetSize * 1.5 * scaling + && showPasswordPrompt ? 130 * scaling : Style.baseWidgetSize * 1.75 * scaling ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { + id: rect Layout.fillWidth: true - Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling + Layout.fillHeight: true radius: Style.radiusS * scaling - color: modelData.connected ? Color.mPrimary : (networkMouseArea.containsMouse ? Color.mTertiary : Color.transparent) + color: networkMouseArea.containsMouse ? Color.mTertiary : Color.transparent + border.color: modelData.connected ? Color.mPrimary : Color.transparent + border.width: Math.max(1, Style.borderM * scaling) RowLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling + anchors { + fill: parent + leftMargin: Style.marginL * scaling + rightMargin: Style.marginL * scaling + } spacing: Style.marginS * scaling NIcon { text: NetworkService.signalIcon(modelData.signal) font.pointSize: Style.fontSizeXXL * scaling - color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface } ColumnLayout { Layout.fillWidth: true - spacing: Style.marginXS * scaling + Layout.alignment: Qt.AlignVCenter + spacing: 0 // SSID NText { + Layout.fillWidth: true text: modelData.ssid || "Unknown Network" font.pointSize: Style.fontSizeNormal * scaling elide: Text.ElideRight - Layout.fillWidth: true - color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface } // Security Protocol NText { text: modelData.security && modelData.security !== "--" ? modelData.security : "Open" - font.pointSize: Style.fontSizeXS * scaling - elide: Text.ElideRight - Layout.fillWidth: true - color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) - } - - NText { - visible: NetworkService.connectStatusSsid === modelData.ssid - && NetworkService.connectStatus === "error" && NetworkService.connectError.length > 0 - text: NetworkService.connectError - color: Color.mError - font.pointSize: Style.fontSizeXS * scaling + font.pointSize: Style.fontSizeXXS * scaling elide: Text.ElideRight Layout.fillWidth: true + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface } } @@ -203,17 +210,25 @@ NPanel { NBusyIndicator { visible: NetworkService.connectingSsid === modelData.ssid running: NetworkService.connectingSsid === modelData.ssid - color: Color.mPrimary + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface anchors.centerIn: parent size: Style.baseWidgetSize * 0.7 * scaling } } - NText { + RowLayout { visible: modelData.connected - text: "connected" - font.pointSize: Style.fontSizeXS * scaling - color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) + NText { + text: "Connected" + font.pointSize: Style.fontSizeXS * scaling + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + } + NIcon { + text: "check" + font.pointSize: Style.fontSizeXXL * scaling + color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface + } } } @@ -224,6 +239,7 @@ NPanel { onClicked: { if (modelData.connected) { NetworkService.disconnectNetwork(modelData.ssid) + showPasswordPrompt = false } else if (NetworkService.isSecured(modelData.security) && !modelData.existing) { passwordPromptSsid = modelData.ssid showPasswordPrompt = true @@ -240,11 +256,11 @@ NPanel { // Password prompt section Rectangle { - id: passwordPromptSection + visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt Layout.fillWidth: true Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0 Layout.margins: Style.marginS * scaling - visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt + color: Color.mSurfaceVariant radius: Style.radiusS * scaling @@ -280,8 +296,10 @@ NPanel { echoMode: TextInput.Password onTextChanged: passwordInput = text onAccepted: { - NetworkService.submitPassword(passwordPromptSsid, passwordInput) - showPasswordPrompt = false + if (passwordInput !== "") { + NetworkService.submitPassword(passwordPromptSsid, passwordInput) + showPasswordPrompt = false + } } MouseArea { @@ -315,8 +333,10 @@ NPanel { MouseArea { anchors.fill: parent onClicked: { - NetworkService.submitPassword(passwordPromptSsid, passwordInput) - showPasswordPrompt = false + if (passwordInput !== "") { + NetworkService.submitPassword(passwordPromptSsid, passwordInput) + showPasswordPrompt = false + } } cursorShape: Qt.PointingHandCursor hoverEnabled: true @@ -324,6 +344,13 @@ NPanel { onExited: parent.color = Color.mPrimary } } + + NIconButton { + icon: "close" + onClicked: { + showPasswordPrompt = false + } + } } } } diff --git a/Services/ArchUpdaterService.qml b/Services/ArchUpdaterService.qml index 611ce59..bfc4565 100644 --- a/Services/ArchUpdaterService.qml +++ b/Services/ArchUpdaterService.qml @@ -1,8 +1,9 @@ pragma Singleton -import Quickshell import QtQuick +import Quickshell import Quickshell.Io +import qs.Commons Singleton { id: updateService @@ -15,20 +16,23 @@ Singleton { property int selectedPackagesCount: 0 property bool updateInProgress: false + // Initial check + Component.onCompleted: doPoll() + // Process for checking updates Process { id: checkupdatesProcess command: ["checkupdates"] onExited: function (exitCode) { if (exitCode !== 0 && exitCode !== 2) { - console.warn("[UpdateService] checkupdates failed (code:", exitCode, ")") + Logger.warn("ArchUpdater", "checkupdates failed (code:", exitCode, ")") updatePackages = [] - return } } stdout: StdioCollector { onStreamFinished: { parseCheckupdatesOutput(text) + Logger.log("ArchUpdater", "found", updatePackages.length, "upgradable package(s)") } } } @@ -150,7 +154,4 @@ Singleton { running: true onTriggered: doPoll() } - - // Initial check - Component.onCompleted: doPoll() } diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 62ea73c..b6e735c 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -10,7 +10,7 @@ Singleton { // Widget registry object mapping widget names to components property var widgets: ({ "ActiveWindow": activeWindowComponent, - // "ArchUpdater": archUpdaterComponent, + "ArchUpdater": archUpdaterComponent, "Battery": batteryComponent, "Bluetooth": bluetoothComponent, "Brightness": brightnessComponent, @@ -32,9 +32,9 @@ Singleton { property Component activeWindowComponent: Component { ActiveWindow {} } - // property Component archUpdaterComponent: Component { - // ArchUpdater {} - // } + property Component archUpdaterComponent: Component { + ArchUpdater {} + } property Component batteryComponent: Component { Battery {} } diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index ba354fb..aac2b53 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -144,7 +144,7 @@ Singleton { function setBluetoothEnabled(enabled) { if (!adapter) { - console.warn("BluetoothService: No adapter available") + Logger.warn("Bluetooth", "No adapter available") return } diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 501abdd..0b43142 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -29,8 +29,7 @@ Singleton { Logger.log("Wallpapers", "Listing wallpapers") scanning = true wallpaperList = [] - // Unsetting, then setting the folder will re-trigger the parsing! - folderModel.folder = "" + // Set the folder directly to avoid model reset issues folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") } diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index dc8e162..4d14478 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -32,8 +32,6 @@ Loader { signal closed Component.onCompleted: { - // console.log("Oh Yeah") - // console.log(objectName) PanelService.registerPanel(root) } diff --git a/Widgets/NText.qml b/Widgets/NText.qml index 7436c54..e6986fb 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -10,4 +10,7 @@ Text { font.pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface + renderType: Text.QtRendering + font.hintingPreference: Font.PreferNoHinting + font.kerning: true } diff --git a/Widgets/NWidgetLoader.qml b/Widgets/NWidgetLoader.qml index 600b5e7..e0ec294 100644 --- a/Widgets/NWidgetLoader.qml +++ b/Widgets/NWidgetLoader.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import qs.Services +import qs.Commons Item { id: root diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2a54f7a --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "quickshell": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1753595452, + "narHash": "sha256-vqkSDvh7hWhPvNjMjEDV4KbSCv2jyl2Arh73ZXe274k=", + "ref": "refs/heads/master", + "rev": "a5431dd02dc23d9ef1680e67777fed00fe5f7cda", + "revCount": 665, + "type": "git", + "url": "https://git.outfoxxed.me/outfoxxed/quickshell" + }, + "original": { + "type": "git", + "url": "https://git.outfoxxed.me/outfoxxed/quickshell" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "quickshell": "quickshell", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7025c70 --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "Desktop shell for Caelestia dots"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default"; + + quickshell = { + url = "git+https://git.outfoxxed.me/outfoxxed/quickshell"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { + self, + nixpkgs, + systems, + ... + } @ inputs: let + eachSystem = nixpkgs.lib.genAttrs (import systems); + in { + formatter = eachSystem (pkgs: pkgs.alejandra); + + packages = eachSystem (system: rec { + noctalia-shell = nixpkgs.legacyPackages.${system}.callPackage ./nix { + rev = self.rev or self.dirtyRev; + quickshell = inputs.quickshell.packages.${system}.default.override { + withX11 = false; + withI3 = false; + }; + }; + default = noctalia-shell; + }); + }; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..7834f17 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,84 @@ +{ + rev, + lib, + stdenv, + makeWrapper, + makeFontsConf, + ddcutil, + brightnessctl, + cava, + networkmanager, + wl-clipboard, + libnotify, + bluez, + bash, + coreutils, + findutils, + file, + material-symbols, + roboto, + inter-nerdfont, + matugen, + cliphist, + swww, + gpu-screen-recorder, + gcc, + qt6, + quickshell, + xkeyboard-config, + extraRuntimeDeps ? [], +}: let + runtimeDeps = + [ + bash + bluez + brightnessctl + cava + cliphist + coreutils + ddcutil + file + findutils + gpu-screen-recorder + libnotify + matugen + networkmanager + swww + wl-clipboard + ] + ++ extraRuntimeDeps; + + fontconfig = makeFontsConf { + fontDirectories = [ + material-symbols + roboto + inter-nerdfont + ]; + }; +in + stdenv.mkDerivation { + pname = "noctalia-shell"; + version = "${rev}"; + src = ./..; + + nativeBuildInputs = [gcc makeWrapper qt6.wrapQtAppsHook]; + buildInputs = [quickshell xkeyboard-config qt6.qtbase]; + propagatedBuildInputs = runtimeDeps; + + installPhase = '' + mkdir -p $out/share/noctalia-shell + cp -r ./* $out/share/noctalia-shell + + makeWrapper ${quickshell}/bin/qs $out/bin/noctalia-shell \ + --prefix PATH : "${lib.makeBinPath runtimeDeps}" \ + --set FONTCONFIG_FILE "${fontconfig}" \ + --add-flags "-p $out/share/noctalia-shell" + ''; + + meta = { + description = "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell."; + homepage = "https://github.com/noctalia-dev/noctalia-shell"; + license = lib.licenses.mit; + mainProgram = "noctalia-shell"; + }; + }