Compare commits

...

10 commits

Author SHA1 Message Date
44b257ce85 feat(nosignal): CachyOS repo toggle + wire Quick Settings placebos
- CachyRepoToggleRow.qml (Settings -> Services): sudoless CachyOS optimized
  repos + linux-cachyos kernel switch, auto-tier (v4>v3); polls
  nosignal-cachy-repo status, runs enable/disable in a floating kitty. Inserted
  after SudoToggleRow in ServicesPage.qml.
- NsQuickSettings.qml: Night Light now drives hyprsunset (tile bound to the real
  pgrep'd process, not a flag); Airplane drives nmcli + bluetoothctl and binds to
  real radio state; VPN placebo tile removed (no backend).

Both built + live-tested on the reference box (06-17 PM). Helper script, sudoers
and hyprsunset dep ship in the nosignal builder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:06:20 +01:00
53c5833de0 fix(nosignal): hardware-test feedback round 2 — weather, dashboard, tray
From the 2026-06-15 on-box hardware test (all 6 prior fixes confirmed; these
are the new findings, baked into the fork source rather than shipped as
pacman-hook patches, per the Phase-2 "we own the shell" approach):

- F-W1 (Weather.qml): fetch at startup. Stock only called reload() on a config
  CHANGE, so a saved location blanked after reboot and an empty/auto location
  never populated on a fresh boot. Add Component.onCompleted: reload() and
  switch the hourly Timer to reload() (re-detects auto/IP too).
- F-W3 (Weather.qml): 7-day forecast showed "undefined°" in °F mode — only
  maxTempC/minTempC were stored. Store rounded maxTempF/minTempF (and round °C).
- F-W2 (Weather.qml): empty/auto-IP location never populated — the in-shell
  ipinfo.io fetch didn't set loc (provider-specific; the city path works via
  the same Requests.get). Repair attempt: defensive JSON parse + error callback
  + fallback to geojs.io. Compiled Requests module not inspectable, so this is
  best-effort; a saved city still works regardless.
- F-D1a (modules/dashboard/*.qml): square the 25 card radii bound to the
  compiled Tokens.rounding.* Material defaults (which ignore shell.json
  rounding.scale=0) so the dashboard matches the square redesign.
- F-D1b (Glyphs.qml): remap the weather glyphs to the Material-weather family
  (every codepoint verified present in JetBrainsMono Nerd Font Mono); the old
  Font-Awesome codepoints were absent (rendered "?") and thunderstorm /
  snowing_heavy were unmapped.
- F-T1 (NsBar.qml): gate the system-tray pill on SystemTray.items count so it
  only shows when an app registers a tray icon (was opening an empty panel).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:04:28 +01:00
f21e5b5eaf fix(nosignal): hardware-test feedback — additions icons, power menu, weather picker
From the 2026-06-15 hardware-test feedback (redesign passed end-to-end):

- Glyphs: map the 12 unmapped additions-page icon names (brush, cloud_sync,
  data_object, deployed_code, movie_edit, neurology, psychology, robot_2,
  smart_toy, sports_esports, videocam, vpn_lock) so the nexus Additional
  Software page stops rendering "?" boxes. Verified Nerd Font codepoints.
- Glyphs: cp() now uses String.fromCodePoint, not fromCharCode — the 5 MDI
  glyphs above 0xFFFF were being truncated by the 16-bit fromCharCode.
- Shortcuts: Super+Escape (session) now drives the redesigned full-screen
  Power Menu via NsShell.toggle("power"); old caelestia session panel is
  now dead code.
- nexus LanguageAndRegion: replace the "coming soon" weather placeholder with
  a real location input bound to GlobalConfig.services.weatherLocation (the
  Weather backend already geocodes / IP-auto-detects).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:02:53 +01:00
83c6b62c13 feat(nosignal): finish Nerd Font icon remap — core surfaces
Convert remaining reachable surfaces to NsIcon (launcher, utilities, windowinfo,
sidebar, dashboard, notification toast, shared controls IconTextButton/Menu/
SplitButton/ToggleButton/CollapsibleSection/CustomSpinBox, CoverArt). Handles
the `sourceComponent: MaterialIcon {` form and icon-via-alias controls.

- IconTextButton: alias -> plain `icon` property + bound NsIcon (cross-file alias
  to a custom property was rejected).
- Reverted IconButton `font:` (the controls-wide font->fontStyle sweep wrongly
  hit ButtonBase, which uses font not fontStyle).
- Glyphs map: +31 names (dashboard/utilities/launcher leftovers) — no "?" glyphs.

Old collapsed bar + old lock sub-components left as-is (unused, never render).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:59:28 +01:00
c4738ae974 fix(nosignal): notification toasts — below the bar + no blob curve
- Position the toast wrapper at y:46 so toasts clear the 38px NsBar (they were
  overlapping it; caelestia's Content negates the wrapper top margin, so the
  offset must be on the wrapper's y, not its margin).
- Fully collapse the notifsBg blob (visible:false + implicitWidth/Height 0) —
  opacity/visible alone don't hide a BlobGroup member, which left a rounded
  corner peeking under the square card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:14:36 +01:00
f6a2584d31 fix(nosignal): square notification toasts + guard lock screen.name
- Notification toasts: square the cards + round urgency/icon badges
  (Tokens.rounding.full ignored rounding.scale) and hide/flatten the notifsBg
  blob (deformAmount 0, radius 0, opacity 0) so toasts are flat square cards.
- LockSurface: guard screen?.name to silence a transient init warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:00:16 +01:00
dcce2440f6 fix(nosignal): square the OSD (volume/brightness) to match the design
The volume/brightness OSD popped out as a rounded caelestia blob in the dynamic
surface colour. Square it + match the design glass:
- osd/Content: draw a flat square Theme.panelBg + 1px border background.
- ContentWindow: osdBg blob deformAmount 0 + radius 0 + opacity 0 (hidden;
  the OSD now owns its background).
- FilledSlider: track + handle radius -> 0 (Tokens.rounding.full ignored
  rounding.scale, so it stayed round).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:54:35 +01:00
16de3f16fe feat(nosignal): NoSignal logo + V1 on About; fully square the UI
- About page: replace caelestia AnimatedLogo with the NoSignal logo
  (assets/nosignal-logo.png), title "NoSignal", version reads "V1".
- Square every remaining curved element to match the no-curves design:
  avatars, toggle/icon circles, power/media buttons, slider tracks+handles,
  NsSwitch track+knob, notification dot, calendar today, lock avatar -> radius 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:42:08 +01:00
7c06b808ae feat(nosignal): redesign lock screen to match handoff
Rewrite the WlSessionLockSurface visuals to the NoSignal lock design while
keeping the secure session-lock + PAM machinery (Pam buffer/handleKey/state,
WlSessionLock unlock):

- Blurred screencopy wallpaper + dark scrim; top status row (lock+Locked /
  wifi/bt/battery); centred 108px ExtraLight clock + date; gradient avatar +
  username; password field (key glyph, masked dots, peach enter glyph, error
  border) fed to pam.handleKey; bottom now-playing strip + "Press Enter to
  unlock". JetBrains Mono / Nerd Font (NsIcon) / Theme tokens.
- Escape does not dismiss; correct password (PAM) unlocks; wrong password shows
  inline error. Verified in VM: renders, masks, unlocks.
- Glyphs: add login (enter arrow) + key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:30:20 +01:00
83d5de1ba3 feat(nosignal): Nerd Font icons in IconButton (controls-wide)
Convert the shared IconButton control to render via NsIcon, so every icon-button
across the shell (nexus, lock, utilities, launcher chrome, ...) uses Nerd Font.
Alias icon -> label.icon; internal MaterialIcon -> NsIcon (sizes from root.font
via fontStyle); dropped the Material-Symbols vertical-center fudge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:19:16 +01:00
65 changed files with 766 additions and 407 deletions

BIN
assets/nosignal-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -12,10 +12,10 @@ ColumnLayout {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
animate: true animate: true
text: root.icon icon: root.icon
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).weight(Font.Bold).build()
} }

View file

@ -13,7 +13,7 @@ Rectangle {
implicitWidth: 38 implicitWidth: 38
implicitHeight: 22 implicitHeight: 22
radius: 11 radius: 0
color: on ? Theme.accent : Theme.fillSubtle color: on ? Theme.accent : Theme.fillSubtle
Behavior on color { Behavior on color {
@ -27,7 +27,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitWidth: 16 implicitWidth: 16
implicitHeight: 16 implicitHeight: 16
radius: 8 radius: 0
color: root.on ? Theme.onAccent : Theme.text color: root.on ? Theme.onAccent : Theme.text
Behavior on x { Behavior on x {

View file

@ -45,8 +45,8 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
} }
MaterialIcon { NsIcon {
text: "expand_more" icon: "expand_more"
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium

View file

@ -110,11 +110,11 @@ RowLayout {
} }
} }
MaterialIcon { NsIcon {
id: upIcon id: upIcon
anchors.centerIn: parent anchors.centerIn: parent
text: "keyboard_arrow_up" icon: "keyboard_arrow_up"
color: Colours.palette.m3onPrimary color: Colours.palette.m3onPrimary
} }
} }
@ -145,11 +145,11 @@ RowLayout {
onReleased: timer.stop() onReleased: timer.stop()
} }
MaterialIcon { NsIcon {
id: downIcon id: downIcon
anchors.centerIn: parent anchors.centerIn: parent
text: "keyboard_arrow_down" icon: "keyboard_arrow_down"
color: Colours.palette.m3onPrimary color: Colours.palette.m3onPrimary
} }
} }

View file

@ -16,7 +16,7 @@ Slider {
background: StyledRect { background: StyledRect {
color: Colours.layer(Colours.palette.m3surfaceContainer, 2) color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.full radius: 0
StyledRect { StyledRect {
anchors.left: parent.left anchors.left: parent.left
@ -51,7 +51,7 @@ Slider {
anchors.fill: parent anchors.fill: parent
color: Colours.palette.m3inverseSurface color: Colours.palette.m3inverseSurface
radius: Tokens.rounding.full radius: 0
MouseArea { MouseArea {
id: handleInteraction id: handleInteraction

View file

@ -6,7 +6,7 @@ import qs.services
ButtonBase { ButtonBase {
id: root id: root
property alias icon: label.text property alias icon: label.icon
readonly property alias label: label readonly property alias label: label
font: Tokens.font.icon.medium font: Tokens.font.icon.medium
@ -34,11 +34,10 @@ ButtonBase {
return h; return h;
} }
MaterialIcon { NsIcon {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1 // AHHHHHHH material symbols whyyyy
color: root.onColour color: root.onColour
fontStyle: root.font fontStyle: root.font
fill: !root.isToggle || root.internalChecked ? 1 : 0 fill: !root.isToggle || root.internalChecked ? 1 : 0

View file

@ -7,7 +7,7 @@ import qs.services
ButtonBase { ButtonBase {
id: root id: root
property alias icon: iconLabel.text property string icon
property alias text: label.text property alias text: label.text
readonly property alias iconLabel: iconLabel readonly property alias iconLabel: iconLabel
@ -44,9 +44,10 @@ ButtonBase {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
id: iconLabel id: iconLabel
icon: root.icon
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575) Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
color: root.onColour color: root.onColour

View file

@ -157,9 +157,9 @@ MouseArea {
anchors.margins: Tokens.padding.medium anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: item.modelData?.icon ?? "" icon: item.modelData?.icon ?? ""
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
} }
@ -176,8 +176,8 @@ MouseArea {
active: item.modelData?.trailingIcon.length > 0 active: item.modelData?.trailingIcon.length > 0
visible: active visible: active
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: item.modelData.trailingIcon icon: item.modelData.trailingIcon
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
} }
} }

View file

@ -64,12 +64,12 @@ Row {
anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4) anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
id: iconLabel id: iconLabel
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
text: root.active?.activeIcon ?? root.fallbackIcon icon: root.active?.activeIcon ?? root.fallbackIcon
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColour : root.textColour
fill: 1 fill: 1
} }
@ -116,13 +116,13 @@ Row {
onClicked: root.expanded = !root.expanded onClicked: root.expanded = !root.expanded
} }
MaterialIcon { NsIcon {
id: expandIcon id: expandIcon
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4) anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
text: "expand_more" icon: "expand_more"
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColour : root.textColour
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0

View file

@ -54,12 +54,12 @@ StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
id: toggleBtnIcon id: toggleBtnIcon
visible: !!text visible: !!text
fill: root.toggled ? 1 : 0 fill: root.toggled ? 1 : 0
text: root.icon icon: root.icon
color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`] color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
fontStyle: Tokens.font.icon.size(root.iconSize).build() fontStyle: Tokens.font.icon.size(root.iconSize).build()

View file

@ -57,11 +57,11 @@ Item {
} }
} }
MaterialIcon { NsIcon {
anchors.centerIn: parent anchors.centerIn: parent
grade: 200 grade: 200
text: image.status === Image.Error ? "broken_image" : "art_track" icon: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build() fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0 opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0

View file

@ -54,8 +54,9 @@ Scope {
onPressed: { onPressed: {
if (root.hasFullscreen) if (root.hasFullscreen)
return; return;
const visibilities = Visibilities.getForActive(); // NoSignal: drive the redesigned full-screen Power Menu (NsOverlay),
visibilities.session = !visibilities.session; // not the old caelestia session panel (now dead code).
NsShell.toggle("power", 0);
} }
} }

View file

@ -75,7 +75,7 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.margins: Tokens.padding.large anchors.margins: Tokens.padding.large
radius: Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: "transparent" color: "transparent"
Flickable { Flickable {

View file

@ -21,7 +21,7 @@ GridLayout {
Layout.preferredWidth: Tokens.sizes.dashboard.userWidth Layout.preferredWidth: Tokens.sizes.dashboard.userWidth
Layout.fillHeight: true Layout.fillHeight: true
radius: Tokens.rounding.extraLarge radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
User { User {
id: user id: user
@ -37,7 +37,7 @@ GridLayout {
Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth Layout.preferredWidth: Tokens.sizes.dashboard.weatherWidth
Layout.preferredHeight: weather.implicitHeight Layout.preferredHeight: weather.implicitHeight
radius: Tokens.rounding.extraLarge * 1.5 radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
SmallWeather { SmallWeather {
id: weather id: weather
@ -49,7 +49,7 @@ GridLayout {
Layout.preferredWidth: dateTime.implicitWidth Layout.preferredWidth: dateTime.implicitWidth
Layout.fillHeight: true Layout.fillHeight: true
radius: Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
DateTime { DateTime {
id: dateTime id: dateTime
@ -63,7 +63,7 @@ GridLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: calendar.implicitHeight Layout.preferredHeight: calendar.implicitHeight
radius: Tokens.rounding.extraLarge radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
Calendar { Calendar {
id: calendar id: calendar
@ -78,7 +78,7 @@ GridLayout {
Layout.preferredWidth: resources.implicitWidth Layout.preferredWidth: resources.implicitWidth
Layout.fillHeight: true Layout.fillHeight: true
radius: Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
Resources { Resources {
id: resources id: resources
@ -92,7 +92,7 @@ GridLayout {
Layout.preferredWidth: media.implicitWidth Layout.preferredWidth: media.implicitWidth
Layout.fillHeight: true Layout.fillHeight: true
radius: Tokens.rounding.extraLarge * 2 radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
Media { Media {
id: media id: media

View file

@ -102,11 +102,11 @@ Item {
CAnim {} CAnim {}
} }
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
text: "queue_music" icon: "queue_music"
fontStyle: Tokens.font.icon.builders.large.scale(2).build() fontStyle: Tokens.font.icon.builders.large.scale(2).build()
color: Colours.palette.m3onPrimaryContainer color: Colours.palette.m3onPrimaryContainer
} }

View file

@ -23,9 +23,9 @@ Item {
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "tune" icon: "tune"
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
} }

View file

@ -86,7 +86,7 @@ Item {
implicitHeight: parent.implicitHeight * 2 implicitHeight: parent.implicitHeight * 2
color: Colours.palette.m3primary color: Colours.palette.m3primary
radius: Tokens.rounding.full radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
} }
Behavior on x { Behavior on x {
@ -148,18 +148,18 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitHeight: parent.height + Tokens.sizes.dashboard.tabIndicatorSpacing * 2 implicitHeight: parent.height + Tokens.sizes.dashboard.tabIndicatorSpacing * 2
radius: Tokens.rounding.medium radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
onClicked: root.dashState.currentTab = tab.TabBar.index onClicked: root.dashState.currentTab = tab.TabBar.index
} }
MaterialIcon { NsIcon {
id: icon id: icon
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: label.top anchors.bottom: label.top
text: tab.iconName icon: tab.iconName
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
fill: tab.current ? 1 : 0 fill: tab.current ? 1 : 0
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium

View file

@ -67,7 +67,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: bigInfoRow.implicitHeight + Tokens.padding.small implicitHeight: bigInfoRow.implicitHeight + Tokens.padding.small
radius: Tokens.rounding.extraLarge * 2 radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
RowLayout { RowLayout {
@ -76,9 +76,9 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.largeIncreased spacing: Tokens.spacing.largeIncreased
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: Weather.icon icon: Weather.icon
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build()
color: Colours.palette.m3secondary color: Colours.palette.m3secondary
animate: true animate: true
@ -155,7 +155,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2 implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
ColumnLayout { ColumnLayout {
@ -180,9 +180,9 @@ Item {
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
} }
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: forecastItem.modelData.icon icon: forecastItem.modelData.icon
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
color: Colours.palette.m3secondary color: Colours.palette.m3secondary
} }
@ -209,15 +209,15 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 60 Layout.preferredHeight: 60
radius: Tokens.rounding.medium radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
text: detailRoot.icon icon: detailRoot.icon
color: detailRoot.colour color: detailRoot.colour
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -252,8 +252,8 @@ Item {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: weatherStat.icon icon: weatherStat.icon
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
color: weatherStat.colour color: weatherStat.colour
} }

View file

@ -62,7 +62,7 @@ CustomMouseArea {
StateLayer { StateLayer {
color: Colours.palette.m3primary color: Colours.palette.m3primary
radius: pressed ? Tokens.rounding.small : Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
disabled: { disabled: {
const now = new Date(); const now = new Date();
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear(); return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();

View file

@ -65,10 +65,10 @@ Item {
Anim {} Anim {}
} }
MaterialIcon { NsIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: res.icon icon: res.icon
font: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
color: res.fgColour color: res.fgColour
} }
} }

View file

@ -13,14 +13,14 @@ Item {
Component.onCompleted: Weather.reload() Component.onCompleted: Weather.reload()
MaterialIcon { NsIcon {
id: icon id: icon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
animate: true animate: true
text: Weather.icon icon: Weather.icon
color: Colours.palette.m3secondary color: Colours.palette.m3secondary
fontStyle: Tokens.font.icon.builders.extraLarge.scale(1.6).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(1.6).build()
} }

View file

@ -75,8 +75,8 @@ Item {
asynchronous: true asynchronous: true
active: pfp.status !== Image.Ready active: pfp.status !== Image.Ready
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: "person_add" icon: "person_add"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
fill: 1 fill: 1
@ -120,9 +120,9 @@ Item {
} }
} }
MaterialIcon { NsIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: "person_edit" icon: "person_edit"
color: Colours.palette.m3onPrimary color: Colours.palette.m3onPrimary
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }
@ -187,9 +187,9 @@ Item {
CAnim {} CAnim {}
} }
MaterialIcon { NsIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: "clock_arrow_up" icon: "clock_arrow_up"
color: Colours.palette.m3onTertiaryContainer color: Colours.palette.m3onTertiaryContainer
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -216,7 +216,7 @@ Item {
implicitWidth: 10 implicitWidth: 10
implicitHeight: 10 implicitHeight: 10
radius: Tokens.rounding.full radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.palette.m3secondaryContainer color: Colours.palette.m3secondaryContainer
} }
@ -229,7 +229,7 @@ Item {
implicitWidth: 15 implicitWidth: 15
implicitHeight: 15 implicitHeight: 15
radius: Tokens.rounding.full radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.palette.m3secondaryContainer color: Colours.palette.m3secondaryContainer
} }
@ -240,7 +240,7 @@ Item {
anchors.leftMargin: -Tokens.padding.medium anchors.leftMargin: -Tokens.padding.medium
y: Tokens.padding.extraSmall y: Tokens.padding.extraSmall
radius: Tokens.rounding.largeIncreased radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
color: Colours.palette.m3secondaryContainer color: Colours.palette.m3secondaryContainer
implicitWidth: wmLabel.implicitWidth + Tokens.padding.medium * 2 implicitWidth: wmLabel.implicitWidth + Tokens.padding.medium * 2
implicitHeight: wmLabel.implicitHeight + Tokens.padding.small * 2 implicitHeight: wmLabel.implicitHeight + Tokens.padding.small * 2
@ -251,11 +251,11 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Tokens.spacing.extraSmall spacing: Tokens.spacing.extraSmall
MaterialIcon { NsIcon {
id: wmIcon id: wmIcon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "select_window" icon: "select_window"
color: Colours.palette.m3onSecondaryContainer color: Colours.palette.m3onSecondaryContainer
fontStyle: wmText.font fontStyle: wmText.font
} }

View file

@ -176,7 +176,7 @@ Item {
implicitWidth: shape.implicitSize + Tokens.padding.medium * 2 implicitWidth: shape.implicitSize + Tokens.padding.medium * 2
implicitHeight: shape.implicitSize + Tokens.padding.medium * 2 implicitHeight: shape.implicitSize + Tokens.padding.medium * 2
color: Colours.palette.m3primaryContainer color: Colours.palette.m3primaryContainer
radius: Tokens.rounding.full radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
LoadingIndicator { LoadingIndicator {
id: shape id: shape
@ -212,9 +212,9 @@ Item {
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "sentiment_sad" icon: "sentiment_sad"
fontStyle: Tokens.font.icon.builders.large.scale(2).build() fontStyle: Tokens.font.icon.builders.large.scale(2).build()
color: Colours.palette.m3outline color: Colours.palette.m3outline
} }

View file

@ -20,9 +20,9 @@ Item {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
z: 1 z: 1
MaterialIcon { NsIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.12) Layout.topMargin: Math.round(fontInfo.pointSize * 0.12)
text: "lyrics" icon: "lyrics"
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }

View file

@ -33,7 +33,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0 anchors.margins: !btn.pressed && btn.containsMouse ? -Tokens.padding.extraSmall : 0
group: blobGroup group: blobGroup
radius: Tokens.rounding.medium radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
Behavior on anchors.margins { Behavior on anchors.margins {
Anim {} Anim {}
@ -50,7 +50,7 @@ Item {
implicitHeight: parent.height implicitHeight: parent.height
group: blobGroup group: blobGroup
radius: Tokens.rounding.medium radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
deformScale: 0.00001 deformScale: 0.00001
states: State { states: State {
@ -197,11 +197,11 @@ Item {
hoverEnabled: true hoverEnabled: true
onClicked: root.open = !root.open onClicked: root.open = !root.open
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
text: "more_vert" icon: "more_vert"
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
} }

View file

@ -12,7 +12,7 @@ StyledClippingRect {
property real animPerc: UPower.displayDevice.percentage property real animPerc: UPower.displayDevice.percentage
color: Colours.palette.m3secondaryContainer color: Colours.palette.m3secondaryContainer
radius: Tokens.rounding.large radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
implicitWidth: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && Gpu.type !== Gpu.None) || Config.dashboard.performance.showStorage || Config.dashboard.performance.showMemory ? Tokens.sizes.dashboard.perfBattWidth : Tokens.sizes.dashboard.perfBattWidthSingle implicitWidth: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && Gpu.type !== Gpu.None) || Config.dashboard.performance.showStorage || Config.dashboard.performance.showMemory ? Tokens.sizes.dashboard.perfBattWidth : Tokens.sizes.dashboard.perfBattWidthSingle
implicitHeight: Tokens.sizes.dashboard.perfBattHeight implicitHeight: Tokens.sizes.dashboard.perfBattHeight
@ -39,7 +39,7 @@ StyledClippingRect {
implicitHeight: parent.height * root.animPerc implicitHeight: parent.height * root.animPerc
color: Colours.palette.m3secondary color: Colours.palette.m3secondary
radius: Tokens.rounding.extraSmall radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
clip: true clip: true
Contents { Contents {
@ -65,9 +65,9 @@ StyledClippingRect {
spacing: 0 spacing: 0
MaterialIcon { NsIcon {
Layout.leftMargin: -Tokens.padding.extraSmall Layout.leftMargin: -Tokens.padding.extraSmall
text: "battery_full" icon: "battery_full"
color: contents.accentColour color: contents.accentColour
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }
@ -115,8 +115,8 @@ StyledClippingRect {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
spacing: Tokens.spacing.extraSmall spacing: Tokens.spacing.extraSmall
MaterialIcon { NsIcon {
text: "bolt" icon: "bolt"
color: contents.accentColour color: contents.accentColour
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
fill: 1 fill: 1

View file

@ -17,7 +17,7 @@ StyledRect {
required property real temperature required property real temperature
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.extraLarge radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
implicitWidth: Tokens.sizes.dashboard.perfHeroCardWidth implicitWidth: Tokens.sizes.dashboard.perfHeroCardWidth
implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Tokens.spacing.large, usageShape.implicitHeight + usageLabel.implicitHeight) + Tokens.padding.large * 2 implicitHeight: Math.max(tempProg.implicitHeight + detailsRow.implicitHeight + Tokens.spacing.large, usageShape.implicitHeight + usageLabel.implicitHeight) + Tokens.padding.large * 2
@ -40,11 +40,11 @@ StyledRect {
Anim {} Anim {}
} }
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
text: root.icon icon: root.icon
color: root.accent color: root.accent
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -84,9 +84,9 @@ StyledRect {
Layout.leftMargin: -Tokens.padding.extraSmall Layout.leftMargin: -Tokens.padding.extraSmall
spacing: Tokens.spacing.extraSmall spacing: Tokens.spacing.extraSmall
MaterialIcon { NsIcon {
Layout.topMargin: Math.round(fontInfo.pointSize * 0.08) Layout.topMargin: Math.round(fontInfo.pointSize * 0.08)
text: root.temperature > 90 ? "thermometer_alert" : "thermometer" icon: root.temperature > 90 ? "thermometer_alert" : "thermometer"
color: root.temperature > 90 ? Colours.palette.m3error : root.accent color: root.temperature > 90 ? Colours.palette.m3error : root.accent
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
fill: 1 fill: 1

View file

@ -12,7 +12,7 @@ StyledRect {
readonly property color accent: Colours.palette.m3tertiary readonly property color accent: Colours.palette.m3tertiary
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.medium radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
implicitWidth: layout.implicitWidth + Tokens.padding.extraLargeIncreased * 2 implicitWidth: layout.implicitWidth + Tokens.padding.extraLargeIncreased * 2
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2 implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
@ -31,8 +31,8 @@ StyledRect {
Layout.leftMargin: -Tokens.padding.extraSmall Layout.leftMargin: -Tokens.padding.extraSmall
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: "memory_alt" icon: "memory_alt"
fill: 1 fill: 1
color: root.accent color: root.accent
fontStyle: Tokens.font.icon.builders.medium.weight(Font.DemiBold).build() // DemiBold to fix fill issues fontStyle: Tokens.font.icon.builders.medium.weight(Font.DemiBold).build() // DemiBold to fix fill issues

View file

@ -10,7 +10,7 @@ StyledRect {
id: root id: root
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.extraLarge radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth implicitWidth: Tokens.sizes.dashboard.perfNetworkCardWidth
implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight implicitHeight: Tokens.sizes.dashboard.perfNetworkCardHeight
@ -30,8 +30,8 @@ StyledRect {
RowLayout { RowLayout {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: "swap_vert" icon: "swap_vert"
color: Colours.palette.m3primary color: Colours.palette.m3primary
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -105,8 +105,8 @@ StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: "download" icon: "download"
color: Colours.palette.m3tertiary color: Colours.palette.m3tertiary
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -136,8 +136,8 @@ StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: "upload" icon: "upload"
color: Colours.palette.m3secondary color: Colours.palette.m3secondary
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -167,8 +167,8 @@ StyledRect {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
text: "history" icon: "history"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }

View file

@ -14,7 +14,7 @@ StyledRect {
readonly property real percentage: Storage.primaryDisk?.perc ?? 0 readonly property real percentage: Storage.primaryDisk?.perc ?? 0
color: Colours.tPalette.m3surfaceContainer color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.extraExtraLarge radius: 0 // nosignal: square dashboard cards to match the redesign (F-D1)
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2 implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2 implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
@ -55,9 +55,9 @@ StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 0 spacing: 0
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "hard_drive" icon: "hard_drive"
color: root.accent color: root.accent
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }

View file

@ -198,7 +198,9 @@ StyledWindow {
id: osdBg id: osdBg
panel: panels.osdWrapper panel: panels.osdWrapper
deformAmount: 0.25 deformAmount: 0 // NoSignal: square OSD (no organic blob deformation)
radius: 0
opacity: 0 // NoSignal: OSD draws its own flat square glass bg (osd/Content)
x: panels.osdWrapper.x + panels.osd.x + root.borderThickness x: panels.osdWrapper.x + panels.osd.x + root.borderThickness
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
} }
@ -207,6 +209,12 @@ StyledWindow {
id: notifsBg id: notifsBg
panel: panels.notifications panel: panels.notifications
deformAmount: 0 // NoSignal: square notification toasts
radius: 0
opacity: 0
visible: false
implicitWidth: 0 // collapse the blob entirely (toasts draw their own square cards)
implicitHeight: 0
} }
PanelBg { PanelBg {

View file

@ -70,7 +70,7 @@ Item {
sessionPanel: sessionWrapper sessionPanel: sessionWrapper
utilitiesPanel: utilities utilitiesPanel: utilities
anchors.top: parent.top y: 46 // NoSignal: drop the toasts below the 38px NsBar (+ gap)
anchors.right: parent.right anchors.right: parent.right
} }

View file

@ -58,14 +58,14 @@ Item {
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight) implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
MaterialIcon { NsIcon {
id: searchIcon id: searchIcon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: root.padding anchors.leftMargin: root.padding
text: "search" icon: "search"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
} }
@ -145,7 +145,7 @@ Item {
} }
} }
MaterialIcon { NsIcon {
id: clearIcon id: clearIcon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -163,7 +163,7 @@ Item {
return 1; return 1;
} }
text: "close" icon: "close"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
MouseArea { MouseArea {

View file

@ -116,8 +116,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
MaterialIcon { NsIcon {
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search" icon: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge

View file

@ -25,11 +25,11 @@ Item {
anchors.rightMargin: Tokens.padding.medium anchors.rightMargin: Tokens.padding.medium
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
MaterialIcon { NsIcon {
id: icon id: icon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: root.modelData?.icon ?? "" icon: root.modelData?.icon ?? ""
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.large.scale(1.3).build() fontStyle: Tokens.font.icon.builders.large.scale(1.3).build()
} }

View file

@ -79,8 +79,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
active: root.modelData && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, root.modelData.id) active: root.modelData && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, root.modelData.id)
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: "favorite" icon: "favorite"
fill: 1 fill: 1
color: Colours.palette.m3primary color: Colours.palette.m3primary
} }

View file

@ -40,8 +40,8 @@ Item {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
text: "function" icon: "function"
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -105,14 +105,14 @@ Item {
} }
} }
MaterialIcon { NsIcon {
id: icon id: icon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Tokens.padding.medium anchors.rightMargin: Tokens.padding.medium
text: "open_in_new" icon: "open_in_new"
color: Colours.palette.m3onTertiary color: Colours.palette.m3onTertiary
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -92,8 +92,8 @@ Item {
active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme active: `${root.modelData?.name} ${root.modelData?.flavour}` === Schemes.currentScheme
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: "check" icon: "check"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -26,10 +26,10 @@ Item {
anchors.rightMargin: Tokens.padding.medium anchors.rightMargin: Tokens.padding.medium
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
MaterialIcon { NsIcon {
id: icon id: icon
text: root.modelData?.icon ?? "" icon: root.modelData?.icon ?? ""
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -68,8 +68,8 @@ Item {
active: root.modelData?.variant === Schemes.currentVariant active: root.modelData?.variant === Schemes.currentVariant
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: "check" icon: "check"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -57,9 +57,9 @@ Item {
implicitWidth: Tokens.sizes.launcher.wallpaperWidth implicitWidth: Tokens.sizes.launcher.wallpaperWidth
implicitHeight: implicitWidth / 16 * 9 implicitHeight: implicitWidth / 16 * 9
MaterialIcon { NsIcon {
anchors.centerIn: parent anchors.centerIn: parent
text: "image" icon: "image"
color: Colours.tPalette.m3outline color: Colours.tPalette.m3outline
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).weight(Font.DemiBold).build()
} }

View file

@ -1,11 +1,22 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
// NoSignal lock screen surface (interface redesign 2026-06-14).
// Secure ext-session-lock content: blurred wallpaper + scrim, top status row,
// centred clock/date/avatar/username/password, bottom now-playing strip.
// Reuses caelestia's Pam (buffer + handleKey + state) for PAM auth and the
// WlSessionLock unlock signal input is fed to pam.handleKey, never compared
// in QML. JetBrains Mono / Nerd Font / NoSignal Theme tokens.
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Effects import QtQuick.Effects
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Bluetooth
import Quickshell.Services.UPower
import Caelestia.Config import Caelestia.Config
import qs.components import qs.components
import qs.services import qs.services
import qs.utils
WlSessionLockSurface { WlSessionLockSurface {
id: root id: root
@ -13,153 +24,26 @@ WlSessionLockSurface {
required property WlSessionLock lock required property WlSessionLock lock
required property Pam pam required property Pam pam
readonly property alias unlocking: unlockAnim.running readonly property bool errored: root.pam.state === "error" || root.pam.state === "fail"
contentItem.Config.screen: screen.name contentItem.Config.screen: screen?.name ?? ""
contentItem.Tokens.screen: screen.name contentItem.Tokens.screen: screen?.name ?? ""
color: "transparent" color: "transparent"
Connections { Connections {
function onUnlock(): void { function onUnlock(): void {
unlockAnim.start(); root.lock.locked = false;
} }
target: root.lock target: root.lock
} }
SequentialAnimation { // Blurred wallpaper + scrim
id: unlockAnim
ParallelAnimation {
Anim {
target: lockContent
properties: "implicitWidth,implicitHeight"
to: lockContent.size
}
Anim {
target: lockBg
property: "radius"
to: lockContent.radius
}
Anim {
target: content
property: "scale"
to: 0
}
Anim {
target: content
property: "opacity"
to: 0
type: Anim.StandardSmall
}
Anim {
target: lockIcon
property: "opacity"
to: 1
type: Anim.StandardLarge
}
Anim {
target: background
property: "opacity"
to: 0
type: Anim.StandardLarge
}
SequentialAnimation {
PauseAnimation {
duration: Tokens.anim.durations.small
}
Anim {
type: Anim.Standard
target: lockContent
property: "opacity"
to: 0
}
}
}
PropertyAction {
target: root.lock
property: "locked"
value: false
}
}
ParallelAnimation {
id: initAnim
running: true
Anim {
target: background
property: "opacity"
to: 1
type: Anim.StandardLarge
}
SequentialAnimation {
ParallelAnimation {
Anim {
target: lockContent
property: "scale"
to: 1
type: Anim.FastSpatial
}
Anim {
target: lockContent
property: "rotation"
to: 360
duration: Tokens.anim.durations.expressiveFastSpatial
easing: Tokens.anim.standardAccel
}
}
ParallelAnimation {
Anim {
target: lockIcon
property: "rotation"
to: 360
easing: Tokens.anim.standardDecel
}
Anim {
type: Anim.DefaultEffects
target: lockIcon
property: "opacity"
to: 0
}
Anim {
type: Anim.DefaultEffects
target: content
property: "opacity"
to: 1
}
Anim {
target: content
property: "scale"
to: 1
}
Anim {
target: lockBg
property: "radius"
to: lockContent.Tokens.rounding.extraLarge * 1.5
}
Anim {
target: lockContent
property: "implicitWidth"
to: (root.screen?.height ?? 0) * lockContent.Tokens.sizes.lock.heightMult * lockContent.Tokens.sizes.lock.ratio
}
Anim {
target: lockContent
property: "implicitHeight"
to: (root.screen?.height ?? 0) * lockContent.Tokens.sizes.lock.heightMult
}
}
}
}
ScreencopyView { ScreencopyView {
id: background id: background
anchors.fill: parent anchors.fill: parent
captureSource: root.screen captureSource: root.screen
opacity: 0
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {
@ -168,57 +52,265 @@ WlSessionLockSurface {
blur: 1 blur: 1
blurMax: 64 blurMax: 64
blurMultiplier: 1 blurMultiplier: 1
brightness: -0.38
saturation: 0.1
} }
} }
Rectangle {
anchors.fill: parent
color: Qt.rgba(8 / 255, 10 / 255, 14 / 255, 0.5)
}
// Keyboard capture (feed PAM; Escape does nothing)
Item { Item {
id: lockContent anchors.fill: parent
focus: true
Component.onCompleted: forceActiveFocus()
Keys.onPressed: event => root.pam.handleKey(event)
}
readonly property int size: lockIcon.implicitHeight + Tokens.padding.large * 4 // Top status row
readonly property int radius: size / 4 * Tokens.rounding.scale RowLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 22
anchors.leftMargin: 28
anchors.rightMargin: 28
anchors.centerIn: parent RowLayout {
implicitWidth: size spacing: 7
implicitHeight: size NsIcon {
icon: "lock"
rotation: 180 color: Theme.textMuted
scale: 0 size: 15
}
StyledRect { StyledText {
id: lockBg text: "Locked"
color: Theme.textMuted
anchors.fill: parent font.family: Theme.font.family
color: Colours.palette.m3surface font.pixelSize: Theme.font.bar
radius: parent.radius
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 15
shadowColor: Qt.alpha(Colours.palette.m3shadow, 0.7)
} }
} }
MaterialIcon { Item {
id: lockIcon Layout.fillWidth: true
anchors.centerIn: parent
text: "lock"
fontStyle: Tokens.font.icon.builders.extraLarge.scale(4).weight(Font.Bold).build()
rotation: 180
} }
Content { RowLayout {
id: content spacing: 14
anchors.centerIn: parent NsIcon {
width: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult * Tokens.sizes.lock.ratio - Tokens.padding.extraLargeIncreased icon: Nmcli.active ? Icons.getNetworkIcon(Nmcli.active.strength ?? 0) : "wifi_off"
height: (root.screen?.height ?? 0) * Tokens.sizes.lock.heightMult - Tokens.padding.extraLargeIncreased color: Theme.textMuted
size: 15
}
NsIcon {
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth_connected" : "bluetooth_disabled"
color: Theme.textMuted
size: 15
}
RowLayout {
spacing: 6
visible: UPower.displayDevice.isLaptopBattery
NsIcon {
icon: Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged].includes(UPower.displayDevice.state))
color: Theme.textMuted
size: 15
}
StyledText {
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
color: Theme.textMuted
font.family: Theme.font.family
font.pixelSize: Theme.font.bar
}
}
}
}
lock: root // Centre block
opacity: 0 ColumnLayout {
scale: 0 anchors.centerIn: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Time.format("hh:mm")
color: Theme.text
font.family: Theme.font.family
font.pixelSize: 108
font.weight: Font.ExtraLight
font.letterSpacing: 3
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: Time.format("ddd, d MMMM yyyy")
color: Theme.textMuted
font.family: Theme.font.family
font.pixelSize: 15
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 64
implicitWidth: 74
implicitHeight: 74
radius: 0
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
position: 0
color: Theme.accent
}
GradientStop {
position: 1
color: Theme.blue
}
}
NsIcon {
anchors.centerIn: parent
icon: "person"
color: Theme.onAccent
size: 34
}
}
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 14
text: SysInfo.user || "user"
color: Theme.text
font.family: Theme.font.family
font.pixelSize: 14
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
implicitWidth: 312
implicitHeight: 44
radius: Theme.radius.panel
color: Qt.rgba(17 / 255, 20 / 255, 26 / 255, 0.55)
border.width: 1
border.color: root.errored ? Qt.rgba(224 / 255, 116 / 255, 106 / 255, 0.6) : Qt.rgba(1, 1, 1, 0.1)
RowLayout {
anchors.fill: parent
anchors.leftMargin: 14
anchors.rightMargin: 12
spacing: 10
NsIcon {
icon: "key"
color: Theme.textMuted
size: 15
}
StyledText {
Layout.fillWidth: true
text: root.pam.buffer.length > 0 ? "•".repeat(root.pam.buffer.length) : "Enter password"
color: root.pam.buffer.length > 0 ? Theme.text : Theme.textFaint
elide: Text.ElideRight
font.family: Theme.font.family
font.pixelSize: Theme.font.body
font.letterSpacing: root.pam.buffer.length > 0 ? 2 : 0
}
NsIcon {
icon: "login"
color: root.pam.buffer.length > 0 ? Theme.accent : Theme.textFaint
size: 16
MouseArea {
anchors.fill: parent
anchors.margins: -6
cursorShape: Qt.PointingHandCursor
onClicked: if (root.pam.buffer.length > 0)
root.pam.passwd.start()
}
}
}
}
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8
Layout.preferredHeight: 14
text: root.errored ? "Incorrect — try again" : (root.pam.lockMessage || "")
color: Theme.danger
font.family: Theme.font.family
font.pixelSize: 12
}
}
// Bottom strip
RowLayout {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: 26
anchors.leftMargin: 28
anchors.rightMargin: 28
RowLayout {
spacing: 10
visible: Players.active !== null
Rectangle {
implicitWidth: 42
implicitHeight: 42
radius: Theme.radius.panel
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop {
position: 0
color: Theme.accent
}
GradientStop {
position: 1
color: Theme.blue
}
}
NsIcon {
anchors.centerIn: parent
icon: "music_note"
color: Theme.onAccent
size: 18
}
}
ColumnLayout {
spacing: 0
StyledText {
text: Players.active?.trackTitle || ""
color: Theme.text
elide: Text.ElideRight
Layout.maximumWidth: 280
font.family: Theme.font.family
font.pixelSize: Theme.font.bodySmall
}
StyledText {
text: Players.active?.trackArtist || ""
color: Theme.textMuted
elide: Text.ElideRight
Layout.maximumWidth: 280
font.family: Theme.font.family
font.pixelSize: Theme.font.meta
}
}
}
Item {
Layout.fillWidth: true
}
StyledText {
text: "Press Enter to unlock"
color: Theme.textFaint
font.family: Theme.font.family
font.pixelSize: 11
} }
} }
} }

View file

@ -0,0 +1,73 @@
// CachyRepoToggleRow.qml (NoSignal) Settings -> Services toggle to enable /
// disable the CachyOS pacman repositories. Auto-detects the best x86-64 microarch
// tier the CPU supports (v4 > v3). Live state polls `nosignal-cachy-repo status`
// (no root). enable/disable run in a floating terminal so the pacman/download
// output stays visible; the grant is passwordless via sudo -n
// (/etc/sudoers.d/02-nosignal-cachy). enable adds repos, converts userspace to
// the optimized builds (pacman -Suu, pacman pinned stock), and installs the
// linux-cachyos kernel; disable reverts to stock and removes the cachy kernel
// (keeping stock `linux` bootable). A reboot switches the running kernel.
//
// Untracked file under modules/nexus/common auto-discovered as the type
// `CachyRepoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
// upgrades; pairs with the one-line insert in ServicesPage.qml.
import QtQuick
import Quickshell.Io
import qs.modules.nexus.common
ToggleRow {
id: root
property bool repoOn: false // NB: not `enabled` (reserved Item property)
text: qsTr("CachyOS repositories")
subtext: repoOn
? qsTr("On — CachyOS optimized builds + linux-cachyos kernel (reboot to run it)")
: qsTr("Switch to CachyOS optimized builds + linux-cachyos kernel (reboot after)")
onToggled: {
if (checked)
enableProc.running = true;
else
disableProc.running = true;
reconcile.restart();
}
// --- live state -----------------------------------------------------------
Process {
id: statusProc
command: ["/usr/local/bin/nosignal-cachy-repo", "status"]
stdout: StdioCollector {
onStreamFinished: {
root.repoOn = text.trim() === "enabled";
root.checked = root.repoOn; // drive switch from real state
}
}
}
// --- actions (passwordless; shown in a terminal for pacman output) ---------
Process {
id: enableProc
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo enable; printf '\\nPress Enter to close...'; read _"]
onExited: reconcile.restart()
}
Process {
id: disableProc
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo disable; printf '\\nPress Enter to close...'; read _"]
onExited: reconcile.restart()
}
// --- polling --------------------------------------------------------------
Timer {
interval: 5000
repeat: true
running: true
triggeredOnStart: true
onTriggered: statusProc.running = true
}
Timer {
id: reconcile
interval: 2000
onTriggered: statusProc.running = true
}
}

View file

@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import Quickshell.Io import Quickshell.Io
import Caelestia import Caelestia
import Caelestia.Config import Caelestia.Config
@ -61,22 +62,27 @@ PageBase {
width: parent.width - Tokens.padding.largeIncreased * 2 width: parent.width - Tokens.padding.largeIncreased * 2
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
AnimatedLogo { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: implicitWidth Layout.preferredWidth: 104
Layout.preferredHeight: implicitHeight Layout.preferredHeight: 104
source: Quickshell.shellDir + "/assets/nosignal-logo.png"
sourceSize.width: 208
sourceSize.height: 208
fillMode: Image.PreserveAspectFit
smooth: true
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.small Layout.topMargin: Tokens.spacing.small
text: "Caelestia" text: "NoSignal"
font: Tokens.font.headline.builders.large.width(110).build() font: Tokens.font.headline.builders.large.width(110).build()
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: CUtils.version ? `v${CUtils.version}` : "…" text: "V1"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
font: Tokens.font.body.medium font: Tokens.font.body.medium
} }

View file

@ -92,41 +92,71 @@ PageBase {
text: qsTr("Weather") text: qsTr("Weather")
} }
// Placeholder until the map-based location picker lands // Location for the weather service. Weather.qml accepts a city name
// (geocoded) or a "lat,lon" string, and auto-detects via IP when empty.
ConnectedRect { ConnectedRect {
Layout.fillWidth: true Layout.fillWidth: true
first: true first: true
last: true last: true
implicitHeight: comingSoon.implicitHeight + Tokens.padding.extraLarge * 2 implicitHeight: weatherLayout.implicitHeight + weatherLayout.anchors.margins * 2
ColumnLayout { ColumnLayout {
id: comingSoon id: weatherLayout
anchors.centerIn: parent anchors.fill: parent
width: parent.width - Tokens.padding.largeIncreased * 2 anchors.margins: Tokens.padding.medium
spacing: Tokens.padding.extraSmall anchors.leftMargin: Tokens.padding.largeIncreased
anchors.rightMargin: Tokens.padding.largeIncreased
NsIcon { spacing: Tokens.spacing.small
Layout.alignment: Qt.AlignHCenter
icon: "map"
color: Colours.palette.m3outlineVariant
fontStyle: Tokens.font.icon.extraLarge
}
StyledText { StyledText {
Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true
text: qsTr("Location picker coming soon") text: qsTr("Location")
color: Colours.palette.m3outlineVariant font: Tokens.font.body.small
font: Tokens.font.title.small elide: Text.ElideRight
}
StyledRect {
Layout.fillWidth: true
implicitHeight: locField.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.small
color: Colours.tPalette.m3surfaceContainerLowest
border.color: Colours.palette.m3outlineVariant
Behavior on border.color {
CAnim {}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: locField.focus = true
}
StyledTextField {
id: locField
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large
text: GlobalConfig.services.weatherLocation
placeholderText: qsTr("City name or lat,long — empty = auto (IP)")
placeholderTextColor: Colours.palette.m3onSurfaceVariant
color: Colours.palette.m3onSurface
onEditingFinished: GlobalConfig.services.weatherLocation = text
}
} }
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Choose your weather location on a map in a future update") text: qsTr("Leave empty to detect your location automatically.")
color: Colours.palette.m3outlineVariant color: Colours.palette.m3outline
font: Tokens.font.body.small font: Tokens.font.label.small
} }
} }
} }

View file

@ -216,6 +216,11 @@ PageBase {
Layout.fillWidth: true Layout.fillWidth: true
} }
// NoSignal: CachyOS repos enable/disable (sudoless, auto-tier)
CachyRepoToggleRow {
Layout.fillWidth: true
}
SelectRow { SelectRow {
Layout.fillWidth: true Layout.fillWidth: true
last: true last: true

View file

@ -63,7 +63,7 @@ Item {
anchors.rightMargin: root.clampedPadding anchors.rightMargin: root.clampedPadding
color: "transparent" color: "transparent"
radius: Tokens.rounding.large radius: 0
StyledListView { StyledListView {
id: list id: list

View file

@ -23,7 +23,7 @@ StyledRect {
property bool expanded: Config.notifs.openExpanded property bool expanded: Config.notifs.openExpanded
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.large radius: 0
implicitHeight: inner.implicitHeight implicitHeight: inner.implicitHeight
@ -116,7 +116,7 @@ StyledRect {
visible: root.hasImage || root.hasAppIcon visible: root.hasImage || root.hasAppIcon
sourceComponent: StyledClippingRect { sourceComponent: StyledClippingRect {
radius: Tokens.rounding.full radius: 0
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
implicitWidth: TokenConfig.sizes.notifs.image implicitWidth: TokenConfig.sizes.notifs.image
implicitHeight: TokenConfig.sizes.notifs.image implicitHeight: TokenConfig.sizes.notifs.image
@ -147,7 +147,7 @@ StyledRect {
anchors.bottom: root.hasImage ? image.bottom : undefined anchors.bottom: root.hasImage ? image.bottom : undefined
sourceComponent: StyledRect { sourceComponent: StyledRect {
radius: Tokens.rounding.full radius: 0
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) : Colours.palette.m3secondaryContainer
implicitWidth: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image implicitWidth: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
implicitHeight: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image implicitHeight: root.hasImage ? Tokens.sizes.notifs.badge : TokenConfig.sizes.notifs.image
@ -177,8 +177,8 @@ StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1 anchors.verticalCenterOffset: 1
sourceComponent: MaterialIcon { sourceComponent: NsIcon {
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency) icon: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -358,17 +358,17 @@ StyledRect {
implicitHeight: expandIcon.implicitHeight implicitHeight: expandIcon.implicitHeight
StateLayer { StateLayer {
radius: Tokens.rounding.full radius: 0
color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface color: root.modelData.urgency === NotificationUrgency.Critical ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
onClicked: root.expanded = !root.expanded onClicked: root.expanded = !root.expanded
} }
MaterialIcon { NsIcon {
id: expandIcon id: expandIcon
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: root.expanded ? -1 : 1 anchors.verticalCenterOffset: root.expanded ? -1 : 1
text: "expand_more" icon: "expand_more"
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0

View file

@ -16,6 +16,7 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Bluetooth import Quickshell.Bluetooth
import Quickshell.Services.UPower import Quickshell.Services.UPower
import Quickshell.Services.SystemTray
import qs.services import qs.services
import qs.utils import qs.utils
import qs.components import qs.components
@ -139,10 +140,12 @@ Variants {
} }
} }
// System tray // System tray only shown when an app actually registers a tray
// icon, otherwise the pill just opened an empty panel (F-T1).
IconPill { IconPill {
icon: "widgets" icon: "widgets"
panel: "tray" panel: "tray"
visible: SystemTray.items.values.length > 0
} }
// Notifications bell (with unread dot overlaid on the icon) // Notifications bell (with unread dot overlaid on the icon)
@ -164,7 +167,7 @@ Variants {
anchors.topMargin: 1 anchors.topMargin: 1
implicitWidth: 6 implicitWidth: 6
implicitHeight: 6 implicitHeight: 6
radius: 3 radius: 0
color: Theme.accent color: Theme.accent
} }
} }

View file

@ -122,13 +122,13 @@ NsPanel {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 6 implicitHeight: 6
radius: 3 radius: 0
color: Theme.fillSubtle color: Theme.fillSubtle
Rectangle { Rectangle {
width: parent.width * Math.max(0, Math.min(1, s.value)) width: parent.width * Math.max(0, Math.min(1, s.value))
height: parent.height height: parent.height
radius: 3 radius: 0
color: Theme.accent color: Theme.accent
} }
@ -137,7 +137,7 @@ NsPanel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitWidth: 14 implicitWidth: 14
implicitHeight: 14 implicitHeight: 14
radius: 7 radius: 0
color: Theme.accent color: Theme.accent
} }

View file

@ -109,7 +109,7 @@ NsPanel {
visible: parent.modelData > 0 visible: parent.modelData > 0
implicitWidth: 26 implicitWidth: 26
implicitHeight: 26 implicitHeight: 26
radius: 13 radius: 0
color: parent.today ? Theme.accent : "transparent" color: parent.today ? Theme.accent : "transparent"
StyledText { StyledText {

View file

@ -40,7 +40,7 @@ NsPanel {
Rectangle { Rectangle {
implicitWidth: 26 implicitWidth: 26
implicitHeight: 26 implicitHeight: 26
radius: 13 radius: 0
color: Notifs.dnd ? Theme.accentSoft : dndMa.containsMouse ? Theme.hover : "transparent" color: Notifs.dnd ? Theme.accentSoft : dndMa.containsMouse ? Theme.hover : "transparent"
NsIcon { NsIcon {

View file

@ -3,10 +3,13 @@ pragma ComponentBehavior: Bound
// NoSignal Quick Settings popout (off the media / battery pill). Avatar + user + // NoSignal Quick Settings popout (off the media / battery pill). Avatar + user +
// uptime + power button; 2-col toggle grid; brightness + volume sliders; media // uptime + power button; 2-col toggle grid; brightness + volume sliders; media
// card. Wired to the real services (Network, Bluetooth, Notifs, Audio, // card. Wired to the real services (Network, Bluetooth, Notifs, Audio,
// Brightness, Players); Night Light / Airplane / VPN are stateful toggles for now. // Brightness, Players). Night Light drives hyprsunset; Airplane drives the
// NetworkManager + Bluetooth radios. (VPN tile removed pending a real backend.)
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Bluetooth import Quickshell.Bluetooth
import qs.services import qs.services
import qs.utils import qs.utils
@ -20,6 +23,35 @@ NsPanel {
readonly property var brightMon: Brightness.monitors[0] ?? null readonly property var brightMon: Brightness.monitors[0] ?? null
// Night Light state tracks the real hyprsunset process rather than a local
// flag, so the tile stays correct across shell reloads or changes made
// elsewhere. Seeded on load (probe runs immediately), reconciled on a timer,
// and re-checked right after a toggle.
property bool nightLightOn: false
Process {
id: nightProbe
command: ["pgrep", "-x", "hyprsunset"]
running: true
stdout: StdioCollector {
onStreamFinished: root.nightLightOn = text.trim().length > 0
}
}
Timer {
interval: 3000
running: true
repeat: true
onTriggered: nightProbe.running = true
}
Timer {
id: nightRefresh
interval: 500
repeat: false
onTriggered: nightProbe.running = true
}
// Header // Header
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -28,7 +60,7 @@ NsPanel {
Rectangle { Rectangle {
implicitWidth: 40 implicitWidth: 40
implicitHeight: 40 implicitHeight: 40
radius: 20 radius: 0
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Vertical orientation: Gradient.Vertical
GradientStop { GradientStop {
@ -75,7 +107,7 @@ NsPanel {
Rectangle { Rectangle {
implicitWidth: 34 implicitWidth: 34
implicitHeight: 34 implicitHeight: 34
radius: 17 radius: 0
color: pwrMa.containsMouse ? Theme.accentSoft : Theme.fillSubtle color: pwrMa.containsMouse ? Theme.accentSoft : Theme.fillSubtle
NsIcon { NsIcon {
@ -128,21 +160,31 @@ NsPanel {
icon: "nightlight" icon: "nightlight"
label: "Night Light" label: "Night Light"
sub: on ? "Warm 3500K" : "Off" sub: on ? "Warm 3500K" : "Off"
onToggled: on = !on on: root.nightLightOn
onToggled: {
if (root.nightLightOn)
Quickshell.execDetached(["pkill", "-x", "hyprsunset"]);
else
Quickshell.execDetached(["sh", "-c", "pkill -x hyprsunset; hyprsunset -t 3500"]);
nightRefresh.restart();
}
} }
QSToggle { QSToggle {
id: airTog id: airTog
icon: "airplanemode_active" icon: "airplanemode_active"
label: "Airplane" label: "Airplane"
sub: on ? "On" : "Off" // Reflects the real radio state: airplane is "On" when both Wi-Fi and
onToggled: on = !on // Bluetooth are down. Network.wifiEnabled tracks nmcli; the adapter
} // tracks BlueZ, so the tile self-corrects if radios change elsewhere.
QSToggle { readonly property bool radiosOff: !Network.wifiEnabled && !(Bluetooth.defaultAdapter?.enabled ?? false)
id: vpnTog sub: radiosOff ? "On" : "Off"
icon: "vpn_key" on: radiosOff
label: "VPN" onToggled: {
sub: on ? "On" : "Off" if (radiosOff)
onToggled: on = !on Quickshell.execDetached(["sh", "-c", "nmcli radio all on; bluetoothctl power on"]);
else
Quickshell.execDetached(["sh", "-c", "nmcli radio all off; bluetoothctl power off"]);
}
} }
} }
@ -264,7 +306,7 @@ NsPanel {
Rectangle { Rectangle {
implicitWidth: 30 implicitWidth: 30
implicitHeight: 30 implicitHeight: 30
radius: 15 radius: 0
color: tile.on ? Theme.accent : Qt.rgba(1, 1, 1, 0.06) color: tile.on ? Theme.accent : Qt.rgba(1, 1, 1, 0.06)
NsIcon { NsIcon {
@ -319,13 +361,13 @@ NsPanel {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 6 implicitHeight: 6
radius: 3 radius: 0
color: Theme.fillSubtle color: Theme.fillSubtle
Rectangle { Rectangle {
width: parent.width * Math.max(0, Math.min(1, s.value)) width: parent.width * Math.max(0, Math.min(1, s.value))
height: parent.height height: parent.height
radius: 3 radius: 0
color: Theme.accent color: Theme.accent
} }
@ -334,7 +376,7 @@ NsPanel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitWidth: 14 implicitWidth: 14
implicitHeight: 14 implicitHeight: 14
radius: 7 radius: 0
color: Theme.accent color: Theme.accent
} }
@ -362,7 +404,7 @@ NsPanel {
implicitWidth: 28 implicitWidth: 28
implicitHeight: 28 implicitHeight: 28
radius: 14 radius: 0
color: mbm.containsMouse ? Theme.hover : "transparent" color: mbm.containsMouse ? Theme.hover : "transparent"
NsIcon { NsIcon {

View file

@ -24,6 +24,15 @@ Item {
implicitWidth: layout.implicitWidth + Tokens.padding.large + layout.anchors.horizontalCenterOffset * 2 implicitWidth: layout.implicitWidth + Tokens.padding.large + layout.anchors.horizontalCenterOffset * 2
implicitHeight: layout.implicitHeight + Tokens.padding.large * 2 implicitHeight: layout.implicitHeight + Tokens.padding.large * 2
// NoSignal: flat square glass background matching the design popouts
Rectangle {
anchors.fill: parent
color: Theme.panelBg
radius: 0
border.width: 1
border.color: Theme.panelBorder
}
ColumnLayout { ColumnLayout {
id: layout id: layout

View file

@ -160,9 +160,9 @@ Item {
Component { Component {
id: iconBtn id: iconBtn
MaterialIcon { NsIcon {
animate: action.modelData.isCopy ?? false animate: action.modelData.isCopy ?? false
text: action.modelData.isCopy ? "content_copy" : "close" icon: action.modelData.isCopy ? "content_copy" : "close"
color: Colours.palette.m3onSurfaceVariant color: Colours.palette.m3onSurfaceVariant
} }
} }

View file

@ -110,8 +110,8 @@ StyledRect {
Component { Component {
id: materialIconComp id: materialIconComp
MaterialIcon { NsIcon {
text: Icons.getNotifIcon(root.activeNotifs[0]?.summary, root.urgency) icon: Icons.getNotifIcon(root.activeNotifs[0]?.summary, root.urgency)
color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.medium fontStyle: Tokens.font.icon.medium
} }
@ -214,9 +214,9 @@ StyledRect {
font: Tokens.font.body.small font: Tokens.font.body.small
} }
MaterialIcon { NsIcon {
Layout.rightMargin: -Tokens.padding.extraSmall / 2 Layout.rightMargin: -Tokens.padding.extraSmall / 2
text: "expand_more" icon: "expand_more"
color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurfaceVariant color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurfaceVariant
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
Layout.topMargin: root.expanded ? -Math.floor(Tokens.padding.extraSmall) : 0 Layout.topMargin: root.expanded ? -Math.floor(Tokens.padding.extraSmall) : 0

View file

@ -33,11 +33,11 @@ StyledRect {
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: IdleInhibitor.enabled ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer color: IdleInhibitor.enabled ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
text: "coffee" icon: "coffee"
color: IdleInhibitor.enabled ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer color: IdleInhibitor.enabled ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -43,12 +43,12 @@ StyledRect {
radius: Tokens.rounding.full radius: Tokens.rounding.full
color: Recorder.running ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer color: Recorder.running ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
anchors.verticalCenterOffset: 1 anchors.verticalCenterOffset: 1
text: "screen_record" icon: "screen_record"
color: Recorder.running ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer color: Recorder.running ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -29,9 +29,9 @@ ColumnLayout {
RowLayout { RowLayout {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: "list" icon: "list"
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }
@ -165,9 +165,9 @@ ColumnLayout {
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "scan_delete" icon: "scan_delete"
color: Colours.palette.m3outline color: Colours.palette.m3outline
fontStyle: Tokens.font.icon.extraLarge fontStyle: Tokens.font.icon.extraLarge
@ -193,9 +193,9 @@ ColumnLayout {
RowLayout { RowLayout {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "scan_delete" icon: "scan_delete"
color: Colours.palette.m3outline color: Colours.palette.m3outline
opacity: !root.props.recordingListExpanded ? 1 : 0 opacity: !root.props.recordingListExpanded ? 1 : 0

View file

@ -70,11 +70,11 @@ StyledRect {
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.large implicitHeight: icon.implicitHeight + Tokens.padding.large
MaterialIcon { NsIcon {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
text: root.modelData.icon icon: root.modelData.icon
color: { color: {
if (root.modelData.type === Toast.Success) if (root.modelData.type === Toast.Success)
return Colours.palette.m3onSuccess; return Colours.palette.m3onSuccess;

View file

@ -41,13 +41,13 @@ ColumnLayout {
onClicked: root.moveToWsExpanded = !root.moveToWsExpanded onClicked: root.moveToWsExpanded = !root.moveToWsExpanded
} }
MaterialIcon { NsIcon {
id: moveToWsIcon id: moveToWsIcon
anchors.centerIn: parent anchors.centerIn: parent
animate: true animate: true
text: root.moveToWsExpanded ? "expand_more" : "keyboard_arrow_right" icon: root.moveToWsExpanded ? "expand_more" : "keyboard_arrow_right"
color: Colours.palette.m3onPrimary color: Colours.palette.m3onPrimary
fontStyle: Tokens.font.icon.large fontStyle: Tokens.font.icon.large
} }

View file

@ -135,11 +135,11 @@ ColumnLayout {
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
MaterialIcon { NsIcon {
id: icon id: icon
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: detail.icon icon: detail.icon
} }
StyledText { StyledText {

View file

@ -40,9 +40,9 @@ Item {
sourceComponent: ColumnLayout { sourceComponent: ColumnLayout {
spacing: 0 spacing: 0
MaterialIcon { NsIcon {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "web_asset_off" icon: "web_asset_off"
color: Colours.palette.m3outline color: Colours.palette.m3outline
fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build() fontStyle: Tokens.font.icon.builders.extraLarge.scale(3).build()
} }

View file

@ -2,8 +2,8 @@ pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
// NoSignal icon remap: Material Symbol name -> Nerd Font (JetBrainsMono NF) // NoSignal icon remap: Material Symbol name -> Nerd Font (JetBrainsMono NF)
// codepoint (Font Awesome range), via String.fromCharCode so the source stays // codepoint (Font Awesome + MDI ranges), via String.fromCodePoint so the source
// pure ASCII. Used by components/NsIcon. Covers bar/panel icons + the dynamic // stays pure ASCII. Used by components/NsIcon. Covers bar/panel icons + the dynamic
// Icons.getX() returns. get() falls back to a question glyph so anything // Icons.getX() returns. get() falls back to a question glyph so anything
// unmapped is visible (easy to spot + add a mapping). // unmapped is visible (easy to spot + add a mapping).
@ -14,7 +14,9 @@ Singleton {
id: root id: root
function cp(code: int): string { function cp(code: int): string {
return String.fromCharCode(code); // fromCodePoint (not fromCharCode) so astral-plane glyphs (Nerd Font
// MDI range, > 0xFFFF) emit correct surrogate pairs, not truncated.
return String.fromCodePoint(code);
} }
readonly property var map: { readonly property var map: {
@ -143,16 +145,21 @@ Singleton {
"warning": root.cp(0xf071), "warning": root.cp(0xf071),
"web_asset_off": root.cp(0xf2d0), "web_asset_off": root.cp(0xf2d0),
"wifi_find": root.cp(0xf1eb), "wifi_find": root.cp(0xf1eb),
// weather (Icons.getWeatherIcon) // weather (Icons.getWeatherIcon) Material-weather family, every
"clear_day": root.cp(0xf185), // codepoint verified present in JetBrainsMono Nerd Font Mono. The old
"clear_night": root.cp(0xf186), // Font-Awesome mapping had several glyphs absent from the font (rendered
"partly_cloudy_day": root.cp(0xf6c4), // as "?" boxes) and was missing thunderstorm/snowing_heavy (F-D1).
"cloudy": root.cp(0xf0c2), "clear_day": root.cp(0xf0599),
"cloud": root.cp(0xf0c2), "clear_night": root.cp(0xf0594),
"rainy": root.cp(0xf73d), "partly_cloudy_day": root.cp(0xf0595),
"cloudy_snowing": root.cp(0xf2dc), "cloudy": root.cp(0xf0590),
"foggy": root.cp(0xf75f), "cloud": root.cp(0xf0590),
"air": root.cp(0xf72e), "rainy": root.cp(0xf0597),
"cloudy_snowing": root.cp(0xf0598),
"snowing_heavy": root.cp(0xf0f37),
"thunderstorm": root.cp(0xf0593),
"foggy": root.cp(0xf0591),
"air": root.cp(0xf059d),
// apps / devices (Icons.get*) // apps / devices (Icons.get*)
"code": root.cp(0xf121), "code": root.cp(0xf121),
"forum": root.cp(0xf086), "forum": root.cp(0xf086),
@ -197,7 +204,55 @@ Singleton {
"update": root.cp(0xf021), "update": root.cp(0xf021),
"wallpaper": root.cp(0xf03e), "wallpaper": root.cp(0xf03e),
"workspaces": root.cp(0xf009), "workspaces": root.cp(0xf009),
"dock": root.cp(0xf2d1) "dock": root.cp(0xf2d1),
// lock screen
"login": root.cp(0xf090),
"key": root.cp(0xf084),
// dashboard / utilities / launcher leftovers
"account_tree": root.cp(0xf0e8),
"android": root.cp(0xf17b),
"category": root.cp(0xf02c),
"clear_all": root.cp(0xf2ed),
"compare": root.cp(0xf362),
"compare_arrows": root.cp(0xf362),
"contrast": root.cp(0xf042),
"delete_forever": root.cp(0xf1f8),
"filter_b_and_w": root.cp(0xf0b0),
"fullscreen": root.cp(0xf065),
"gamepad": root.cp(0xf11b),
"gradient": root.cp(0xf043),
"hard_disk": root.cp(0xf0a0),
"keep": root.cp(0xf08d),
"location_on": root.cp(0xf041),
"location_searching": root.cp(0xf05b),
"looks": root.cp(0xf53f),
"memory": root.cp(0xf2db),
"notifications_off": root.cp(0xf1f6),
"nutrition": root.cp(0xf787),
"page_header": root.cp(0xf036),
"picture_in_picture_center": root.cp(0xf2d0),
"resize": root.cp(0xf065),
"screenshot_region": root.cp(0xf247),
"select_to_speak": root.cp(0xf028),
"sentiment_calm": root.cp(0xf118),
"sentiment_very_dissatisfied": root.cp(0xf119),
"stop": root.cp(0xf04d),
"thermostat": root.cp(0xf2c9),
"water_drop": root.cp(0xf043),
"wb_twilight": root.cp(0xf185),
// additions-page icons (nexus Additional Software)
"brush": root.cp(0xf1fc), // fa-paintbrush
"cloud_sync": root.cp(0xf063f), // md-cloud_sync
"data_object": root.cp(0xf0169), // md-code_braces
"deployed_code": root.cp(0xf1b2), // fa-cube
"movie_edit": root.cp(0xf008), // fa-film
"neurology": root.cp(0xee9c), // fa-brain
"psychology": root.cp(0xf133c), // md-head_cog
"robot_2": root.cp(0xee0d), // fa-robot
"smart_toy": root.cp(0xf06a9), // md-robot
"sports_esports": root.cp(0xf11b), // fa-gamepad
"videocam": root.cp(0xf03d), // fa-video_camera
"vpn_lock": root.cp(0xf099d) // md-shield_lock
} }
function get(name: string): string { function get(name: string): string {

View file

@ -41,15 +41,43 @@ Singleton {
fetchCoordsFromCity(configLocation); fetchCoordsFromCity(configLocation);
} }
} else if (!loc || timer.elapsed() > 900) { } else if (!loc || timer.elapsed() > 900) {
Requests.get("https://ipinfo.io/json", text => { fetchLocationFromIp();
}
}
// Auto-detect location from the public IP (used when weatherLocation is
// empty). Stock caelestia only queried ipinfo.io with no error handling;
// on the 2026-06-15 hardware test that call never populated `loc` (the
// city path works via the same Requests.get, so it is provider-specific
// finding F-W2). Repair: wrap parsing defensively and fall back to a second
// provider (geojs.io, which returns separate latitude/longitude fields) on
// any error or empty response.
function fetchLocationFromIp(): void {
const useGeoJs = () => {
Requests.get("https://get.geojs.io/v1/ip/geo.json", text => {
try {
const g = JSON.parse(text);
if (g.latitude && g.longitude) {
loc = g.latitude + "," + g.longitude;
city = g.city ?? "";
timer.restart();
}
} catch (e) {}
});
};
Requests.get("https://ipinfo.io/json", text => {
try {
const response = JSON.parse(text); const response = JSON.parse(text);
if (response.loc) { if (response.loc) {
loc = response.loc; loc = response.loc;
city = response.city ?? ""; city = response.city ?? "";
timer.restart(); timer.restart();
return;
} }
}); } catch (e) {}
} useGeoJs();
}, useGeoJs);
} }
function fetchCityFromCoords(coords: string): void { function fetchCityFromCoords(coords: string): void {
@ -131,8 +159,10 @@ Singleton {
for (let i = 0; i < json.daily.time.length; i++) for (let i = 0; i < json.daily.time.length; i++)
forecastList.push({ forecastList.push({
date: json.daily.time[i].replace(/-/g, "/"), date: json.daily.time[i].replace(/-/g, "/"),
maxTempC: json.daily.temperature_2m_max[i], maxTempC: Math.round(json.daily.temperature_2m_max[i]),
minTempC: json.daily.temperature_2m_min[i], minTempC: Math.round(json.daily.temperature_2m_min[i]),
maxTempF: Math.round(toFahrenheit(json.daily.temperature_2m_max[i])),
minTempF: Math.round(toFahrenheit(json.daily.temperature_2m_min[i])),
weatherCode: json.daily.weather_code[i], weatherCode: json.daily.weather_code[i],
icon: Icons.getWeatherIcon(json.daily.weather_code[i]) icon: Icons.getWeatherIcon(json.daily.weather_code[i])
}); });
@ -210,6 +240,11 @@ Singleton {
onLocChanged: fetchWeatherData() onLocChanged: fetchWeatherData()
// Stock caelestia only called reload() on a config CHANGE, never at startup,
// so a saved location blanked after a reboot and an empty (auto/IP) location
// never populated on a fresh boot (finding F-W1). Fetch once on load.
Component.onCompleted: reload()
Connections { Connections {
function onWeatherLocationChanged(): void { function onWeatherLocationChanged(): void {
root.reload(); root.reload();
@ -222,7 +257,7 @@ Singleton {
interval: 3600000 // 1 hour interval: 3600000 // 1 hour
running: true running: true
repeat: true repeat: true
onTriggered: fetchWeatherData() onTriggered: reload() // re-detect an auto/IP location too, not just refetch a known one
} }
ElapsedTimer { ElapsedTimer {