Live wall: tint all text one hue, green unless set

e49521d · gmorell · 2026-11-06 04:20

10 files +121 -2
Message
{commit_body(@commit)}

Files changed

modified README.md
+3 −1
@@ -74,7 +74,7 @@ Every job running right now, a card each, tiled to fill the space, the same as G
74 74
75 75 There are two ways to show it:
76 76
77 −- **A window**: the grid button in the popup's header, or right-click the widget → **Open Live CI Wall**. **F11** (or the button) toggles full screen and **Esc** leaves it. **T** (or the opacity button) makes the background see-through and the cards partly so, keeping the text solid. KWin's own *More Actions → Opacity* fades the text too. Put it on a spare monitor.
77 +- **A window**: the grid button in the popup's header, or right-click the widget → **Open Live CI Wall**. **F11** (or the button) toggles full screen and **Esc** leaves it. **T** (or the opacity button) makes the background see-through and the cards partly so, keeping the text solid. KWin's own *More Actions → Opacity* fades the text too. **H** (or the tint button) draws all the text, the clock, the card borders and the icons in one hue, green by default, which suits the see-through look. Failed steps and errors keep their colours. Pick the hue under *Behavior → Live CI wall*. Put it on a spare monitor.
78 78 - **Wall mode on the desktop**: *Behavior → Live CI wall → Wall mode*. A widget on the desktop (not in a panel) then *is* the wall. Resize it as large as you like. In a panel the widget stays the circles.
79 79
80 80 It needs a GitGud server with the `/api/mobile/v1/ci/live` endpoint and a token with `ci:read`.
@@ -123,6 +123,8 @@ Ring the **bell** on a run that is still going (on hover in the popup, in the ru
123 123 | Hide repositories that have never run CI | on | |
124 124 | Notify when any run I triggered fails | off | See [Notifications](#notifications) |
125 125 | Wall mode: on the desktop, show the wall | off | See [Live CI wall](#live-ci-wall) |
126 +| Tint all text one hue | off | Also **H** in the wall window |
127 +| Tint hue | 120° (green) | |
126 128
127 129 Each poll makes one `/repos` call, which carries every repo's latest CI status. It then calls `/repos/:owner/:name/runs` only for repos that need it, so the fast poll stays cheap even when you watch a large organization.
128 130
modified contents/config/main.xml
+13 −0
@@ -47,6 +47,19 @@
47 47 <default>false</default>
48 48 </entry>
49 49
50 + <entry name="wallTint" type="Bool">
51 + <label>Live wall: tint all text one hue</label>
52 + <default>false</default>
53 + </entry>
54 +
55 + <!-- degrees on the colour wheel; 120 is green -->
56 + <entry name="wallTintHue" type="Int">
57 + <label>Live wall: the hue text is tinted</label>
58 + <default>120</default>
59 + <min>0</min>
60 + <max>359</max>
61 + </entry>
62 +
50 63 <entry name="desktopWall" type="Bool">
51 64 <label>On the desktop, the widget is the live CI wall</label>
52 65 <default>false</default>
modified contents/ui/WallCard.qml
+4 −1
@@ -14,6 +14,9 @@ Rectangle {
14 14 required property var card
15 15 property double now: Date.now()
16 16 property bool translucent: false
17 + // The step bar's colour for a step in progress. A status colour, not
18 + // a text one, so the wall's tint leaves it alone.
19 + property color runningColor: Kirigami.Theme.neutralTextColor
17 20
18 21 readonly property var job: card.job || {}
19 22 readonly property var run: card.run || {}
@@ -123,7 +126,7 @@ Rectangle {
123 126 Layout.fillHeight: true
124 127 radius: 2
125 128 color: status === "running" || status === "in_progress"
126 − ? Kirigami.Theme.neutralTextColor
129 + ? cardItem.runningColor
127 130 : Status.isFinished(status) ? Status.color(status, Kirigami.Theme)
128 131 : Qt.alpha(Kirigami.Theme.disabledTextColor, 0.3)
129 132 }
modified contents/ui/WallView.qml
+40 −0
@@ -40,10 +40,33 @@ Item {
40 40 // A small desktop widget: the header keeps only what it must.
41 41 readonly property bool narrow: width < Kirigami.Units.gridUnit * 34
42 42
43 + // Tint: every bit of text — and the clock, borders and shimmer that
44 + // share its colours — in one hue (store.wallTintHue, green unless
45 + // set), a terminal look that suits the translucent window. Done by
46 + // overriding the theme's text colours on the layout below, which
47 + // every Label, icon and card under it inherits. Status colours —
48 + // a failed step, "offline" — keep their meaning and aren't touched.
49 + //
50 + // Lightness comes from the theme's background, not its text colour:
51 + // reading the text colour the layout itself overrides would be a
52 + // loop. A dark theme gets a bright tint, a light one a deep one.
53 + readonly property bool tinted: store.wallTint
54 + readonly property real tintHue: store.wallTintHue / 360
55 + readonly property bool darkTheme: Kirigami.Theme.backgroundColor.hslLightness < 0.5
56 + // The accent (clock, live dot, borders) is the most saturated, so it
57 + // still stands out from the body text the way the neutral colour does.
58 + readonly property color tintText: Qt.hsla(tintHue, 0.70, darkTheme ? 0.62 : 0.30, 1)
59 + readonly property color tintDim: Qt.hsla(tintHue, 0.40, darkTheme ? 0.45 : 0.50, 1)
60 + readonly property color tintAccent: Qt.hsla(tintHue, 1.0, darkTheme ? 0.55 : 0.28, 1)
61 +
43 62 Component.onCompleted: store.wallViewers += 1
44 63 Component.onDestruction: store.wallViewers -= 1
45 64
46 65 ColumnLayout {
66 + Kirigami.Theme.textColor: wall.tinted ? wall.tintText : wall.Kirigami.Theme.textColor
67 + Kirigami.Theme.disabledTextColor: wall.tinted ? wall.tintDim : wall.Kirigami.Theme.disabledTextColor
68 + Kirigami.Theme.neutralTextColor: wall.tinted ? wall.tintAccent : wall.Kirigami.Theme.neutralTextColor
69 +
47 70 anchors.fill: parent
48 71 anchors.margins: Kirigami.Units.smallSpacing
49 72 spacing: Kirigami.Units.smallSpacing
@@ -157,6 +180,19 @@ Item {
157 180 PlasmaComponents3.ToolTip { text: i18n("See-through background (T)") }
158 181 }
159 182
183 + PlasmaComponents3.ToolButton {
184 + checkable: true
185 + checked: wall.tinted
186 + icon.name: "fill-color"
187 + text: i18n("Tint")
188 + display: QQC2.AbstractButton.IconOnly
189 + onClicked: wall.store.setWallTint(!wall.tinted)
190 + PlasmaComponents3.ToolTip {
191 + text: wall.inWindow ? i18n("All text in one hue (H) — pick the hue in the widget's settings")
192 + : i18n("All text in one hue — pick the hue in the widget's settings")
193 + }
194 + }
195 +
160 196 PlasmaComponents3.ToolButton {
161 197 visible: wall.inWindow
162 198 icon.name: wall.fullScreen ? "view-restore" : "view-fullscreen"
@@ -220,6 +256,10 @@ Item {
220 256 Layout.preferredHeight: 1 // equal shares
221 257 card: modelData
222 258 translucent: wall.translucent
259 + // The theme's own, from above the tint:
260 + // a running step stays told apart from
261 + // a passed one whatever the hue.
262 + runningColor: wall.Kirigami.Theme.neutralTextColor
223 263 now: wall.store.now
224 264 }
225 265 }
modified contents/ui/WallWindow.qml
+1 −0
@@ -48,6 +48,7 @@ Window {
48 48 Shortcut { sequences: [StandardKey.FullScreen]; onActivated: wallWindow.toggleFullScreen() }
49 49 Shortcut { sequence: "Escape"; enabled: wallWindow.fullScreen; onActivated: wallWindow.showNormal() }
50 50 Shortcut { sequence: "T"; onActivated: wallWindow.store.setWallTranslucent(!wallWindow.store.wallTranslucent) }
51 + Shortcut { sequence: "H"; onActivated: wallWindow.store.setWallTint(!wallWindow.store.wallTint) }
51 52 Shortcut { sequence: "R"; onActivated: wallWindow.store.setWallNewestFirst(!wallWindow.store.wallNewestFirst) }
52 53
53 54 WallView {
modified contents/ui/configBehavior.qml
+54 −0
@@ -13,6 +13,8 @@ KCM.SimpleKCM {
13 13 property alias cfg_hideIdle: hideIdleCheckBox.checked
14 14 property alias cfg_notifyMyFailures: notifyMyFailuresCheckBox.checked
15 15 property alias cfg_desktopWall: desktopWallCheckBox.checked
16 + property alias cfg_wallTint: wallTintCheckBox.checked
17 + property alias cfg_wallTintHue: wallTintHueSpinBox.value
16 18
17 19 Kirigami.FormLayout {
18 20 QQC2.SpinBox {
@@ -67,6 +69,58 @@ KCM.SimpleKCM {
67 69 text: i18n("Every job running right now, a card each, tiled to fill the widget. Resize it, or give it a screen of its own. In a panel the widget stays the circles; open the wall from the popup or the right-click menu instead.")
68 70 }
69 71
72 + QQC2.CheckBox {
73 + id: wallTintCheckBox
74 + text: i18n("Tint all text one hue")
75 + }
76 +
77 + RowLayout {
78 + Kirigami.FormData.label: i18n("Tint hue:")
79 + enabled: wallTintCheckBox.checked
80 + spacing: Kirigami.Units.smallSpacing
81 +
82 + QQC2.SpinBox {
83 + id: wallTintHueSpinBox
84 + from: 0
85 + to: 359
86 + value: 120
87 + // The wheel goes round: past 359 is 0 again.
88 + wrap: true
89 + textFromValue: function(value) { return value + "°" }
90 + valueFromText: function(text) { return parseInt(text, 10) || 0 }
91 + }
92 +
93 + // What the wall's text will look like, on a dark theme.
94 + Rectangle {
95 + Layout.preferredWidth: Kirigami.Units.gridUnit * 3
96 + Layout.preferredHeight: wallTintHueSpinBox.implicitHeight
97 + radius: Kirigami.Units.cornerRadius
98 + color: "#1b1e20"
99 + opacity: parent.enabled ? 1 : 0.5
100 + QQC2.Label {
101 + anchors.centerIn: parent
102 + font.family: "monospace"
103 + font.bold: true
104 + color: Qt.hsla(wallTintHueSpinBox.value / 360, 0.85, 0.62, 1)
105 + text: "0:42"
106 + }
107 + }
108 +
109 + QQC2.Button {
110 + text: i18n("Green")
111 + visible: wallTintHueSpinBox.value !== 120
112 + onClicked: wallTintHueSpinBox.value = 120
113 + }
114 + }
115 +
116 + QQC2.Label {
117 + Layout.fillWidth: true
118 + wrapMode: Text.WordWrap
119 + font: Kirigami.Theme.smallFont
120 + color: Kirigami.Theme.disabledTextColor
121 + text: i18n("Labels, the clock, card borders and icons, for a terminal look that pairs with the window's translucent background. Failed steps and errors keep their colours. H toggles it in the window, as does the header's tint button.")
122 + }
123 +
70 124 QQC2.CheckBox {
71 125 id: notifyMyFailuresCheckBox
72 126 Kirigami.FormData.label: i18n("Notifications:")
modified contents/ui/main.qml
+6 −0
@@ -86,6 +86,8 @@ PlasmoidItem {
86 86 readonly property string wallScope: Plasmoid.configuration.wallScope
87 87 readonly property bool wallNewestFirst: Plasmoid.configuration.wallNewestFirst
88 88 readonly property bool wallTranslucent: Plasmoid.configuration.wallTranslucent
89 + readonly property bool wallTint: Plasmoid.configuration.wallTint
90 + readonly property int wallTintHue: Plasmoid.configuration.wallTintHue
89 91 readonly property bool wallConfigured: api.configured
90 92 readonly property bool onDesktop: Plasmoid.formFactor !== PlasmaCore.Types.Horizontal
91 93 && Plasmoid.formFactor !== PlasmaCore.Types.Vertical
@@ -707,6 +709,10 @@ PlasmoidItem {
707 709 Plasmoid.configuration.wallTranslucent = on
708 710 }
709 711
712 + function setWallTint(on) {
713 + Plasmoid.configuration.wallTint = on
714 + }
715 +
710 716 function wallWebUrl() {
711 717 return api.wallUrl(wallScope)
712 718 }

Parents: ac30301