Merge branch 'bootstrap-icons'

This commit is contained in:
Ly-sec 2025-09-09 16:14:18 +02:00
commit a4534b1611
77 changed files with 2939 additions and 720 deletions

View file

@ -1,19 +1,19 @@
{
"dark": {
"mPrimary": "#ebbcba",
"mOnPrimary": "#1f1d2e",
"mOnPrimary": "#191724",
"mSecondary": "#9ccfd8",
"mOnSecondary": "#1f1d2e",
"mOnSecondary": "#191724",
"mTertiary": "#f6c177",
"mOnTertiary": "#1f1d2e",
"mOnTertiary": "#191724",
"mError": "#eb6f92",
"mOnError": "#1f1d2e",
"mSurface": "#1f1d2e",
"mOnError": "#191724",
"mSurface": "#191724",
"mOnSurface": "#e0def4",
"mSurfaceVariant": "#26233a",
"mOnSurfaceVariant": "#908caa",
"mOutline": "#403d52",
"mShadow": "#1f1d2e"
"mShadow": "#191724"
},
"light": {
"mPrimary": "#d46e6b",

View file

@ -1,270 +0,0 @@
#!/usr/bin/env -S bash
# A Bash script to monitor system stats and output them in JSON format.
# --- Configuration ---
# Default sleep duration in seconds. Can be overridden by the first argument.
SLEEP_DURATION=3
# --- Argument Parsing ---
# Check if a command-line argument is provided for the sleep duration.
if [[ -n "$1" ]]; then
# Basic validation to ensure the argument is a number (integer or float).
if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
SLEEP_DURATION=$1
else
# Output to stderr if the format is invalid.
echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
fi
fi
# --- Global Cache Variables ---
# These variables will store the discovered CPU temperature sensor path and type
# to avoid searching for it on every loop iteration.
TEMP_SENSOR_PATH=""
TEMP_SENSOR_TYPE=""
# Network speed monitoring variables
PREV_RX_BYTES=0
PREV_TX_BYTES=0
PREV_TIME=0
# --- Data Collection Functions ---
#
# Gets memory usage in GB, MB, and as a percentage.
#
get_memory_info() {
awk '
/MemTotal/ {total=$2}
/MemAvailable/ {available=$2}
END {
if (total > 0) {
usage_kb = total - available
usage_gb = usage_kb / 1000000
usage_percent = (usage_kb / total) * 100
printf "%.1f %.0f\n", usage_gb, usage_percent
} else {
# Fallback if /proc/meminfo is unreadable or empty.
print "0.0 0 0"
}
}
' /proc/meminfo
}
#
# Gets the usage percentage of the root filesystem ("/").
#
get_disk_usage() {
# df gets disk usage. --output=pcent shows only the percentage for the root path.
# tail -1 gets the data line, and tr removes the '%' sign and whitespace.
df --output=pcent / | tail -1 | tr -d ' %'
}
#
# Calculates current CPU usage over a short interval.
#
get_cpu_usage() {
# Read all 10 CPU time fields to prevent errors on newer kernels.
read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice < /proc/stat
# Calculate previous total and idle times.
local prev_total_idle=$((prev_idle + prev_iowait))
local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
# Wait for a short period.
sleep 0.05
# Read all 10 CPU time fields again for the second measurement.
read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
# Calculate new total and idle times.
local total_idle=$((idle + iowait))
local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
# Add a check to prevent division by zero if total hasn't changed.
if (( total <= prev_total )); then
echo "0.0"
return
fi
# Calculate the difference over the interval.
local diff_total=$((total - prev_total))
local diff_idle=$((total_idle - prev_total_idle))
# Use awk for floating-point calculation and print the percentage.
awk -v total="$diff_total" -v idle="$diff_idle" '
BEGIN {
if (total > 0) {
# Formula: 100 * (Total - Idle) / Total
usage = 100 * (total - idle) / total
printf "%.1f\n", usage
} else {
print "0.0"
}
}'
}
#
# Finds and returns the CPU temperature in degrees Celsius.
# Caches the sensor path for efficiency.
#
get_cpu_temp() {
# If the sensor path hasn't been found yet, search for it.
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
for dir in /sys/class/hwmon/hwmon*; do
# Check if the 'name' file exists and read it.
if [[ -f "$dir/name" ]]; then
local name
name=$(<"$dir/name")
# Check for supported sensor types.
if [[ "$name" == "coretemp" || "$name" == "k10temp" || "$name" == "zenpower" ]]; then
TEMP_SENSOR_PATH=$dir
TEMP_SENSOR_TYPE=$name
break # Found it, no need to keep searching.
fi
fi
done
fi
# If after searching no sensor was found, return 0.
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
echo 0
return
fi
# --- Get temp based on sensor type ---
if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
# For Intel 'coretemp', average all available temperature sensors.
local total_temp=0
local sensor_count=0
# Use a for loop with a glob to iterate over all temp input files.
# This is more efficient than 'find' for this simple case.
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
# The glob returns the pattern itself if no files match,
# so we must check if the file actually exists.
if [[ -f "$temp_file" ]]; then
total_temp=$((total_temp + $(<"$temp_file")))
sensor_count=$((sensor_count + 1))
fi
done
if (( sensor_count > 0 )); then
# Use awk for the final division to handle potential floating point numbers
# and convert from millidegrees to integer degrees Celsius.
awk -v total="$total_temp" -v count="$sensor_count" 'BEGIN { print int(total / count / 1000) }'
else
# If no sensor files were found, return 0.
echo 0
fi
elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
# For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
local tctl_input=""
for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
# The input file has the same name but with '_input' instead of '_label'.
tctl_input="${label_file%_label}_input"
break
fi
done
if [[ -f "$tctl_input" ]]; then
# Read the temperature and convert from millidegrees to degrees.
echo "$(( $(<"$tctl_input") / 1000 ))"
else
echo 0 # Fallback
fi
elif [[ "$TEMP_SENSOR_TYPE" == "zenpower" ]]; then
# For zenpower, read the first available temp sensor
for temp_file in "$TEMP_SENSOR_PATH"/temp*_input; do
if [[ -f "$temp_file" ]]; then
local temp_value
temp_value=$(cat "$temp_file" | tr -d '\n\r') # Remove any newlines
echo "$((temp_value / 1000))"
return
fi
done
echo 0
if [[ -f "$tctl_input" ]]; then
# Read the temperature and convert from millidegrees to degrees.
echo "$(($(<"$tctl_input") / 1000))"
else
echo 0 # Fallback
fi
else
echo 0 # Should not happen if cache logic is correct.
fi
}
# --- Main Loop ---
# This loop runs indefinitely, gathering and printing stats.
while true; do
# Call the functions to gather all the data.
# get_memory_info
read -r mem_gb mem_per <<< "$(get_memory_info)"
# Command substitution captures the single output from the other functions.
disk_per=$(get_disk_usage)
cpu_usage=$(get_cpu_usage)
cpu_temp=$(get_cpu_temp)
# Get network speeds
current_time=$(date +%s.%N)
total_rx=0
total_tx=0
# Read total bytes from /proc/net/dev for all interfaces
while IFS=: read -r interface stats; do
# Skip only loopback interface, allow other interfaces
if [[ "$interface" =~ ^lo[[:space:]]*$ ]]; then
continue
fi
# Extract rx and tx bytes (fields 1 and 9 in the stats part)
rx_bytes=$(echo "$stats" | awk '{print $1}')
tx_bytes=$(echo "$stats" | awk '{print $9}')
# Add to totals if they are valid numbers
if [[ "$rx_bytes" =~ ^[0-9]+$ ]] && [[ "$tx_bytes" =~ ^[0-9]+$ ]]; then
total_rx=$((total_rx + rx_bytes))
total_tx=$((total_tx + tx_bytes))
fi
done < <(tail -n +3 /proc/net/dev)
# Calculate speeds if we have previous data
rx_speed=0
tx_speed=0
if [[ "$PREV_TIME" != "0" ]]; then
time_diff=$(awk -v current="$current_time" -v prev="$PREV_TIME" 'BEGIN { printf "%.3f", current - prev }')
rx_diff=$((total_rx - PREV_RX_BYTES))
tx_diff=$((total_tx - PREV_TX_BYTES))
# Calculate speeds in bytes per second using awk
rx_speed=$(awk -v rx="$rx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", rx / time }')
tx_speed=$(awk -v tx="$tx_diff" -v time="$time_diff" 'BEGIN { printf "%.0f", tx / time }')
fi
# Update previous values for next iteration
PREV_RX_BYTES=$total_rx
PREV_TX_BYTES=$total_tx
PREV_TIME=$current_time
# Use printf to format the final JSON output string, adding the mem_mb key.
printf '{"cpu": "%s", "cputemp": "%s", "memgb":"%s", "memper": "%s", "diskper": "%s", "rx_speed": "%s", "tx_speed": "%s"}\n' \
"$cpu_usage" \
"$cpu_temp" \
"$mem_gb" \
"$mem_per" \
"$disk_per" \
"$rx_speed" \
"$tx_speed"
# Wait for the specified duration before the next update.
sleep "$SLEEP_DURATION"
done

2090
Commons/Bootstrap.qml Normal file

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "system_update_alt"
icon: "box"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
@ -44,7 +44,7 @@ NPanel {
// Reset button (only show if update failed)
NIconButton {
visible: ArchUpdaterService.updateFailed
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Reset update state"
sizeRatio: 0.8
colorBg: Color.mError
@ -55,7 +55,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
sizeRatio: 0.8
onClicked: root.close()
@ -103,7 +103,7 @@ NPanel {
} // Spacer
NIcon {
text: "hourglass_empty"
icon: "hourglass"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter
@ -143,7 +143,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "terminal"
icon: "terminal"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError
Layout.alignment: Qt.AlignHCenter
@ -181,7 +181,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "package"
icon: "box"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError
Layout.alignment: Qt.AlignHCenter
@ -219,7 +219,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "error"
icon: "exclamation-triangle"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError
Layout.alignment: Qt.AlignHCenter
@ -245,7 +245,7 @@ NPanel {
// Prominent refresh button
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Try checking again"
sizeRatio: 1.2
colorBg: Color.mPrimary
@ -270,7 +270,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "error_outline"
icon: "exclamation-triangle"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mError
Layout.alignment: Qt.AlignHCenter
@ -295,7 +295,7 @@ NPanel {
// Prominent refresh button
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Refresh and try again"
sizeRatio: 1.2
colorBg: Color.mPrimary
@ -323,7 +323,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "check_circle"
icon: "check-lg"
font.pointSize: Style.fontSizeXXXL * scaling
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter
@ -483,7 +483,7 @@ NPanel {
spacing: Style.marginL * scaling
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: ArchUpdaterService.aurBusy ? "Checking for updates..." : (!ArchUpdaterService.canPoll ? "Refresh available soon" : "Refresh package lists")
onClicked: {
ArchUpdaterService.forceRefresh()
@ -495,7 +495,7 @@ NPanel {
}
NIconButton {
icon: "system_update_alt"
icon: "box-fill"
tooltipText: "Update all packages"
enabled: ArchUpdaterService.totalUpdates > 0
onClicked: {
@ -508,7 +508,7 @@ NPanel {
}
NIconButton {
icon: "check_box"
icon: "box"
tooltipText: "Update selected packages"
enabled: ArchUpdaterService.selectedPackagesCount > 0
onClicked: {

View file

@ -139,7 +139,7 @@ Variants {
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_fade.frag.qsb")
}
// Wipe transition shader
@ -164,7 +164,7 @@ Variants {
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_wipe.frag.qsb")
}
// Disc reveal transition shader
@ -191,7 +191,7 @@ Variants {
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_disc.frag.qsb")
}
// Diagonal stripes transition shader
@ -218,7 +218,7 @@ Variants {
property real screenWidth: width
property real screenHeight: height
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/wp_stripes.frag.qsb")
}
// Animation for the transition progress

View file

@ -33,28 +33,34 @@ RowLayout {
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
readonly property real minWidth: 160
readonly property real maxWidth: 400
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling
visible: getTitle() !== ""
// 6% of total width
readonly property real minWidth: Math.max(1, screen.width * 0.06)
readonly property real maxWidth: minWidth * 2
function getTitle() {
return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : ""
}
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling
visible: getTitle() !== ""
function getAppIcon() {
// Try CompositorService first
const focusedWindow = CompositorService.getFocusedWindow()
if (focusedWindow && focusedWindow.appId) {
return Icons.iconForAppId(focusedWindow.appId.toLowerCase())
const idValue = focusedWindow.appId
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
return Icons.iconForAppId(normalizedId.toLowerCase())
}
// Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) {
const activeToplevel = ToplevelManager.activeToplevel
if (activeToplevel.appId) {
return Icons.iconForAppId(activeToplevel.appId.toLowerCase())
const idValue2 = activeToplevel.appId
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
return Icons.iconForAppId(normalizedId2.toLowerCase())
}
}
@ -123,7 +129,7 @@ RowLayout {
font.weight: Style.fontWeightMedium
elide: mouseArea.containsMouse ? Text.ElideNone : Text.ElideRight
verticalAlignment: Text.AlignVCenter
color: Color.mSecondary
color: Color.mPrimary
clip: true
Behavior on Layout.preferredWidth {

View file

@ -29,15 +29,15 @@ NIconButton {
return "terminal"
}
if (!ArchUpdaterService.aurHelperAvailable) {
return "package"
return "box"
}
if (ArchUpdaterService.aurBusy) {
return "sync"
return "arrow-repeat"
}
if (ArchUpdaterService.totalUpdates > 0) {
return "system_update_alt"
return "box-fill"
}
return "task_alt"
return "box"
}
// Tooltip with repo vs AUR breakdown and sample lists

View file

@ -39,7 +39,7 @@ Item {
// Test mode
readonly property bool testMode: false
readonly property int testPercent: 50
readonly property bool testCharging: true
readonly property bool testCharging: false
// Main properties
readonly property var battery: UPower.displayDevice
@ -57,9 +57,7 @@ Item {
// Only notify once we are a below threshold
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
root.hasNotifiedLowBattery = true
// Maybe go with toast ?
Quickshell.execDetached(
["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`])
ToastService.showWarning("Low Battery", `Battery is at ${Math.round(percent)}%. Please connect the charger.`)
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
// Reset when charging starts or when battery recovers 5% above threshold
root.hasNotifiedLowBattery = false
@ -87,11 +85,7 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
charging, isReady)
iconRotation: -90
text: ((isReady && battery.isLaptopBattery) || testMode) ? Math.round(percent) + "%" : "-"
textColor: charging ? Color.mPrimary : Color.mOnSurface
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
text: (isReady || testMode) ? Math.round(percent) + "%" : "-"
autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))

View file

@ -46,8 +46,7 @@ Item {
function getIcon() {
var monitor = getMonitor()
var brightness = monitor ? monitor.brightness : 0
return brightness <= 0 ? "brightness_1" : brightness < 0.33 ? "brightness_low" : brightness
< 0.66 ? "brightness_medium" : "brightness_high"
return brightness <= 0.5 ? "brightness-low" : "brightness-high"
}
// Connection used to open the pill when brightness changes
@ -80,8 +79,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: {
var monitor = getMonitor()

View file

@ -60,6 +60,7 @@ Rectangle {
anchors.centerIn: parent
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NTooltip {

View file

@ -9,12 +9,12 @@ NIconButton {
property ShellScreen screen
property real scaling: 1.0
icon: "contrast"
icon: "transparency"
tooltipText: "Toggle light/dark mode"
sizeRatio: 0.8
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBg: Settings.data.colorSchemes.darkMode ? Color.mSurfaceVariant : Color.mPrimary
colorFg: Settings.data.colorSchemes.darkMode ? Color.mOnSurface : Color.mOnPrimary
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -13,10 +13,10 @@ NIconButton {
sizeRatio: 0.8
icon: "coffee"
icon: "cup"
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake" : "Enable keep awake"
colorBg: Color.mSurfaceVariant
colorFg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mOnSurface
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
onClicked: {
IdleInhibitorService.manualToggle()

View file

@ -24,9 +24,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: "keyboard_alt"
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
icon: "keyboard"
autoHide: false // Important to be false so we can hover as long as we want
text: currentLayout
tooltipText: "Keyboard layout: " + currentLayout

View file

@ -38,8 +38,9 @@ RowLayout {
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
readonly property real minWidth: 160
readonly property real maxWidth: 400
// 6% of total width
readonly property real minWidth: Math.max(1, screen.width * 0.06)
readonly property real maxWidth: minWidth * 2
function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
@ -134,7 +135,7 @@ RowLayout {
NIcon {
id: windowIcon
text: MediaService.isPlaying ? "pause" : "play_arrow"
text: MediaService.isPlaying ? "pause" : "play"
font.pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignVCenter
@ -154,7 +155,8 @@ RowLayout {
id: trackArt
anchors.fill: parent
imagePath: MediaService.trackArtUrl
fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow"
fallbackIcon: MediaService.isPlaying ? "pause" : "play"
fallbackIconSize: 10 * scaling
borderWidth: 0
border.color: Color.transparent
}
@ -178,7 +180,7 @@ RowLayout {
font.weight: Style.fontWeightMedium
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
color: Color.mTertiary
color: Color.mSecondary
Behavior on Layout.preferredWidth {
NumberAnimation {

View file

@ -43,9 +43,9 @@ Item {
function getIcon() {
if (AudioService.inputMuted) {
return "mic_off"
return "mic-mute"
}
return AudioService.inputVolume <= Number.EPSILON ? "mic_off" : (AudioService.inputVolume < 0.33 ? "mic" : "mic")
return AudioService.inputVolume <= Number.EPSILON ? "mic-mute" : (AudioService.inputVolume < 0.33 ? "mic" : "mic")
}
// Connection used to open the pill when input volume changes
@ -92,8 +92,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.inputVolume * 100) + "%"
forceOpen: alwaysShowPercentage

View file

@ -15,12 +15,12 @@ NIconButton {
property real scaling: 1.0
sizeRatio: 0.8
colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface
colorBg: Settings.data.nightLight.enabled ? Color.mPrimary : Color.mSurfaceVariant
colorFg: Settings.data.nightLight.enabled ? Color.mOnPrimary : Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent
icon: Settings.data.nightLight.enabled ? "bedtime" : "bedtime_off"
icon: "moon-stars"
tooltipText: `Night light: ${Settings.data.nightLight.enabled ? "enabled." : "disabled."}\nLeft click to toggle.\nRight click to access settings.`
onClicked: Settings.data.nightLight.enabled = !Settings.data.nightLight.enabled

View file

@ -53,10 +53,10 @@ NIconButton {
}
sizeRatio: 0.8
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications"
icon: Settings.data.notifications.doNotDisturb ? "bell-slash" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'."
colorBg: Color.mSurfaceVariant
colorFg: Settings.data.notifications.doNotDisturb ? Color.mError : Color.mOnSurface
colorFg: Color.mOnSurface
colorBorder: Color.transparent
colorBorderHover: Color.transparent

View file

@ -19,13 +19,13 @@ NIconButton {
function profileIcon() {
if (!hasPP)
return "balance"
return "yin-yang"
if (powerProfiles.profile === PowerProfile.Performance)
return "speed"
return "speedometer2"
if (powerProfiles.profile === PowerProfile.Balanced)
return "balance"
return "yin-yang"
if (powerProfiles.profile === PowerProfile.PowerSaver)
return "eco"
return "leaf"
}
function profileName() {

View file

@ -13,7 +13,7 @@ NIconButton {
sizeRatio: 0.8
icon: "power_settings_new"
icon: "power"
tooltipText: "Power Settings"
colorBg: Color.mSurfaceVariant
colorFg: Color.mError

View file

@ -11,7 +11,7 @@ NIconButton {
property real scaling: 1.0
visible: ScreenRecorderService.isRecording
icon: "videocam"
icon: "camera-video"
tooltipText: "Screen recording is active\nClick to stop recording"
sizeRatio: 0.8
colorBg: Color.mPrimary

View file

@ -33,7 +33,7 @@ NIconButton {
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
icon: useDistroLogo ? "" : "widgets"
icon: useDistroLogo ? "" : "layout-sidebar-inset-reverse"
tooltipText: "Open side panel."
sizeRatio: 0.8

View file

@ -38,12 +38,4 @@ Item {
implicitHeight: Style.barHeight * scaling
width: implicitWidth
height: implicitHeight
// Optional: Add a subtle visual indicator in debug mode
Rectangle {
anchors.fill: parent
color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint
visible: Settings.data.general.debugMode || false
radius: Style.radiusXXS * scaling
}
}

View file

@ -38,6 +38,8 @@ RowLayout {
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage
!== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling
@ -52,126 +54,188 @@ RowLayout {
RowLayout {
id: mainLayout
anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
anchors.centerIn: parent // Better centering than margins
width: parent.width - Style.marginM * scaling * 2
spacing: Style.marginS * scaling
// CPU Usage Component
RowLayout {
id: cpuUsageLayout
spacing: Style.marginXS * scaling
Item {
Layout.preferredWidth: cpuUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
NIcon {
id: cpuUsageIcon
text: "speed"
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: cpuUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NText {
id: cpuUsageText
text: `${SystemStatService.cpuUsage}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
NIcon {
icon: "speedometer2"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.cpuUsage}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
// CPU Temperature Component
RowLayout {
id: cpuTempLayout
// spacing is thin here to compensate for the vertical thermometer icon
spacing: Style.marginXXS * scaling
Item {
Layout.preferredWidth: cpuTempRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showCpuTemp
NIcon {
text: "thermometer"
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: cpuTempRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NText {
text: `${SystemStatService.cpuTemp}°C`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
NIcon {
icon: "fire"
// Fire is so tall, we need to make it smaller
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.cpuTemp}°C`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
// Memory Usage Component
RowLayout {
id: memoryUsageLayout
spacing: Style.marginXS * scaling
Item {
Layout.preferredWidth: memoryUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showMemoryUsage
NIcon {
text: "memory"
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: memoryUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
NIcon {
icon: "cpu"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
// Network Download Speed Component
RowLayout {
id: networkDownloadLayout
spacing: Style.marginXS * scaling
Item {
Layout.preferredWidth: networkDownloadRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats
NIcon {
text: "download"
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: networkDownloadRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NText {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
NIcon {
icon: "cloud-arrow-down"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
// Network Upload Speed Component
RowLayout {
id: networkUploadLayout
spacing: Style.marginXS * scaling
Item {
Layout.preferredWidth: networkUploadRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showNetworkStats
NIcon {
text: "upload"
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
id: networkUploadRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NText {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
NIcon {
icon: "cloud-arrow-up"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
// Disk Usage Component (primary drive)
Item {
Layout.preferredWidth: diskUsageRow.implicitWidth
Layout.preferredHeight: Math.round(Style.capsuleHeight * scaling)
Layout.alignment: Qt.AlignVCenter
visible: showDiskUsage
RowLayout {
id: diskUsageRow
anchors.centerIn: parent
spacing: Style.marginXS * scaling
NIcon {
icon: "hdd"
font.pointSize: Style.fontSizeM * scaling
Layout.alignment: Qt.AlignVCenter
}
NText {
text: `${SystemStatService.diskPercent}%`
font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
color: Color.mPrimary
}
}
}
}

View file

@ -43,9 +43,9 @@ Item {
function getIcon() {
if (AudioService.muted) {
return "volume_off"
return "volume-mute"
}
return AudioService.volume <= Number.EPSILON ? "volume_off" : (AudioService.volume < 0.33 ? "volume_down" : "volume_up")
return AudioService.volume <= 0.2 ? "volume-off" : (AudioService.volume < 0.6 ? "volume-down" : "volume-up")
}
// Connection used to open the pill when volume changes
@ -77,8 +77,6 @@ Item {
rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: getIcon()
iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100) + "%"
forceOpen: alwaysShowPercentage

View file

@ -23,7 +23,7 @@ NIconButton {
icon: {
try {
if (NetworkService.ethernetConnected) {
return "lan"
return "ethernet"
}
let connected = false
let signalStrength = 0
@ -34,7 +34,7 @@ NIconButton {
break
}
}
return connected ? NetworkService.signalIcon(signalStrength) : "wifi_find"
return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off"
} catch (error) {
Logger.error("Wi-Fi", "Error getting icon:", error)
return "signal_wifi_bad"

View file

@ -66,7 +66,7 @@ ColumnLayout {
// One device BT icon
NIcon {
text: BluetoothService.getDeviceIcon(modelData)
icon: BluetoothService.getDeviceIcon(modelData)
font.pointSize: Style.fontSizeXXL * scaling
color: getContentColor(Color.mOnSurface)
Layout.alignment: Qt.AlignVCenter
@ -164,7 +164,7 @@ ColumnLayout {
}
return "Connect"
}
icon: (isBusy ? "hourglass_full" : null)
icon: (isBusy ? "hourglass-split" : null)
onClicked: {
if (modelData.connected) {
BluetoothService.disconnectDevice(modelData)

View file

@ -28,7 +28,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "bluetooth"
icon: "bluetooth"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
@ -42,7 +42,7 @@ NPanel {
}
NIconButton {
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop_circle" : "refresh"
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "arrow-repeat"
tooltipText: "Refresh Devices"
sizeRatio: 0.8
onClicked: {
@ -53,7 +53,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
sizeRatio: 0.8
onClicked: {

View file

@ -22,7 +22,9 @@ Item {
IpcHandler {
target: "screenRecorder"
function toggle() {
ScreenRecorderService.toggleRecording()
if (ScreenRecorderService.isAvailable) {
ScreenRecorderService.toggleRecording()
}
}
}

View file

@ -418,7 +418,7 @@ Loader {
font.weight: Style.fontWeightBold
}
NIcon {
text: "keyboard_alt"
icon: "keyboard"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurface
}
@ -428,7 +428,7 @@ Loader {
spacing: Style.marginS * scaling
visible: batteryIndicator.batteryVisible
NIcon {
text: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
icon: BatteryService.getIcon(batteryIndicator.percent, batteryIndicator.charging,
batteryIndicator.isReady)
font.pointSize: Style.fontSizeM * scaling
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
@ -718,21 +718,47 @@ Loader {
anchors.margins: 50 * scaling
spacing: 20 * scaling
// Shutdown
Rectangle {
Layout.preferredWidth: 60 * scaling
Layout.preferredHeight: 60 * scaling
Layout.preferredWidth: iconPower.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5
color: powerButtonArea.containsMouse ? Color.mError : Qt.alpha(Color.mError, 0.2)
border.color: Color.mError
border.width: Math.max(1, Style.borderM * scaling)
NIcon {
id: iconPower
anchors.centerIn: parent
text: "power_settings_new"
font.pointSize: Style.fontSizeXL * scaling
icon: "power"
font.pointSize: Style.fontSizeXXXL * scaling
color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError
}
// Tooltip (inline rectangle to avoid separate Window during lock)
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: 12 * scaling
radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: powerButtonArea.containsMouse
z: 1
NText {
id: shutdownTooltipText
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: "Shut down the computer."
font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
implicitWidth: shutdownTooltipText.implicitWidth + Style.marginM * 2 * scaling
implicitHeight: shutdownTooltipText.implicitHeight + Style.marginM * 2 * scaling
}
MouseArea {
id: powerButtonArea
anchors.fill: parent
@ -743,21 +769,47 @@ Loader {
}
}
// Reboot
Rectangle {
Layout.preferredWidth: 60 * scaling
Layout.preferredHeight: 60 * scaling
Layout.preferredWidth: iconReboot.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5
color: restartButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, Style.opacityLight)
border.color: Color.mPrimary
border.width: Math.max(1, Style.borderM * scaling)
NIcon {
id: iconReboot
anchors.centerIn: parent
text: "restart_alt"
font.pointSize: Style.fontSizeXL * scaling
icon: "arrow-repeat"
font.pointSize: Style.fontSizeXXXL * scaling
color: restartButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
}
// Tooltip
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: 12 * scaling
radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: restartButtonArea.containsMouse
z: 1
NText {
id: restartTooltipText
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: "Restart the computer."
font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
implicitWidth: restartTooltipText.implicitWidth + Style.marginM * 2 * scaling
implicitHeight: restartTooltipText.implicitHeight + Style.marginM * 2 * scaling
}
MouseArea {
id: restartButtonArea
anchors.fill: parent
@ -765,24 +817,51 @@ Loader {
onClicked: {
CompositorService.reboot()
}
// Tooltip handled via inline rectangle visibility
}
}
// Suspend
Rectangle {
Layout.preferredWidth: 60 * scaling
Layout.preferredHeight: 60 * scaling
Layout.preferredWidth: iconSuspend.implicitWidth + Style.marginXL * scaling
Layout.preferredHeight: Layout.preferredWidth
radius: width * 0.5
color: suspendButtonArea.containsMouse ? Color.mSecondary : Qt.alpha(Color.mSecondary, 0.2)
border.color: Color.mSecondary
border.width: Math.max(1, Style.borderM * scaling)
NIcon {
id: iconSuspend
anchors.centerIn: parent
text: "bedtime"
font.pointSize: Style.fontSizeXL * scaling
icon: "pause-fill"
font.pointSize: Style.fontSizeXXXL * scaling
color: suspendButtonArea.containsMouse ? Color.mOnSecondary : Color.mSecondary
}
// Tooltip
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: 12 * scaling
radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: suspendButtonArea.containsMouse
z: 1
NText {
id: suspendTooltipText
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: "Suspend the system."
font.pointSize: Style.fontSizeM * scaling
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
implicitWidth: suspendTooltipText.implicitWidth + Style.marginM * 2 * scaling
implicitHeight: suspendTooltipText.implicitHeight + Style.marginM * 2 * scaling
}
MouseArea {
id: suspendButtonArea
anchors.fill: parent
@ -790,6 +869,7 @@ Loader {
onClicked: {
CompositorService.suspend()
}
// Tooltip handled via inline rectangle visibility
}
}
}

View file

@ -205,14 +205,12 @@ Variants {
Layout.fillWidth: true
spacing: Style.marginM * scaling
// Avatar
// Image
NImageCircled {
id: appAvatar
Layout.preferredWidth: 40 * scaling
Layout.preferredHeight: 40 * scaling
Layout.alignment: Qt.AlignTop
imagePath: model.image && model.image !== "" ? model.image : ""
fallbackIcon: ""
borderColor: Color.transparent
borderWidth: 0
visible: (model.image && model.image !== "")
@ -294,7 +292,7 @@ Variants {
// Close button positioned absolutely
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
sizeRatio: 0.6
anchors.top: parent.top

View file

@ -31,7 +31,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: "notifications"
icon: "bell"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
@ -45,21 +45,21 @@ NPanel {
}
NIconButton {
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications_active"
icon: Settings.data.notifications.doNotDisturb ? "bell-slash" : "bell"
tooltipText: Settings.data.notifications.doNotDisturb ? "'Do Not Disturb' is enabled." : "'Do Not Disturb' is disabled."
sizeRatio: 0.8
onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
}
NIconButton {
icon: "delete"
icon: "trash"
tooltipText: "Clear history"
sizeRatio: 0.8
onClicked: NotificationService.clearHistory()
}
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
sizeRatio: 0.8
onClicked: {
@ -85,7 +85,7 @@ NPanel {
}
NIcon {
text: "notifications_off"
icon: "bell-slash"
font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
@ -175,7 +175,7 @@ NPanel {
// Delete button
NIconButton {
icon: "delete"
icon: "trash"
tooltipText: "Delete notification"
sizeRatio: 0.7
Layout.alignment: Qt.AlignTop

View file

@ -29,27 +29,27 @@ NPanel {
property int selectedIndex: 0
readonly property var powerOptions: [{
"action": "lock",
"icon": "lock_outline",
"icon": "lock",
"title": "Lock",
"subtitle": "Lock your session"
}, {
"action": "suspend",
"icon": "bedtime",
"icon": "pause-circle",
"title": "Suspend",
"subtitle": "Put the system to sleep"
}, {
"action": "reboot",
"icon": "refresh",
"icon": "arrow-repeat",
"title": "Reboot",
"subtitle": "Restart the system"
}, {
"action": "logout",
"icon": "exit_to_app",
"icon": "escape",
"title": "Logout",
"subtitle": "End your session"
}, {
"action": "shutdown",
"icon": "power_settings_new",
"icon": "power",
"title": "Shutdown",
"subtitle": "Turn off the system",
"isShutdown": true
@ -276,7 +276,7 @@ NPanel {
}
NIconButton {
icon: timerActive ? "back_hand" : "close"
icon: timerActive ? "x-square" : "x-lg"
tooltipText: timerActive ? "Cancel Timer" : "Close"
Layout.alignment: Qt.AlignVCenter
colorBg: timerActive ? Qt.alpha(Color.mError, 0.08) : Color.transparent
@ -360,7 +360,7 @@ NPanel {
id: iconElement
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: buttonRoot.icon
icon: buttonRoot.icon
color: {
if (buttonRoot.pending)
return Color.mPrimary

View file

@ -85,7 +85,7 @@ NBox {
}
NIconButton {
icon: "add"
icon: "plus-lg"
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
@ -170,7 +170,7 @@ NBox {
Loader {
active: BarWidgetRegistry.widgetHasUserSettings(modelData.id)
sourceComponent: NIconButton {
icon: "settings"
icon: "gear"
sizeRatio: 0.6
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface
@ -210,7 +210,7 @@ NBox {
}
NIconButton {
icon: "close"
icon: "x-lg"
sizeRatio: 0.6
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
colorBg: Color.mOnSurface

View file

@ -84,7 +84,7 @@ Popup {
}
NIconButton {
icon: "close"
icon: "x-lg"
onClicked: settingsPopup.close()
}
}
@ -107,6 +107,7 @@ Popup {
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
Item {
Layout.fillWidth: true
@ -120,7 +121,7 @@ Popup {
NButton {
text: "Apply"
icon: "check"
icon: "check-lg"
onClicked: {
if (settingsLoader.item && settingsLoader.item.saveSettings) {
var newSettings = settingsLoader.item.saveSettings()

View file

@ -16,10 +16,13 @@ ColumnLayout {
// Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
property int valueWarningThreshold: widgetData.warningThreshold
!== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage
settings.warningThreshold = valueWarningThreshold
return settings
}
@ -28,4 +31,14 @@ ColumnLayout {
checked: root.valueAlwaysShowPercentage
onToggled: checked => root.valueAlwaysShowPercentage = checked
}
NSpinBox {
label: "Low battery warning threshold"
description: "Show a warning when battery falls below this percentage."
value: valueWarningThreshold
suffix: "%"
minimum: 5
maximum: 50
onValueChanged: valueWarningThreshold = value
}
}

View file

@ -1,6 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import qs.Commons
import qs.Widgets
import qs.Services
@ -9,7 +10,6 @@ ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
@ -22,16 +22,190 @@ ColumnLayout {
return settings
}
// Icon setting
NTextInput {
id: iconInput
Layout.fillWidth: true
label: "Icon Name"
description: "Choose a name from the Material Icon set."
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
description: "Pick from Bootstrap Icons or type a name."
placeholderText: "Enter icon name (e.g., speedometer2, gear, house)"
text: widgetData?.icon || widgetMetadata.icon
}
RowLayout {
spacing: Style.marginS * scaling
Layout.alignment: Qt.AlignLeft
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: iconInput.text
visible: iconInput.text !== ""
}
NButton {
text: "Browse"
onClicked: iconPicker.open()
}
}
Popup {
id: iconPicker
modal: true
property real panelWidth: {
var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling)
w = Math.min(w, Screen.width - Style.marginL * 2)
return w
}
property real panelHeight: {
var h = Math.round(Math.max(Screen.height * 0.65, 700) * scaling)
h = Math.min(h, Screen.height - Style.barHeight * scaling - Style.marginL * 2)
return h
}
width: panelWidth
height: panelHeight
anchors.centerIn: Overlay.overlay
padding: Style.marginXL * scaling
property string query: ""
property string selectedIcon: ""
property var allIcons: Object.keys(Bootstrap.icons)
property var filteredIcons: allIcons.filter(function (name) {
return query === "" || name.toLowerCase().indexOf(query.toLowerCase()) !== -1
})
readonly property int tileBase: Math.round(112 * scaling)
readonly property int columns: Math.max(3, Math.floor(grid.width / (tileBase + Style.marginS * 2)))
readonly property int cellW: Math.floor(grid.width / columns)
readonly property int cellH: Math.round(cellW * 0.7 + 36 * scaling)
background: Rectangle {
color: Color.mSurface
radius: Style.radiusL * scaling
border.color: Color.mPrimary
border.width: Style.borderM * scaling
}
ColumnLayout {
anchors.fill: parent
spacing: Style.marginM * scaling
// Title row
RowLayout {
Layout.fillWidth: true
NText {
text: "Icon Picker"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
}
NIconButton {
icon: "x-lg"
onClicked: iconPicker.close()
}
}
NDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NTextInput {
Layout.fillWidth: true
label: "Search"
placeholderText: "Search (e.g., arrow, battery, cloud)"
text: iconPicker.query
onTextChanged: iconPicker.query = text.trim().toLowerCase()
}
}
// Icon grid
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
GridView {
id: grid
anchors.fill: parent
anchors.margins: Style.marginM * scaling
cellWidth: iconPicker.cellW
cellHeight: iconPicker.cellH
model: iconPicker.filteredIcons
delegate: Rectangle {
width: grid.cellWidth
height: grid.cellHeight
radius: Style.radiusS * scaling
clip: true
color: (iconPicker.selectedIcon === modelData) ? Qt.alpha(Color.mPrimary, 0.15) : "transparent"
border.color: (iconPicker.selectedIcon === modelData) ? Color.mPrimary : Qt.rgba(0, 0, 0, 0)
border.width: (iconPicker.selectedIcon === modelData) ? Style.borderS * scaling : 0
MouseArea {
anchors.fill: parent
onClicked: iconPicker.selectedIcon = modelData
onDoubleClicked: {
iconPicker.selectedIcon = modelData
iconInput.text = iconPicker.selectedIcon
iconPicker.close()
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginS * scaling
spacing: Style.marginS * scaling
Item {
Layout.fillHeight: true
Layout.preferredHeight: 4 * scaling
}
NIcon {
Layout.alignment: Qt.AlignHCenter
icon: modelData
font.pointSize: Style.fontSizeXXXL * scaling
}
NText {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: Style.marginXS * scaling
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
horizontalAlignment: Text.AlignHCenter
color: Color.mOnSurfaceVariant
font.pointSize: Style.fontSizeXS * scaling
text: modelData
}
Item {
Layout.fillHeight: true
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
Item {
Layout.fillWidth: true
}
NButton {
text: "Cancel"
outlined: true
onClicked: iconPicker.close()
}
NButton {
text: "Apply"
icon: "check-lg"
enabled: iconPicker.selectedIcon !== ""
onClicked: {
iconInput.text = iconPicker.selectedIcon
iconPicker.close()
}
}
}
}
}
NTextInput {
id: leftClickExecInput
Layout.fillWidth: true

View file

@ -21,6 +21,7 @@ ColumnLayout {
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
property bool valueShowNetworkStats: widgetData.showNetworkStats
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
@ -29,6 +30,7 @@ ColumnLayout {
settings.showMemoryUsage = valueShowMemoryUsage
settings.showMemoryAsPercent = valueShowMemoryAsPercent
settings.showNetworkStats = valueShowNetworkStats
settings.showDiskUsage = valueShowDiskUsage
return settings
}
@ -59,7 +61,7 @@ ColumnLayout {
NToggle {
id: showMemoryAsPercent
Layout.fillWidth: true
label: "Show memory as percentage"
label: "Memory as percentage"
checked: valueShowMemoryAsPercent
onToggled: checked => valueShowMemoryAsPercent = checked
}
@ -71,4 +73,12 @@ ColumnLayout {
checked: valueShowNetworkStats
onToggled: checked => valueShowNetworkStats = checked
}
NToggle {
id: showDiskUsage
Layout.fillWidth: true
label: "Storage usage"
checked: valueShowDiskUsage
onToggled: checked => valueShowDiskUsage = checked
}
}

View file

@ -123,42 +123,42 @@ NPanel {
let newTabs = [{
"id": SettingsPanel.Tab.General,
"label": "General",
"icon": "tune",
"icon": "box",
"source": generalTab
}, {
"id": SettingsPanel.Tab.Bar,
"label": "Bar",
"icon": "web_asset",
"icon": "segmented-nav",
"source": barTab
}, {
"id": SettingsPanel.Tab.Launcher,
"label": "Launcher",
"icon": "apps",
"icon": "rocket",
"source": launcherTab
}, {
"id": SettingsPanel.Tab.Audio,
"label": "Audio",
"icon": "volume_up",
"icon": "speaker",
"source": audioTab
}, {
"id": SettingsPanel.Tab.Display,
"label": "Display",
"icon": "monitor",
"icon": "display",
"source": displayTab
}, {
"id": SettingsPanel.Tab.Network,
"label": "Network",
"icon": "lan",
"icon": "ethernet",
"source": networkTab
}, {
"id": SettingsPanel.Tab.Brightness,
"label": "Brightness",
"icon": "brightness_6",
"icon": "brightness-high",
"source": brightnessTab
}, {
"id": SettingsPanel.Tab.Weather,
"label": "Weather",
"icon": "partly_cloudy_day",
"icon": "cloud-sun",
"source": weatherTab
}, {
"id": SettingsPanel.Tab.ColorScheme,
@ -168,7 +168,7 @@ NPanel {
}, {
"id": SettingsPanel.Tab.Wallpaper,
"label": "Wallpaper",
"icon": "image",
"icon": "easel",
"source": wallpaperTab
}]
@ -177,7 +177,7 @@ NPanel {
newTabs.push({
"id": SettingsPanel.Tab.WallpaperSelector,
"label": "Wallpaper Selector",
"icon": "wallpaper_slideshow",
"icon": "image",
"source": wallpaperSelectorTab
})
}
@ -185,17 +185,17 @@ NPanel {
newTabs.push({
"id": SettingsPanel.Tab.ScreenRecorder,
"label": "Screen Recorder",
"icon": "videocam",
"icon": "camera-video",
"source": screenRecorderTab
}, {
"id": SettingsPanel.Tab.Hooks,
"label": "Hooks",
"icon": "cable",
"icon": "link-45deg",
"source": hooksTab
}, {
"id": SettingsPanel.Tab.About,
"label": "About",
"icon": "info",
"icon": "info-circle",
"source": aboutTab
})
@ -400,15 +400,13 @@ NPanel {
anchors.fill: parent
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
spacing: Style.marginS * scaling
spacing: Style.marginM * scaling
// Tab icon
NIcon {
text: modelData.icon
text: Bootstrap.icons[modelData.icon]
color: tabTextColor
font.pointSize: Style.fontSizeL * scaling
}
// Tab label
NText {
text: modelData.label
@ -416,6 +414,7 @@ NPanel {
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
}
@ -473,7 +472,7 @@ NPanel {
// Close button
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
Layout.alignment: Qt.AlignVCenter
onClicked: root.close()

View file

@ -272,7 +272,7 @@ ColumnLayout {
// Button aligned to the center of the actual input field
NIconButton {
icon: "add"
icon: "plus-lg"
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0
onClicked: {
@ -322,7 +322,7 @@ ColumnLayout {
}
NIconButton {
icon: "close"
icon: "x-lg"
sizeRatio: 0.8
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Style.marginXS * scaling

View file

@ -181,7 +181,7 @@ ColumnLayout {
}
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Reset scaling"
onClicked: ScalingService.setScreenScale(modelData, 1.0)
}

View file

@ -96,7 +96,7 @@ ColumnLayout {
}
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Refresh wallpaper list"
onClicked: {
WallpaperService.refreshWallpapersList()
@ -181,7 +181,7 @@ ColumnLayout {
visible: isSelected
NIcon {
text: "check"
icon: "check-lg"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSecondary

View file

@ -31,7 +31,7 @@ NBox {
}
NIcon {
text: "album"
icon: "disc"
font.pointSize: Style.fontSizeXXXL * 2.5 * scaling
color: Color.mPrimary
Layout.alignment: Qt.AlignHCenter
@ -162,14 +162,14 @@ NBox {
anchors.fill: parent
anchors.margins: Style.marginXS * scaling
imagePath: MediaService.trackArtUrl
fallbackIcon: "music_note"
fallbackIcon: "disc"
borderColor: Color.mOutline
borderWidth: Math.max(1, Style.borderS * scaling)
}
// Fallback icon when no album art available
NIcon {
text: "album"
icon: "disc"
color: Color.mPrimary
font.pointSize: Style.fontSizeL * 12 * scaling
visible: !trackArt.visible
@ -307,7 +307,7 @@ NBox {
// Previous button
NIconButton {
icon: "skip_previous"
icon: "skip-start"
tooltipText: "Previous Media"
visible: MediaService.canGoPrevious
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
@ -315,7 +315,7 @@ NBox {
// Play/Pause button
NIconButton {
icon: MediaService.isPlaying ? "pause" : "play_arrow"
icon: MediaService.isPlaying ? "pause" : "play"
tooltipText: MediaService.isPlaying ? "Pause" : "Play"
visible: (MediaService.canPlay || MediaService.canPause)
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
@ -323,7 +323,7 @@ NBox {
// Next button
NIconButton {
icon: "skip_next"
icon: "skip-end"
tooltipText: "Next media"
visible: MediaService.canGoNext
onClicked: MediaService.canGoNext ? MediaService.next() : {}

View file

@ -28,7 +28,7 @@ NBox {
}
// Performance
NIconButton {
icon: "speed"
icon: "speedometer2"
tooltipText: "Set performance power profile."
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -37,12 +37,13 @@ NBox {
onClicked: {
if (enabled) {
powerProfiles.profile = PowerProfile.Performance
ToastService.showNotice("Power Profile", "Performance")
}
}
}
// Balanced
NIconButton {
icon: "balance"
icon: "yin-yang"
tooltipText: "Set balanced power profile."
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -51,12 +52,13 @@ NBox {
onClicked: {
if (enabled) {
powerProfiles.profile = PowerProfile.Balanced
ToastService.showNotice("Power Profile", "Balanced")
}
}
}
// Eco
NIconButton {
icon: "eco"
icon: "leaf"
tooltipText: "Set eco power profile."
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
@ -65,6 +67,7 @@ NBox {
onClicked: {
if (enabled) {
powerProfiles.profile = PowerProfile.PowerSaver
ToastService.showNotice("Power Profile", "Power Saver")
}
}
}

View file

@ -47,7 +47,8 @@ NBox {
}
NText {
text: `System uptime: ${uptimeText}`
color: Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
}
@ -58,7 +59,7 @@ NBox {
Layout.fillWidth: true
}
NIconButton {
icon: "settings"
icon: "gear"
tooltipText: "Open settings."
onClicked: {
settingsPanel.requestedTab = SettingsPanel.Tab.General
@ -68,7 +69,7 @@ NBox {
NIconButton {
id: powerButton
icon: "power_settings_new"
icon: "power"
tooltipText: "Power menu."
onClicked: {
powerPanel.open(screen)
@ -78,7 +79,7 @@ NBox {
NIconButton {
id: closeButton
icon: "close"
icon: "x-lg"
tooltipText: "Close side panel."
onClicked: {
sidePanel.close()

View file

@ -24,7 +24,7 @@ NBox {
NCircleStat {
value: SystemStatService.cpuUsage
icon: "speed"
icon: "speedometer2"
flat: true
contentScale: 0.8
width: 72 * scaling
@ -33,7 +33,7 @@ NBox {
NCircleStat {
value: SystemStatService.cpuTemp
suffix: "°C"
icon: "device_thermostat"
icon: "fire"
flat: true
contentScale: 0.8
width: 72 * scaling
@ -41,7 +41,7 @@ NBox {
}
NCircleStat {
value: SystemStatService.memPercent
icon: "memory"
icon: "cpu"
flat: true
contentScale: 0.8
width: 72 * scaling
@ -49,7 +49,7 @@ NBox {
}
NCircleStat {
value: SystemStatService.diskPercent
icon: "hard_drive"
icon: "hdd"
flat: true
contentScale: 0.8
width: 72 * scaling

View file

@ -25,11 +25,14 @@ NBox {
}
// Screen Recorder
NIconButton {
icon: "videocam"
tooltipText: ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording."
icon: "camera-video"
enabled: ScreenRecorderService.isAvailable
tooltipText: ScreenRecorderService.isAvailable ? (ScreenRecorderService.isRecording ? "Stop screen recording." : "Start screen recording.") : "Screen recorder not installed."
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant
colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary
onClicked: {
if (!ScreenRecorderService.isAvailable)
return
ScreenRecorderService.toggleRecording()
// If we were not recording and we just initiated a start, close the panel
if (!ScreenRecorderService.isRecording) {
@ -41,7 +44,7 @@ NBox {
// Idle Inhibitor
NIconButton {
icon: "coffee"
icon: "cup"
tooltipText: IdleInhibitorService.isInhibited ? "Disable keep awake." : "Enable keep awake."
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary

View file

@ -27,7 +27,7 @@ NBox {
RowLayout {
spacing: Style.marginS * scaling
NIcon {
text: weatherReady ? LocationService.weatherSymbolFromCode(
icon: weatherReady ? LocationService.weatherSymbolFromCode(
LocationService.data.weather.current_weather.weathercode) : ""
font.pointSize: Style.fontSizeXXXL * 1.75 * scaling
color: Color.mPrimary
@ -98,7 +98,7 @@ NBox {
color: Color.mOnSurface
}
NIcon {
text: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}

View file

@ -34,7 +34,7 @@ NPanel {
spacing: Style.marginM * scaling
NIcon {
text: Settings.data.network.wifiEnabled ? "wifi" : "wifi_off"
icon: Settings.data.network.wifiEnabled ? "wifi" : "wifi-off"
font.pointSize: Style.fontSizeXXL * scaling
color: Settings.data.network.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant
}
@ -55,7 +55,7 @@ NPanel {
}
NIconButton {
icon: "refresh"
icon: "arrow-repeat"
tooltipText: "Refresh"
sizeRatio: 0.8
enabled: Settings.data.network.wifiEnabled && !NetworkService.scanning
@ -63,7 +63,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
tooltipText: "Close"
sizeRatio: 0.8
onClicked: root.close()
@ -91,7 +91,7 @@ NPanel {
spacing: Style.marginS * scaling
NIcon {
text: "error"
icon: "exclamation-triangle"
font.pointSize: Style.fontSizeL * scaling
color: Color.mError
}
@ -105,7 +105,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
sizeRatio: 0.6
onClicked: NetworkService.lastError = ""
}
@ -129,7 +129,7 @@ NPanel {
}
NIcon {
text: "wifi_off"
icon: "wifi-off"
font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
@ -245,7 +245,7 @@ NPanel {
spacing: Style.marginS * scaling
NIcon {
text: NetworkService.signalIcon(modelData.signal)
icon: NetworkService.signalIcon(modelData.signal)
font.pointSize: Style.fontSizeXXL * scaling
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
}
@ -377,7 +377,7 @@ NPanel {
&& NetworkService.connectingTo !== modelData.ssid
&& NetworkService.forgettingNetwork !== modelData.ssid
&& NetworkService.disconnectingFrom !== modelData.ssid
icon: "delete"
icon: "trash"
tooltipText: "Forget network"
sizeRatio: 0.7
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
@ -492,7 +492,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
sizeRatio: 0.8
onClicked: {
passwordSsid = ""
@ -547,7 +547,7 @@ NPanel {
}
NIconButton {
icon: "close"
icon: "x-lg"
sizeRatio: 0.8
onClicked: expandedSsid = ""
}
@ -571,7 +571,7 @@ NPanel {
}
NIcon {
text: "wifi_find"
icon: "search"
font.pointSize: 64 * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
@ -586,7 +586,7 @@ NPanel {
NButton {
text: "Scan again"
icon: "refresh"
icon: "arrow-repeat"
Layout.alignment: Qt.AlignHCenter
onClicked: NetworkService.scan()
}

View file

@ -21,6 +21,11 @@
</a>
</p>
> ⚠️ **BREAKING CHANGE:**
> We transitioned from using Material Symbols to Bootstrap Icons, that means you will have to install the new font.
---
A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.
Features a modern modular architecture with a status bar, notification system, control panel, comprehensive system integration, and more — all styled with a warm lavender palette, or your favorite color scheme!
@ -66,7 +71,7 @@ Features a modern modular architecture with a status bar, notification system, c
- `quickshell-git` - Core shell framework
- `ttf-roboto` - The default font used for most of the UI
- `inter-font` - The default font used for Headers (ex: clock on the LockScreen)
- `ttf-material-symbols-variable-git` - Icon font for UI elements
- `ttf-bootstrap-icons` - Icon font for UI elements
- `gpu-screen-recorder` - Screen recording functionality
- `brightnessctl` - For internal/laptop monitor brightness
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)

View file

@ -62,6 +62,8 @@ Singleton {
function onMutedChanged() {
root._muted = (sink?.audio.muted ?? true)
Logger.log("AudioService", "OnMuteChanged:", root._muted)
// Toast: audio output mute toggle
ToastService.showNotice("Audio Output", root._muted ? "Muted" : "Unmuted")
}
}
@ -79,6 +81,8 @@ Singleton {
function onMutedChanged() {
root._inputMuted = (source?.audio.muted ?? true)
Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted)
// Toast: microphone mute toggle
ToastService.showNotice("Microphone", root._inputMuted ? "Muted" : "Unmuted")
}
}

View file

@ -84,7 +84,8 @@ Singleton {
"showCpuTemp": true,
"showMemoryUsage": true,
"showMemoryAsPercent": false,
"showNetworkStats": false
"showNetworkStats": false,
"showDiskUsage": false
},
"Workspace": {
"allowUserSettings": true,

View file

@ -2,6 +2,8 @@ pragma Singleton
import Quickshell
import Quickshell.Services.UPower
import qs.Commons
import qs.Services
Singleton {
id: root
@ -9,41 +11,20 @@ Singleton {
// Choose icon based on charge and charging state
function getIcon(percent, charging, isReady) {
if (!isReady) {
return "battery_error"
return "exclamation-triangle"
}
if (charging) {
if (percent >= 95)
return "battery_full"
if (percent >= 85)
return "battery_charging_90"
if (percent >= 65)
return "battery_charging_80"
if (percent >= 55)
return "battery_charging_60"
if (percent >= 45)
return "battery_charging_50"
if (percent >= 25)
return "battery_charging_30"
if (percent >= 0)
return "battery_charging_20"
return "battery-charging"
} else {
if (percent >= 95)
return "battery_full"
if (percent >= 85)
return "battery_6_bar"
if (percent >= 70)
return "battery_5_bar"
if (percent >= 55)
return "battery_4_bar"
if (percent >= 40)
return "battery_3_bar"
return "battery-full"
if (percent >= 45)
return "battery-half"
if (percent >= 25)
return "battery_2_bar"
if (percent >= 10)
return "battery_1_bar"
return "battery-low"
if (percent >= 0)
return "battery_0_bar"
return "battery"
}
}
}

View file

@ -62,17 +62,17 @@ Singleton {
}
if (icon.includes("mouse") || name.includes("mouse")) {
return "mouse"
return "mouse-2"
}
if (icon.includes("keyboard") || name.includes("keyboard")) {
return "keyboard"
}
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") || name.includes("android")
|| name.includes("samsung")) {
return "smartphone"
return "phone"
}
if (icon.includes("watch") || name.includes("watch")) {
return "watch"
return "smartwatch"
}
if (icon.includes("speaker") || name.includes("speaker")) {
return "speaker"

View file

@ -23,6 +23,11 @@ Singleton {
// Re-apply current scheme to pick the right variant
applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
// Toast: dark/light mode switched
const enabled = !!Settings.data.colorSchemes.darkMode
const label = enabled ? "Dark Mode" : "Light Mode"
const description = enabled ? "Enabled" : "Enabled"
ToastService.showNotice(label, description)
}
}

View file

@ -192,9 +192,11 @@ Singleton {
}
windowsList.push({
"id": toplevel.address || "",
"title": toplevel.title || "",
"appId": appId,
"id": (toplevel.address !== undefined
&& toplevel.address !== null) ? String(toplevel.address) : "",
"title": (toplevel.title !== undefined && toplevel.title !== null) ? String(
toplevel.title) : "",
"appId": (appId !== undefined && appId !== null) ? String(appId) : "",
"workspaceId": toplevel.workspace?.id || null,
"isFocused": toplevel.activated === true
})

View file

@ -13,6 +13,7 @@ Singleton {
property ListModel displayFonts: ListModel {}
property bool fontsLoaded: false
// -------------------------------------------
function init() {
Logger.log("Font", "Service started")
loadSystemFonts()

View file

@ -231,21 +231,23 @@ Singleton {
// --------------------------------
function weatherSymbolFromCode(code) {
if (code === 0)
return "sunny"
return "sun"
if (code === 1 || code === 2)
return "partly_cloudy_day"
return "cloud-sun"
if (code === 3)
return "cloud"
if (code >= 45 && code <= 48)
return "foggy"
return "cloud-haze"
if (code >= 51 && code <= 67)
return "rainy"
return "cloud-rain"
if (code >= 71 && code <= 77)
return "weather_snowy"
if (code >= 80 && code <= 82)
return "rainy"
return "cloud-snow"
if (code >= 71 && code <= 77)
return "cloud-snow"
if (code >= 85 && code <= 86)
return "cloud-snow"
if (code >= 95 && code <= 99)
return "thunderstorm"
return "cloud-lightning"
return "cloud"
}

View file

@ -202,14 +202,12 @@ Singleton {
// Helper functions
function signalIcon(signal) {
if (signal >= 80)
return "network_wifi"
if (signal >= 60)
return "network_wifi_3_bar"
if (signal >= 40)
return "network_wifi_2_bar"
return "wifi"
if (signal >= 50)
return "wifi-2"
if (signal >= 20)
return "network_wifi_1_bar"
return "signal_wifi_0_bar"
return "wifi-1"
return "dot"
}
function isSecured(security) {

View file

@ -50,6 +50,9 @@ Singleton {
target: Settings.data.nightLight
function onEnabledChanged() {
apply()
// Toast: night light toggled
const enabled = !!Settings.data.nightLight.enabled
ToastService.showNotice("Night Light", enabled ? "Enabled" : "Disabled")
}
function onNightTempChanged() {
apply()

View file

@ -13,6 +13,17 @@ Singleton {
property bool isRecording: false
property bool isPending: false
property string outputPath: ""
property bool isAvailable: false
Component.onCompleted: {
checkAvailability()
}
function checkAvailability() {
// Detect native or Flatpak gpu-screen-recorder
availabilityCheckProcess.command = ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"]
availabilityCheckProcess.running = true
}
// Start or Stop recording
function toggleRecording() {
@ -21,6 +32,9 @@ Singleton {
// Start screen recording using Quickshell.execDetached
function startRecording() {
if (!isAvailable) {
return
}
if (isRecording || isPending) {
return
}
@ -88,6 +102,18 @@ Singleton {
}
}
// Availability check process
Process {
id: availabilityCheckProcess
command: ["sh", "-c", "true"]
onExited: function (exitCode, exitStatus) {
// exitCode 0 means available, non-zero means unavailable
root.isAvailable = (exitCode === 0)
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
Timer {
id: pendingTimer
interval: 2000 // Wait 2 seconds to see if process stays alive

View file

@ -321,10 +321,8 @@ Singleton {
// -------------------------------------------------------
// Helper function to format network speeds
function formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) {
return bytesPerSecond.toFixed(0) + "B/s"
} else if (bytesPerSecond < 1024 * 1024) {
return (bytesPerSecond / 1024).toFixed(0) + "KB/s"
if (bytesPerSecond < 1024 * 1024) {
return (bytesPerSecond / 1024).toFixed(1) + "KB/s"
} else if (bytesPerSecond < 1024 * 1024 * 1024) {
return (bytesPerSecond / (1024 * 1024)).toFixed(1) + "MB/s"
} else {

View file

@ -185,6 +185,11 @@ Singleton {
// Process the message queue
function processQueue() {
if (messageQueue.length === 0 || allToasts.length === 0) {
// Added this so we don't accidentally get duplicate toasts
// if it causes issues, remove it and we'll find a different solution
if (allToasts.length === 0 && messageQueue.length > 0) {
messageQueue = []
}
isShowingToast = false
return
}

View file

@ -80,27 +80,29 @@ Rectangle {
spacing: Style.marginXS * scaling
// Icon (optional)
NIcon {
Layout.alignment: Qt.AlignVCenter
layoutTopMargin: 1 * scaling
visible: root.icon !== ""
text: root.icon
font.pointSize: root.iconSize
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.outlined) {
if (root.pressed || root.hovered)
return root.backgroundColor
return root.backgroundColor
}
return root.textColor
}
Loader {
active: root.icon !== ""
sourceComponent: NIcon {
Layout.alignment: Qt.AlignVCenter
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
icon: root.icon
font.pointSize: root.iconSize
color: {
if (!root.enabled)
return Color.mOnSurfaceVariant
if (root.outlined) {
if (root.pressed || root.hovered)
return root.backgroundColor
return root.backgroundColor
}
return root.textColor
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.OutCubic
}
}
}
}

View file

@ -57,7 +57,7 @@ RowLayout {
NIcon {
visible: root.checked
anchors.centerIn: parent
text: "check"
icon: "check-lg"
color: root.activeOnColor
font.pointSize: Math.max(Style.fontSizeS, root.baseSize * 0.7) * scaling
}

View file

@ -88,20 +88,21 @@ Rectangle {
// Tiny circular badge for the icon, positioned using anchors within the gauge
Rectangle {
id: iconBadge
width: 28 * scaling * contentScale
width: iconText.implicitWidth + Style.marginXS * scaling
height: width
radius: width / 2
color: Color.mSurface
color: Color.mPrimary
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -6 * scaling * contentScale
anchors.topMargin: Style.marginXXS * scaling * contentScale
anchors.rightMargin: Style.marginXXS * scaling * contentScale
anchors.topMargin: Style.marginXS * scaling * contentScale
NIcon {
id: iconText
anchors.centerIn: parent
text: root.icon
font.pointSize: Style.fontSizeLargeXL * scaling * contentScale
color: Color.mOnSurface
icon: root.icon
font.pointSize: Style.fontSizeS * scaling * contentScale
color: Color.mOnPrimary
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
Rectangle {
@ -58,7 +59,7 @@ Rectangle {
}
NIcon {
text: "palette"
icon: "paint-bucket"
color: Color.mOnSurfaceVariant
}
}

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services
import qs.Widgets
Popup {
@ -129,7 +130,7 @@ Popup {
spacing: Style.marginS * scaling
NIcon {
text: "palette"
icon: "eyedropper"
font.pointSize: Style.fontSizeXXL * scaling
color: Color.mPrimary
}
@ -147,7 +148,7 @@ Popup {
}
NIconButton {
icon: "close"
icon: "x-lg"
onClicked: root.close()
}
}
@ -491,7 +492,7 @@ Popup {
NButton {
id: cancelButton
text: "Cancel"
icon: "close"
icon: "x-lg"
outlined: cancelButton.hovered ? false : true
customHeight: 36 * scaling
customWidth: 100 * scaling
@ -502,7 +503,7 @@ Popup {
NButton {
text: "Apply"
icon: "check"
icon: "check-lg"
customHeight: 36 * scaling
customWidth: 100 * scaling
onClicked: {

View file

@ -85,8 +85,8 @@ RowLayout {
indicator: NIcon {
x: combo.width - width - Style.marginM * scaling
y: combo.topPadding + (combo.availableHeight - height) / 2
text: "arrow_drop_down"
font.pointSize: Style.fontSizeXXL * scaling
icon: "chevron-down"
font.pointSize: Style.fontSizeL * scaling
}
popup: Popup {

View file

@ -1,19 +1,22 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import QtQuick.Layouts
Text {
// Optional layout nudge for optical alignment when used inside Layouts
property real layoutTopMargin: 0
text: "question_mark"
font.family: "Material Symbols Rounded"
font.pointSize: Style.fontSizeL * scaling
font.variableAxes: {
"wght"// slightly bold to ensure all lines looks good
: (Font.Normal + Font.Bold) / 2.5
readonly property string defaultIcon: "balloon"
property string icon: defaultIcon
text: {
if (icon === undefined || Bootstrap.icons[icon] === undefined) {
Logger.warn("Icon", `"${icon}"`, "doesn't exist in the bootstrap font")
Logger.callStack()
return Bootstrap.icons[defaultIcon]
}
return Bootstrap.icons[icon]
}
font.family: "bootstrap-icons"
font.pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
Layout.topMargin: layoutTopMargin
}

View file

@ -35,17 +35,31 @@ Rectangle {
opacity: root.enabled ? Style.opacityFull : Style.opacityMedium
color: root.enabled && root.hovering ? colorBgHover : colorBg
radius: width * 0.5
border.color: root.hovering ? colorBorderHover : colorBorder
border.color: root.enabled && root.hovering ? colorBorderHover : colorBorder
border.width: Math.max(1, Style.borderS * scaling)
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.InOutQuad
}
}
NIcon {
text: root.icon
font.pointSize: Style.fontSizeM * scaling
color: root.hovering ? colorFgHover : colorFg
icon: root.icon
font.pointSize: Math.max(1, root.width * 0.4)
color: root.enabled && root.hovering ? colorFgHover : colorFg
// Center horizontally
x: (root.width - width) / 2
// Center vertically accounting for font metrics
y: (root.height - height) / 2 + (height - contentHeight) / 2
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
NTooltip {
@ -56,13 +70,14 @@ Rectangle {
}
MouseArea {
enabled: root.enabled
// Always enabled to allow hover/tooltip even when the button is disabled
enabled: true
anchors.fill: parent
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
hoverEnabled: true
onEntered: {
hovering = true
hovering = root.enabled ? true : false
if (tooltipText) {
tooltip.show()
}
@ -79,6 +94,9 @@ Rectangle {
if (tooltipText) {
tooltip.hide()
}
if (!root.enabled) {
return
}
if (mouse.button === Qt.LeftButton) {
root.clicked()
} else if (mouse.button === Qt.RightButton) {

View file

@ -9,9 +9,10 @@ Rectangle {
id: root
property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent
property real borderWidth: 0
property string fallbackIcon: ""
property real fallbackIconSize: Style.fontSizeXXL * scaling
color: Color.transparent
radius: parent.width * 0.5
@ -45,18 +46,20 @@ Rectangle {
}
property real imageOpacity: root.opacity
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/circled_image.frag.qsb")
supportsAtlasTextures: false
blending: true
}
// Fallback icon
NIcon {
anchors.centerIn: parent
text: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0
Loader {
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
sourceComponent: NIcon {
anchors.centerIn: parent
icon: fallbackIcon
font.pointSize: fallbackIconSize
z: 0
}
}
}

View file

@ -9,10 +9,11 @@ Rectangle {
id: root
property string imagePath: ""
property string fallbackIcon: ""
property color borderColor: Color.transparent
property real borderWidth: 0
property real imageRadius: width * 0.5
property string fallbackIcon: ""
property real fallbackIconSize: Style.fontSizeXXL * scaling
property real scaledRadius: imageRadius * Settings.data.general.radiusRatio
@ -56,7 +57,7 @@ Rectangle {
property real itemHeight: root.height
property real cornerRadius: root.radius
property real imageOpacity: root.opacity
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/rounded_image.frag.qsb")
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb")
// Qt6 specific properties - ensure proper blending
supportsAtlasTextures: false
@ -71,12 +72,14 @@ Rectangle {
}
// Fallback icon
NIcon {
anchors.centerIn: parent
text: fallbackIcon
font.pointSize: Style.fontSizeXXL * scaling
visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
z: 0
Loader {
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
sourceComponent: NIcon {
anchors.centerIn: parent
icon: fallbackIcon
font.pointSize: fallbackIconSize
z: 0
}
}
}

View file

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
// Input and button row
RowLayout {
@ -13,7 +14,7 @@ RowLayout {
property string placeholderText: ""
property string text: ""
property string actionButtonText: "Test"
property string actionButtonIcon: "play_arrow"
property string actionButtonIcon: "play"
property bool actionButtonEnabled: text !== ""
// Signals

View file

@ -9,21 +9,15 @@ Item {
property string icon: ""
property string text: ""
property string tooltipText: ""
property color pillColor: Color.mSurfaceVariant
property color textColor: Color.mOnSurface
property color iconCircleColor: Color.mPrimary
property color iconTextColor: Color.mSurface
property color collapsedIconColor: Color.mOnSurface
property real iconRotation: 0
property real sizeRatio: 0.8
property bool autoHide: false
property bool forceOpen: false
property bool disableOpen: false
property bool rightOpen: false
property bool hovered: false
// Effective shown state (true if hovered/animated open or forced)
readonly property bool effectiveShown: forceOpen || showPill
readonly property bool revealed: forceOpen || showPill
signal shown
signal hidden
@ -50,14 +44,14 @@ Item {
Rectangle {
id: pill
width: effectiveShown ? maxPillWidth : 1
width: revealed ? maxPillWidth : 1
height: pillHeight
x: rightOpen ? (iconCircle.x + iconCircle.width / 2) : // Opens right
(iconCircle.x + iconCircle.width / 2) - width // Opens left
opacity: effectiveShown ? Style.opacityFull : Style.opacityNone
color: pillColor
opacity: revealed ? Style.opacityFull : Style.opacityNone
color: Color.mSurfaceVariant
topLeftRadius: rightOpen ? 0 : pillHeight * 0.5
bottomLeftRadius: rightOpen ? 0 : pillHeight * 0.5
@ -77,8 +71,8 @@ Item {
text: root.text
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: textColor
visible: effectiveShown
color: Color.mPrimary
visible: revealed
}
Behavior on width {
@ -102,11 +96,8 @@ Item {
width: iconSize
height: iconSize
radius: width * 0.5
// When forced shown, match pill background; otherwise use accent when hovered
color: forceOpen ? pillColor : (showPill ? iconCircleColor : Color.mSurfaceVariant)
color: hovered && !forceOpen ? Color.mPrimary : Color.mSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
border.width: Math.max(1, Style.borderS * scaling)
border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent
x: rightOpen ? 0 : (parent.width - width)
@ -118,11 +109,9 @@ Item {
}
NIcon {
text: root.icon
rotation: root.iconRotation
icon: root.icon
font.pointSize: Style.fontSizeM * scaling
// When forced shown, use pill text color; otherwise accent color when hovered
color: forceOpen ? textColor : (showPill ? iconTextColor : Color.mOnSurface)
color: hovered && !forceOpen ? Color.mOnPrimary : Color.mOnSurface
// Center horizontally
x: (iconCircle.width - width) / 2
// Center vertically accounting for font metrics
@ -220,6 +209,7 @@ Item {
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: {
hovered = true
root.entered()
tooltip.show()
if (disableOpen) {
@ -230,6 +220,7 @@ Item {
}
}
onExited: {
hovered = false
root.exited()
if (!forceOpen) {
hide()

View file

@ -95,7 +95,7 @@ RowLayout {
NIcon {
anchors.centerIn: parent
text: "remove"
icon: "chevron-left"
font.pointSize: Style.fontSizeS * scaling
color: decreaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
}
@ -130,7 +130,7 @@ RowLayout {
NIcon {
anchors.centerIn: parent
text: "add"
icon: "chevron-right"
font.pointSize: Style.fontSizeS * scaling
color: increaseArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
}

View file

@ -112,23 +112,13 @@ Item {
RowLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginS * scaling
anchors.margins: Style.marginL * scaling
spacing: Style.marginL * scaling
// Icon
NIcon {
id: icon
text: {
switch (root.type) {
case "warning":
return "warning"
case "notice":
return "info"
default:
return "info"
}
}
icon: (root.type == "warning") ? "exclamation-triangle" : "check-circle"
color: {
switch (root.type) {
case "warning":
@ -172,7 +162,7 @@ Item {
// Close button (only if persistent or manual dismiss needed)
NIconButton {
icon: "close"
icon: "x-lg"
visible: root.persistent || root.duration === 0
colorBg: Color.mSurfaceVariant

View file

@ -1,16 +1,13 @@
{
description = "Noctalia shell - a Wayland desktop shell built with Quickshell";
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,
@ -24,7 +21,6 @@
system:
nixpkgs.legacyPackages.${system}.alejandra
);
packages = eachSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
@ -32,7 +28,33 @@
withX11 = false;
withI3 = false;
};
# Custom ttf-bootstrap-icons package
ttf-bootstrap-icons = pkgs.stdenvNoCC.mkDerivation rec {
pname = "ttf-bootstrap-icons";
version = "1.13.1";
src = pkgs.fetchzip {
url = "https://github.com/twbs/icons/releases/download/v${version}/bootstrap-icons-${version}.zip";
sha256 = "999021e12fab5c9ede5e4e7072eb176122be798b2f99195acf5dda47aef8fc93";
stripRoot = false;
};
installPhase = ''
runHook preInstall
install -Dm644 fonts/bootstrap-icons.ttf $out/share/fonts/truetype/bootstrap-icons.ttf
runHook postInstall
'';
meta = with pkgs.lib; {
description = "Official open source SVG icon library for Bootstrap";
homepage = "https://icons.getbootstrap.com/";
license = licenses.mit;
platforms = platforms.all;
maintainers = [];
};
};
runtimeDeps = with pkgs; [
bash
bluez
@ -49,12 +71,12 @@
networkmanager
wl-clipboard
];
fontconfig = pkgs.makeFontsConf {
fontDirectories = [
pkgs.material-symbols
pkgs.roboto
pkgs.inter-nerdfont
ttf-bootstrap-icons # Add the custom font package here
];
};
in {
@ -62,21 +84,17 @@
pname = "noctalia-shell";
version = self.rev or self.dirtyRev or "dirty";
src = ./.;
nativeBuildInputs = [pkgs.gcc pkgs.makeWrapper pkgs.qt6.wrapQtAppsHook];
buildInputs = [qs pkgs.xkeyboard-config pkgs.qt6.qtbase];
propagatedBuildInputs = runtimeDeps;
installPhase = ''
mkdir -p $out/share/noctalia-shell
cp -r ./* $out/share/noctalia-shell
makeWrapper ${qs}/bin/qs $out/bin/noctalia-shell \
--prefix PATH : "${pkgs.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";
@ -84,9 +102,11 @@
mainProgram = "noctalia-shell";
};
};
# Expose the custom font as a separate package (optional)
ttf-bootstrap-icons = ttf-bootstrap-icons;
}
);
defaultPackage = eachSystem (system: self.packages.${system}.default);
};
}
}