Files
dotfiles/quickshell/bar/Rail.qml
T
nvrl c36a74e861 feat(quickshell): idle chain, drifting wallpaper, and a few fixes
- Idle is handled in the shell instead of hypridle: the screen dims
  slowly at 4 minutes, locks at 5, powers down at 6, and suspends at 15
  on battery. Idle inhibitors hold every stage off; Super+Shift+I does
  the same by hand and puts a cup on the rail.
- The shell draws the wallpaper (backdrop/), so hyprpaper is gone. The
  picture is drawn a little larger than the screen and drifts with the
  workspace a beat after the windows, and dims a shade while windows are
  open. The path lives in quickshell/wallpaper.
- The lock screen and power menu say "Take your time." instead of the
  NieR slogan.
- hyprpaper is no longer launched twice, the media module keeps the
  player that played last instead of an empty browser tab, and grouped
  windows show thin untitled tabs with their place in the group (2/3)
  in the bar title.
- qt6ct uses Darkly; the package list gains adw-gtk-theme and darkly-bin.
2026-10-04 23:10:19 +02:00

234 lines
6.9 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Wayland
import "root:/config"
import "root:/components"
import "root:/services"
import "root:/popouts"
// The right-hand status rail — one slab, not a stack of cards.
//
// Connectivity, audio, power, tray and notifications all sit inside a single
// continuous column, divided by hairline rules the same way the bar divides its
// modules. Same lean, same chamfer, same keyline as the bar and the dock, so
// the three surfaces read as one system.
//
// On a 1080p panel this is the cheap edge: 48px of width is 2.5% of the screen,
// where the same 48px of height would be 4.4% on top of what the bar takes.
PanelWindow {
id: root
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "takemi-rail"
anchors {
top: true
bottom: true
right: true
}
// Hangs from the underside of the bar, flush with the screen edge. Like
// the bar it draws no surface of its own; see Frame.
margins.top: Theme.barHeight
exclusionMode: ExclusionMode.Ignore
implicitWidth: Theme.railWidth
color: "transparent"
readonly property real edgeInset: Theme.padM
// ------------------------------------------------------------------- top
Column {
id: upper
anchors.top: parent.top
anchors.topMargin: root.edgeInset
anchors.horizontalCenter: parent.horizontalCenter
spacing: 0
RailIcon {
id: netIcon
icon: Net.icon
label: "Network"
statusText: Net.label
suppressTooltip: netPopout.detailActive
dim: !Sys.netUp
onClicked: netPopout.togglePinned()
}
RailIcon {
id: btIcon
icon: Bt.icon
label: "Bluetooth"
statusText: Bt.label
suppressTooltip: btPopout.detailActive
dim: !Bt.enabled
onClicked: btPopout.togglePinned()
}
Divider {}
RailIcon {
id: volIcon
icon: Audio.icon
readout: Audio.muted ? "" : Audio.volumePercent + "%"
reserveReadout: true
label: "Volume"
statusText: Audio.muted ? "Muted" : Audio.volumePercent + "% · " + Audio.sinkName
hint: "Scroll to adjust"
suppressTooltip: audioPopout.detailActive
dim: Audio.muted
onClicked: audioPopout.togglePinned()
onScrolled: delta => Audio.changeVolume(delta > 0 ? 0.05 : -0.05)
}
RailIcon {
id: micIcon
icon: Audio.micIcon
label: "Microphone"
statusText: Audio.micMuted ? "Muted" : Audio.micPercent + "% · " + Audio.sourceName
hint: "Scroll to adjust"
suppressTooltip: audioPopout.detailActive
dim: Audio.micMuted
onClicked: audioPopout.togglePinned()
onScrolled: delta => Audio.setMicVolume(Audio.micVolume + (delta > 0 ? 0.05 : -0.05))
}
RailIcon {
id: brightIcon
visible: Brightness.available
icon: Brightness.icon
readout: Math.round(Brightness.percent) + "%"
label: "Brightness"
statusText: Math.round(Brightness.percent) + "%"
hint: "Scroll to adjust"
onScrolled: delta => Brightness.change(delta > 0 ? 5 : -5)
}
Divider { visible: Battery.available }
RailIcon {
id: battIcon
visible: Battery.available
icon: Battery.icon
readout: Math.round(Battery.percent) + "%"
label: "Battery"
statusText: Math.round(Battery.percent) + "% · " + Battery.timeLabel
suppressTooltip: battPopout.detailActive
alarm: Battery.critical
pulsing: Battery.critical
onClicked: battPopout.togglePinned()
}
}
// ---------------------------------------------------------------- bottom
Column {
id: lower
anchors.bottom: parent.bottom
anchors.bottomMargin: root.edgeInset
anchors.horizontalCenter: parent.horizontalCenter
spacing: 0
Tray {
id: tray
anchors.horizontalCenter: parent.horizontalCenter
}
Divider { visible: tray.visible }
// Only there while the idle chain is held off by hand, so it is
// obvious why the screen never locked.
RailIcon {
id: awakeIcon
visible: Idle.stayAwake
icon: "󰅶"
label: "Stay awake"
statusText: "Screen will not dim or lock"
hint: "Click to allow sleep"
live: true
onClicked: Idle.toggleStayAwake()
}
RailIcon {
id: bellIcon
icon: Notifs.icon
label: "Notifications"
statusText: Notifs.dnd
? "Do not disturb"
: (Notifs.unread > 0 ? Notifs.unread + " unread" : "All caught up")
readout: Notifs.unread > 0
? (Notifs.unread > 9 ? "9+" : String(Notifs.unread))
: ""
reserveReadout: true
dim: Notifs.dnd
live: Notifs.hasUnread && !Notifs.dnd
suppressTooltip: notifPopout.detailActive
onClicked: {
notifPopout.togglePinned();
Notifs.markRead();
}
}
RailIcon {
id: powerIcon
icon: "󰐥"
label: "Power"
statusText: "Session actions"
live: Actions.powerMenuOpen
onClicked: Actions.togglePowerMenu()
}
}
// The rail's counterpart to the bar's divider: a horizontal hairline.
component Divider: Item {
implicitWidth: Theme.railWidth
implicitHeight: Theme.padM + 4
Rectangle {
anchors.centerIn: parent
width: 16
height: 1
color: Theme.alpha(Theme.outline, 0.6)
}
}
// ------------------------------------------------------------- popouts
// All of these hang off the left edge of the rail, opening into the screen.
NetPopout {
id: netPopout
anchorItem: netIcon
triggerHovered: netIcon.hovered
fromEdge: Edges.Left
}
BtPopout {
id: btPopout
anchorItem: btIcon
triggerHovered: btIcon.hovered
fromEdge: Edges.Left
}
AudioPopout {
id: audioPopout
anchorItem: volIcon
triggerHovered: volIcon.hovered || micIcon.hovered
fromEdge: Edges.Left
}
BatteryPopout {
id: battPopout
anchorItem: battIcon
triggerHovered: battIcon.hovered
fromEdge: Edges.Left
visible: Battery.available && shown
}
NotifPopout {
id: notifPopout
anchorItem: bellIcon
triggerHovered: bellIcon.hovered
fromEdge: Edges.Left
}
}