Merge branch 'noctalia-dev:main' into fix/heuristic-lookup

This commit is contained in:
Kainoa Kanter 2025-08-25 14:26:35 -07:00 committed by GitHub
commit 38e3d9909f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 389 additions and 92 deletions

View file

@ -22,7 +22,14 @@ jobs:
- name: Create release archive - name: Create release archive
run: | run: |
mkdir -p ../noctalia-release 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 .. cd ..
tar -czf noctalia-${{ github.ref_name }}.tar.gz noctalia-release/ tar -czf noctalia-${{ github.ref_name }}.tar.gz noctalia-release/
cp noctalia-${{ github.ref_name }}.tar.gz noctalia-latest.tar.gz cp noctalia-${{ github.ref_name }}.tar.gz noctalia-latest.tar.gz
@ -58,4 +65,4 @@ jobs:
noctalia-latest.tar.gz noctalia-latest.tar.gz
body_path: release_notes.md body_path: release_notes.md
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -23,4 +23,8 @@ output_path = "~/.config/qt6ct/colors/noctalia.conf"
[templates.qt5] [templates.qt5]
input_path = "templates/qtct.conf" input_path = "templates/qtct.conf"
output_path = "~/.config/qt5ct/colors/noctalia.conf" output_path = "~/.config/qt5ct/colors/noctalia.conf"
[templates.kitty]
input_path = "templates/kitty.conf"
output_path = "~/.config/kitty/noctalia.conf"

View file

@ -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}}

View file

@ -13,6 +13,7 @@ Singleton {
*/ */
// Font size // Font size
property real fontSizeXXS: 8
property real fontSizeXS: 9 property real fontSizeXS: 9
property real fontSizeS: 10 property real fontSizeS: 10
property real fontSizeM: 11 property real fontSizeM: 11

View file

@ -18,7 +18,8 @@ Singleton {
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
let day = date.getDate() let day = date.getDate()
let month = date.toLocaleDateString(Qt.locale(), "MMM") 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 return timeString

View file

@ -13,19 +13,9 @@ NPanel {
panelHeight: 500 * scaling panelHeight: 500 * scaling
panelAnchorRight: true panelAnchorRight: true
// Auto-refresh when service updates // When the panel opens
Connections { onOpened: {
target: ArchUpdaterService ArchUpdaterService.doPoll()
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)
}
}
} }
panelContent: Rectangle { panelContent: Rectangle {

View file

@ -20,7 +20,7 @@ PopupWindow {
implicitWidth: menuWidth * scaling implicitWidth: menuWidth * scaling
// Use the content height of the Flickable for implicit height // 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 visible: false
color: Color.transparent color: Color.transparent
anchor.item: anchorItem anchor.item: anchorItem

View file

@ -8,21 +8,59 @@ import qs.Widgets
Item { Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: ScalingService.scale(screen)
implicitWidth: pill.width implicitWidth: pill.width
implicitHeight: pill.height 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 { NPill {
id: pill id: pill
// Test mode // Test mode
property bool testMode: false property bool testMode: false
property int testPercent: 49 property int testPercent: 20
property bool testCharging: false property bool testCharging: false
property var battery: UPower.displayDevice property var battery: UPower.displayDevice
property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent) property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
@ -30,16 +68,12 @@ Item {
// Choose icon based on charge and charging state // Choose icon based on charge and charging state
function batteryIcon() { function batteryIcon() {
if (!isReady || !battery.isLaptopBattery) if (!isReady || !battery.isLaptopBattery)
return "battery_android_alert" return "battery_android_alert"
if (charging) if (charging)
return "battery_android_bolt" return "battery_android_bolt"
if (percent >= 95) if (percent >= 95)
return "battery_android_full" return "battery_android_full"
// Hardcoded battery symbols // Hardcoded battery symbols
if (percent >= 85) if (percent >= 85)
return "battery_android_6" return "battery_android_6"
@ -60,28 +94,26 @@ Item {
icon: batteryIcon() icon: batteryIcon()
text: (isReady && battery.isLaptopBattery) ? Math.round(percent) + "%" : "-" text: (isReady && battery.isLaptopBattery) ? Math.round(percent) + "%" : "-"
textColor: charging ? Color.mPrimary : Color.mOnSurface textColor: charging ? Color.mPrimary : Color.mOnSurface
forceOpen: isReady && battery.isLaptopBattery && Settings.data.bar.alwaysShowBatteryPercentage iconCircleColor: Color.mPrimary
disableOpen: (!isReady || !battery.isLaptopBattery) collapsedIconColor: Color.mOnSurface
autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && Settings.data.bar.alwaysShowBatteryPercentage
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
tooltipText: { tooltipText: {
let lines = [] let lines = []
if (testMode) { if (testMode) {
lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(12345)) lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(12345))
return lines.join("\n") return lines.join("\n")
} }
if (!isReady || !battery.isLaptopBattery) { if (!isReady || !battery.isLaptopBattery) {
return "No Battery Detected" return "No Battery Detected"
} }
if (battery.timeToEmpty > 0) { if (battery.timeToEmpty > 0) {
lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(battery.timeToEmpty)) lines.push("Time Left: " + Time.formatVagueHumanReadableDuration(battery.timeToEmpty))
} }
if (battery.timeToFull > 0) { if (battery.timeToFull > 0) {
lines.push("Time Until Full: " + Time.formatVagueHumanReadableDuration(battery.timeToFull)) lines.push("Time Until Full: " + Time.formatVagueHumanReadableDuration(battery.timeToFull))
} }
if (battery.changeRate !== undefined) { if (battery.changeRate !== undefined) {
const rate = battery.changeRate const rate = battery.changeRate
if (rate > 0) { if (rate > 0) {
@ -95,7 +127,6 @@ Item {
} else { } else {
lines.push(charging ? "Charging" : "Discharging") lines.push(charging ? "Charging" : "Discharging")
} }
if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) { if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
lines.push("Health: " + Math.round(battery.healthPercentage) + "%") lines.push("Health: " + Math.round(battery.healthPercentage) + "%")
} }

View file

@ -275,7 +275,7 @@ NPanel {
} }
// Defer selectedIndex reset to avoid binding loops // Defer selectedIndex reset to avoid binding loops
Qt.callLater(() => selectedIndex = 0) Qt.callLater(() => selectedIndex = 0)
// Reset cursor position if needed // Reset cursor position if needed
if (shouldResetCursor && text === "") { if (shouldResetCursor && text === "") {
cursorPosition = 0 cursorPosition = 0

View file

@ -41,7 +41,7 @@ Variants {
margins.right: Style.marginM * scaling margins.right: Style.marginM * scaling
implicitWidth: 360 * scaling implicitWidth: 360 * scaling
implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling) implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling)
WlrLayershell.layer: WlrLayer.Overlay //WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
// Connect to animation signal from service // Connect to animation signal from service
@ -202,6 +202,8 @@ Variants {
maximumLineCount: 5 maximumLineCount: 5
elide: Text.ElideRight elide: Text.ElideRight
} }
// Actions removed
} }
NIconButton { NIconButton {

View file

@ -7,7 +7,7 @@ import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
property real scaling: 1 readonly property real scaling: ScalingService.scale(screen)
readonly property string tabIcon: "brightness_6" readonly property string tabIcon: "brightness_6"
readonly property string tabLabel: "Brightness" readonly property string tabLabel: "Brightness"
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -128,10 +128,10 @@ ColumnLayout {
} }
} }
// GTK/QT theming // App theming
NToggle { NToggle {
label: "Theme external apps (GTK & Qt)" label: "Theme external apps (GTK, Qt & kitty)"
description: "Writes GTK (gtk.css) and Qt (qt6ct) themes based on your colors." description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors."
checked: Settings.data.colorSchemes.themeApps checked: Settings.data.colorSchemes.themeApps
onToggled: checked => { onToggled: checked => {
Settings.data.colorSchemes.themeApps = checked Settings.data.colorSchemes.themeApps = checked

View file

@ -35,7 +35,9 @@ Item {
contentWidth: parent.width contentWidth: parent.width
ColumnLayout { ColumnLayout {
width: parent.width id: contentColumn
width: Math.max(parent.width, 300) // Minimum reasonable width without scaling
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.margins: Style.marginL * scaling Layout.margins: Style.marginL * scaling
@ -58,6 +60,8 @@ Item {
model: Quickshell.screens || [] model: Quickshell.screens || []
delegate: Rectangle { delegate: Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
// Remove the scaling-based minimum width that causes issues at low scaling
// Layout.minimumWidth: 400 * scaling
radius: Style.radiusM * scaling radius: Style.radiusM * scaling
color: Color.mSurface color: Color.mSurface
border.color: Color.mOutline border.color: Color.mOutline
@ -69,6 +73,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginL * scaling anchors.margins: Style.marginL * scaling
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.minimumWidth: 0
NText { NText {
text: (modelData.name || "Unknown") text: (modelData.name || "Unknown")
@ -85,8 +90,12 @@ Item {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.minimumWidth: 0
Layout.fillWidth: true
NToggle { NToggle {
Layout.fillWidth: true
Layout.minimumWidth: 0
label: "Bar" label: "Bar"
description: "Enable the bar on this monitor." description: "Enable the bar on this monitor."
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
@ -100,6 +109,8 @@ Item {
} }
NToggle { NToggle {
Layout.fillWidth: true
Layout.minimumWidth: 0
label: "Notifications" label: "Notifications"
description: "Enable notifications on this monitor." description: "Enable notifications on this monitor."
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1 checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
@ -115,6 +126,8 @@ Item {
} }
NToggle { NToggle {
Layout.fillWidth: true
Layout.minimumWidth: 0
label: "Dock" label: "Dock"
description: "Enable the dock on this monitor." description: "Enable the dock on this monitor."
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1 checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
@ -130,11 +143,16 @@ Item {
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true
RowLayout { RowLayout {
Layout.fillWidth: true
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 0
NText { NText {
text: "Scale" text: "Scale"
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
@ -149,14 +167,19 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
NText { NText {
text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%`
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: implicitWidth
} }
} }
RowLayout { RowLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.minimumWidth: 0
NSlider { NSlider {
id: scaleSlider id: scaleSlider
from: 0.6 from: 0.6
@ -169,12 +192,15 @@ Item {
Settings.data.monitorsScaling = data Settings.data.monitorsScaling = data
} }
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 50 // Ensure minimum slider width
} }
NIconButton { NIconButton {
icon: "refresh" icon: "refresh"
tooltipText: "Reset Scaling" tooltipText: "Reset Scaling"
fontPointSize: Style.fontSizeL * scaling fontPointSize: Style.fontSizeL * scaling
Layout.preferredWidth: implicitWidth
Layout.minimumWidth: implicitWidth
onClicked: { onClicked: {
var data = Settings.data.monitorsScaling || {} var data = Settings.data.monitorsScaling || {}
data[modelData.name] = 1.0 data[modelData.name] = 1.0

View file

@ -7,7 +7,7 @@ import qs.Services
import qs.Widgets import qs.Widgets
Item { Item {
property real scaling: 1 readonly property real scaling: ScalingService.scale(screen)
readonly property string tabIcon: "photo_library" readonly property string tabIcon: "photo_library"
readonly property string tabLabel: "Wallpaper Selector" readonly property string tabLabel: "Wallpaper Selector"
readonly property int tabIndex: 7 readonly property int tabIndex: 7

View file

@ -46,6 +46,7 @@ NPanel {
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Style.marginS * scaling
} }
NIconButton { NIconButton {
@ -72,6 +73,16 @@ NPanel {
Layout.fillWidth: true 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 { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@ -127,69 +138,65 @@ NPanel {
// Network list // Network list
ListView { ListView {
id: networkList
anchors.fill: parent anchors.fill: parent
visible: Settings.data.network.wifiEnabled && !NetworkService.isLoading visible: Settings.data.network.wifiEnabled && !NetworkService.isLoading
model: Object.values(NetworkService.networks) model: Object.values(NetworkService.networks)
spacing: Style.marginM * scaling spacing: Style.marginS * scaling
clip: true clip: true
delegate: Item { delegate: Item {
width: parent ? parent.width : 0 width: parent ? parent.width : 0
height: modelData.ssid === passwordPromptSsid height: modelData.ssid === passwordPromptSsid
&& showPasswordPrompt ? 108 * scaling : Style.baseWidgetSize * 1.5 * scaling && showPasswordPrompt ? 130 * scaling : Style.baseWidgetSize * 1.75 * scaling
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
Rectangle { Rectangle {
id: rect
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling Layout.fillHeight: true
radius: Style.radiusS * scaling 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 { RowLayout {
anchors.fill: parent anchors {
anchors.margins: Style.marginS * scaling fill: parent
leftMargin: Style.marginL * scaling
rightMargin: Style.marginL * scaling
}
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
NIcon { NIcon {
text: NetworkService.signalIcon(modelData.signal) text: NetworkService.signalIcon(modelData.signal)
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
} }
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter
spacing: 0
// SSID // SSID
NText { NText {
Layout.fillWidth: true
text: modelData.ssid || "Unknown Network" text: modelData.ssid || "Unknown Network"
font.pointSize: Style.fontSizeNormal * scaling font.pointSize: Style.fontSizeNormal * scaling
elide: Text.ElideRight elide: Text.ElideRight
Layout.fillWidth: true color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface)
} }
// Security Protocol // Security Protocol
NText { NText {
text: modelData.security && modelData.security !== "--" ? modelData.security : "Open" text: modelData.security && modelData.security !== "--" ? modelData.security : "Open"
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXXS * 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
elide: Text.ElideRight elide: Text.ElideRight
Layout.fillWidth: true Layout.fillWidth: true
color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
} }
} }
@ -203,17 +210,25 @@ NPanel {
NBusyIndicator { NBusyIndicator {
visible: NetworkService.connectingSsid === modelData.ssid visible: NetworkService.connectingSsid === modelData.ssid
running: NetworkService.connectingSsid === modelData.ssid running: NetworkService.connectingSsid === modelData.ssid
color: Color.mPrimary color: networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface
anchors.centerIn: parent anchors.centerIn: parent
size: Style.baseWidgetSize * 0.7 * scaling size: Style.baseWidgetSize * 0.7 * scaling
} }
} }
NText { RowLayout {
visible: modelData.connected visible: modelData.connected
text: "connected" NText {
font.pointSize: Style.fontSizeXS * scaling text: "Connected"
color: modelData.connected ? Color.mSurface : (networkMouseArea.containsMouse ? Color.mSurface : Color.mOnSurface) 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: { onClicked: {
if (modelData.connected) { if (modelData.connected) {
NetworkService.disconnectNetwork(modelData.ssid) NetworkService.disconnectNetwork(modelData.ssid)
showPasswordPrompt = false
} else if (NetworkService.isSecured(modelData.security) && !modelData.existing) { } else if (NetworkService.isSecured(modelData.security) && !modelData.existing) {
passwordPromptSsid = modelData.ssid passwordPromptSsid = modelData.ssid
showPasswordPrompt = true showPasswordPrompt = true
@ -240,11 +256,11 @@ NPanel {
// Password prompt section // Password prompt section
Rectangle { Rectangle {
id: passwordPromptSection visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0 Layout.preferredHeight: modelData.ssid === passwordPromptSsid && showPasswordPrompt ? 60 : 0
Layout.margins: Style.marginS * scaling Layout.margins: Style.marginS * scaling
visible: modelData.ssid === passwordPromptSsid && showPasswordPrompt
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
@ -280,8 +296,10 @@ NPanel {
echoMode: TextInput.Password echoMode: TextInput.Password
onTextChanged: passwordInput = text onTextChanged: passwordInput = text
onAccepted: { onAccepted: {
NetworkService.submitPassword(passwordPromptSsid, passwordInput) if (passwordInput !== "") {
showPasswordPrompt = false NetworkService.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false
}
} }
MouseArea { MouseArea {
@ -315,8 +333,10 @@ NPanel {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
NetworkService.submitPassword(passwordPromptSsid, passwordInput) if (passwordInput !== "") {
showPasswordPrompt = false NetworkService.submitPassword(passwordPromptSsid, passwordInput)
showPasswordPrompt = false
}
} }
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: true hoverEnabled: true
@ -324,6 +344,13 @@ NPanel {
onExited: parent.color = Color.mPrimary onExited: parent.color = Color.mPrimary
} }
} }
NIconButton {
icon: "close"
onClicked: {
showPasswordPrompt = false
}
}
} }
} }
} }

View file

@ -1,8 +1,9 @@
pragma Singleton pragma Singleton
import Quickshell
import QtQuick import QtQuick
import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Commons
Singleton { Singleton {
id: updateService id: updateService
@ -15,20 +16,23 @@ Singleton {
property int selectedPackagesCount: 0 property int selectedPackagesCount: 0
property bool updateInProgress: false property bool updateInProgress: false
// Initial check
Component.onCompleted: doPoll()
// Process for checking updates // Process for checking updates
Process { Process {
id: checkupdatesProcess id: checkupdatesProcess
command: ["checkupdates"] command: ["checkupdates"]
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode !== 0 && exitCode !== 2) { if (exitCode !== 0 && exitCode !== 2) {
console.warn("[UpdateService] checkupdates failed (code:", exitCode, ")") Logger.warn("ArchUpdater", "checkupdates failed (code:", exitCode, ")")
updatePackages = [] updatePackages = []
return
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
parseCheckupdatesOutput(text) parseCheckupdatesOutput(text)
Logger.log("ArchUpdater", "found", updatePackages.length, "upgradable package(s)")
} }
} }
} }
@ -150,7 +154,4 @@ Singleton {
running: true running: true
onTriggered: doPoll() onTriggered: doPoll()
} }
// Initial check
Component.onCompleted: doPoll()
} }

View file

@ -10,7 +10,7 @@ Singleton {
// Widget registry object mapping widget names to components // Widget registry object mapping widget names to components
property var widgets: ({ property var widgets: ({
"ActiveWindow": activeWindowComponent, "ActiveWindow": activeWindowComponent,
// "ArchUpdater": archUpdaterComponent, "ArchUpdater": archUpdaterComponent,
"Battery": batteryComponent, "Battery": batteryComponent,
"Bluetooth": bluetoothComponent, "Bluetooth": bluetoothComponent,
"Brightness": brightnessComponent, "Brightness": brightnessComponent,
@ -32,9 +32,9 @@ Singleton {
property Component activeWindowComponent: Component { property Component activeWindowComponent: Component {
ActiveWindow {} ActiveWindow {}
} }
// property Component archUpdaterComponent: Component { property Component archUpdaterComponent: Component {
// ArchUpdater {} ArchUpdater {}
// } }
property Component batteryComponent: Component { property Component batteryComponent: Component {
Battery {} Battery {}
} }

View file

@ -144,7 +144,7 @@ Singleton {
function setBluetoothEnabled(enabled) { function setBluetoothEnabled(enabled) {
if (!adapter) { if (!adapter) {
console.warn("BluetoothService: No adapter available") Logger.warn("Bluetooth", "No adapter available")
return return
} }

View file

@ -29,8 +29,7 @@ Singleton {
Logger.log("Wallpapers", "Listing wallpapers") Logger.log("Wallpapers", "Listing wallpapers")
scanning = true scanning = true
wallpaperList = [] wallpaperList = []
// Unsetting, then setting the folder will re-trigger the parsing! // Set the folder directly to avoid model reset issues
folderModel.folder = ""
folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "") folderModel.folder = "file://" + (Settings.data.wallpaper.directory !== undefined ? Settings.data.wallpaper.directory : "")
} }

View file

@ -32,8 +32,6 @@ Loader {
signal closed signal closed
Component.onCompleted: { Component.onCompleted: {
// console.log("Oh Yeah")
// console.log(objectName)
PanelService.registerPanel(root) PanelService.registerPanel(root)
} }

View file

@ -10,4 +10,7 @@ Text {
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
color: Color.mOnSurface color: Color.mOnSurface
renderType: Text.QtRendering
font.hintingPreference: Font.PreferNoHinting
font.kerning: true
} }

View file

@ -1,6 +1,7 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Services import qs.Services
import qs.Commons
Item { Item {
id: root id: root

64
flake.lock generated Normal file
View file

@ -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
}

35
flake.nix Normal file
View file

@ -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;
});
};
}

84
nix/default.nix Normal file
View file

@ -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";
};
}