glintbar 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
glintbar-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paone9
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: glintbar
3
+ Version: 1.0.0
4
+ Summary: A slim always-on-top hardware monitor that lives in the empty part of the Windows taskbar.
5
+ Author: paone9
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/paone9/GlintBar
8
+ Project-URL: Issues, https://github.com/paone9/GlintBar/issues
9
+ Keywords: hardware-monitor,taskbar,windows,gpu,cpu,temperature,system-monitor
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Win32 (MS Windows)
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: System :: Monitoring
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: pywebview>=6.2.1
20
+ Requires-Dist: psutil>=7.2.2
21
+ Dynamic: license-file
22
+
23
+ # GlintBar
24
+
25
+ [![CI](https://github.com/paone9/GlintBar/actions/workflows/ci.yml/badge.svg)](https://github.com/paone9/GlintBar/actions/workflows/ci.yml)
26
+ [![CodeQL](https://github.com/paone9/GlintBar/actions/workflows/codeql.yml/badge.svg)](https://github.com/paone9/GlintBar/actions/workflows/codeql.yml)
27
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/paone9/GlintBar/badge)](https://scorecard.dev/viewer/?uri=github.com/paone9/GlintBar)
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
29
+
30
+ A small always-on-top hardware monitor that sits in the empty part of your
31
+ Windows taskbar (or docked at the top/bottom). It shows live CPU, RAM, GPU, disk
32
+ and network so you can keep half an eye on them, and it flags thermal, clock or
33
+ power spikes early, before they turn into throttling or a crash like a
34
+ `VIDEO_TDR_FAILURE` GPU hang.
35
+
36
+ ## What it shows
37
+
38
+ A single row of up to nine metrics, each with a value and a 60-second sparkline.
39
+ Hover a metric to pop out a bigger live graph with the session min, max and
40
+ average.
41
+
42
+ | Tile | Source | Watch for |
43
+ |------|--------|-----------|
44
+ | GPU Temp | nvidia-smi | amber >80 °C, red >87 °C |
45
+ | GPU Load | nvidia-smi / perf counters | sustained 100% |
46
+ | VRAM | nvidia-smi | red >95 % |
47
+ | GPU Power | nvidia-smi | sudden dropouts under load |
48
+ | GPU Clock | nvidia-smi | dropping to idle mid-load can mean a TDR hang |
49
+ | SYS Temp | ACPI thermal zone | red >97 °C |
50
+ | CPU Temp | LibreHardwareMonitor | real CPU package temp (needs LHM, see below) |
51
+ | Fan | LibreHardwareMonitor | fan RPM (needs LHM, see below) |
52
+ | CPU | psutil | red >95 % |
53
+ | RAM | psutil | red >95 % |
54
+ | Network | psutil | MB/s in+out |
55
+ | Disk | psutil | MB/s read+write |
56
+
57
+ Everything comes from first-party or open-source sources and runs without admin
58
+ rights. The bar only shows tiles your machine can actually provide (details
59
+ below).
60
+
61
+ ## Supported hardware and platforms
62
+
63
+ Windows 10/11 only. It uses Win32 APIs, so there's no macOS or Linux build.
64
+
65
+ CPU, RAM, disk and network work on any Windows PC, via `psutil`.
66
+
67
+ GPU support is detected at launch:
68
+
69
+ | GPU | Load | VRAM | Temp | Clock | Power | Source |
70
+ |-----|:----:|:----:|:----:|:-----:|:-----:|--------|
71
+ | NVIDIA | ✅ | ✅ | ✅ | ✅ | ✅ | `nvidia-smi` (driver) |
72
+ | AMD / Intel / other | ✅ | — | — | — | — | Windows GPU perf counters |
73
+ | No GPU / headless | — | — | — | — | — | GPU tiles hidden |
74
+
75
+ AMD and Intel GPUs get load only. Temp, clock, power and VRAM need NVIDIA. The
76
+ settings panel greys out anything your hardware can't provide.
77
+
78
+ ### Privilege levels: it works without admin, and does more with it
79
+
80
+ GlintBar itself never needs administrator rights, and it runs fine on its own. At
81
+ normal privilege you get everything except real CPU temperature and fan speed:
82
+ CPU, RAM, disk, network, GPU (per the table above), and the SYS Temp tile.
83
+
84
+ The SYS Temp tile reads the ACPI thermal zone through a Windows performance
85
+ counter, so it needs no admin. It's a generic zone though, so treat it as a rough
86
+ system-heat reading, not the exact CPU-package sensor, and some machines block it.
87
+
88
+ Real per-core CPU temperature and fan RPM need kernel-level access, which no
89
+ no-admin tool can do. For those, run a helper that already does the low-level
90
+ reads with admin, and GlintBar reads from it while staying at normal privilege.
91
+ It picks up the extra **CPU Temp** and **Fan** tiles automatically when one is
92
+ present. Two options, whichever you already use:
93
+
94
+ **HWiNFO** (via shared memory): in HWiNFO, open Settings and tick **Shared Memory
95
+ Support**. That's it. GlintBar reads the shared-memory block directly (no network
96
+ at all). This is easiest if you already run HWiNFO.
97
+
98
+ **LibreHardwareMonitor** (open source, via a local web server): in its Options
99
+ menu turn on **Remote Web Server** (default port 8085). GlintBar reads only from
100
+ `http://127.0.0.1:8085` on your own machine, never the internet. Use
101
+ `GLINTBAR_LHM_PORT` if you change the port.
102
+
103
+ Either way, start the helper before GlintBar (or restart GlintBar) and the tiles
104
+ appear on their own. HWiNFO is preferred if both are running.
105
+
106
+ ### Requirements
107
+
108
+ Python 3.8+ and the WebView2 runtime, which already ships with Windows 11. No
109
+ admin, no compiled binaries, just Python source. The only dependencies are
110
+ `pywebview` and `psutil`, installed automatically.
111
+
112
+ ## Install and run
113
+
114
+ The simplest one-command install (isolated, adds a `glintbar` command):
115
+
116
+ ```
117
+ pipx install git+https://github.com/paone9/GlintBar
118
+ glintbar
119
+ ```
120
+
121
+ Once it is released on PyPI this becomes `pip install glintbar`.
122
+
123
+ From a clone, for hacking on it:
124
+
125
+ ```
126
+ pip install -r requirements.txt
127
+ python -m glintbar
128
+ ```
129
+
130
+ For a launch with no console window, double-click `start_glintbar.vbs` (or
131
+ `start_glintbar.cmd` if Windows Script Host is blocked). To start it on login,
132
+ drop a shortcut to the `glintbar` command (or to `start_glintbar.vbs`) into
133
+ `shell:startup` (Win+R, then `shell:startup`).
134
+
135
+ Controls sit on the right: the gear opens settings, ● toggles CSV logging (it
136
+ turns red while recording), ▤ opens the log folder, and ✕ closes.
137
+
138
+ Settings and logs live in `%LOCALAPPDATA%\GlintBar` (`config.json` and `logs\`),
139
+ so they survive reinstalls and stay out of the install folder.
140
+
141
+ ## Settings
142
+
143
+ Open the gear to change:
144
+
145
+ - Metrics: pick the ones you want, with presets for Essentials, GPU focus, or
146
+ All. The bar rebuilds and resizes to fit only what you keep.
147
+ - Size: Compact, Normal, or Large.
148
+ - Align: Left, Center, or Right within the taskbar gap.
149
+ - Sparklines, critical alerts, and alert sound: on or off.
150
+
151
+ Your choices are saved to `config.json` in `%LOCALAPPDATA%\GlintBar`.
152
+
153
+ ## Placement
154
+
155
+ Set `DOCK` at the top of `glintbar/monitor.py`:
156
+
157
+ - `"taskbar"` (default) floats a topmost bar over the empty part of the taskbar,
158
+ between your app buttons and the system tray. It's a normal top-level window
159
+ that sits above the Windows 11 taskbar's input layer, so its hovers and clicks
160
+ work and it uses no extra screen space. A small background thread keeps it
161
+ fitted to the gap and on top as apps open and close.
162
+ - `"bottom"` is a thin full-width strip just above the taskbar.
163
+ - `"top"` is a thin full-width strip at the top of the screen.
164
+
165
+ It lives on your **primary monitor's** taskbar (the one Windows treats as
166
+ primary), and expects that taskbar along the bottom. On a multi-monitor setup with
167
+ different scaling per monitor it stays correctly placed and sized, since the app is
168
+ per-monitor-DPI aware. Only one copy runs at a time; launching it again just hands
169
+ back to the running one. Letting you pick a different monitor is on the
170
+ [roadmap](ROADMAP.md).
171
+
172
+ When a video, game, or slideshow goes fullscreen on the same screen, the bar
173
+ hides itself so it's not in the way, and it comes back when you exit fullscreen.
174
+
175
+ ## Hover to expand
176
+
177
+ Hover a metric and a bigger popup appears just above it with a full 60-second
178
+ graph, the current value, and min/max/avg. It disappears when you move off.
179
+
180
+ ## Critical alerts
181
+
182
+ When a metric crosses a critical threshold (GPU temp over 87 °C, or CPU, RAM or
183
+ VRAM over 95%), an extra tile appears at the end of the bar. It's added rather
184
+ than swapped in, so your other tiles don't move, and it stays until things settle
185
+ (about two seconds to appear, with a short cooldown before it clears). There's an
186
+ optional beep on a new alert so you notice without watching. Both are toggles in
187
+ settings.
188
+
189
+ ## Away watch
190
+
191
+ If you step away and the machine keeps working hard, GlintBar figures out what was
192
+ responsible and tells you when you get back. It counts you as away as soon as the
193
+ screen is locked, or after a few minutes with no keyboard or mouse activity. While
194
+ you're away and the CPU stays high, it records which processes are behind it. When
195
+ you return it shows a short summary (how long you were away, the peak CPU and
196
+ temperature, and the busiest processes) and appends a line to `logs/away.csv`. If
197
+ nothing unusual happened, it stays quiet.
198
+
199
+ Toggle it in settings. The idle delay and CPU threshold are in `config.json`
200
+ (`away_after_min`, `away_cpu_pct`), defaulting to 5 minutes and 25%.
201
+
202
+ ## CSV logging
203
+
204
+ Press ● to write one row per second to `logs/glintbar_<timestamp>.csv` with every
205
+ metric. Open it in pandas or Excel and line it up against a workload timeline to
206
+ see how throttling, thermal drift or power dropouts track with load.
207
+
208
+ ```python
209
+ import pandas as pd
210
+ df = pd.read_csv("logs/glintbar_<timestamp>.csv", parse_dates=["timestamp"])
211
+ df.set_index("timestamp")[["gpu_temp", "gpu_clock", "gpu_power"]].plot()
212
+ ```
213
+
214
+ ## Notes
215
+
216
+ - It adapts to any resolution and display scaling; DPI is read at runtime.
217
+ - Light footprint: CPU, RAM, disk and network update every second, while the GPU
218
+ (which means spawning `nvidia-smi`) is polled every two seconds and cached in
219
+ between, so the monitor itself stays cheap.
220
+ - Thresholds live in the `META` map at the top of `glintbar/ui.html`, so they're
221
+ easy to change.
222
+ - Ideas that aren't built yet (attention rotation, anomaly detection, toast
223
+ notifications, AMD/Intel temperatures) are written up in [ROADMAP.md](ROADMAP.md).
224
+
225
+ ## Security
226
+
227
+ GlintBar is meant to be easy to review. It's plain Python with no binaries and no
228
+ obfuscation, it needs no admin, and it makes no network connections at all: no
229
+ telemetry, no update checks, nothing. It only reads system metrics and writes
230
+ `config.json` and logs inside its own folder.
231
+
232
+ CI runs `ruff` and `bandit` on every push, and a `CodeQL` workflow runs semantic
233
+ static analysis of the Python, the UI JavaScript, and the workflows themselves.
234
+ GitHub Actions are pinned to commit SHAs and the two dependencies are pinned to
235
+ their tested versions, so the build can't shift under you. An
236
+ [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/paone9/GlintBar)
237
+ grades the repo's security posture (badge above). See the repo's Security tab, and
238
+ [SECURITY.md](SECURITY.md) for exactly what the app touches.
239
+
240
+ ## License
241
+
242
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,220 @@
1
+ # GlintBar
2
+
3
+ [![CI](https://github.com/paone9/GlintBar/actions/workflows/ci.yml/badge.svg)](https://github.com/paone9/GlintBar/actions/workflows/ci.yml)
4
+ [![CodeQL](https://github.com/paone9/GlintBar/actions/workflows/codeql.yml/badge.svg)](https://github.com/paone9/GlintBar/actions/workflows/codeql.yml)
5
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/paone9/GlintBar/badge)](https://scorecard.dev/viewer/?uri=github.com/paone9/GlintBar)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ A small always-on-top hardware monitor that sits in the empty part of your
9
+ Windows taskbar (or docked at the top/bottom). It shows live CPU, RAM, GPU, disk
10
+ and network so you can keep half an eye on them, and it flags thermal, clock or
11
+ power spikes early, before they turn into throttling or a crash like a
12
+ `VIDEO_TDR_FAILURE` GPU hang.
13
+
14
+ ## What it shows
15
+
16
+ A single row of up to nine metrics, each with a value and a 60-second sparkline.
17
+ Hover a metric to pop out a bigger live graph with the session min, max and
18
+ average.
19
+
20
+ | Tile | Source | Watch for |
21
+ |------|--------|-----------|
22
+ | GPU Temp | nvidia-smi | amber >80 °C, red >87 °C |
23
+ | GPU Load | nvidia-smi / perf counters | sustained 100% |
24
+ | VRAM | nvidia-smi | red >95 % |
25
+ | GPU Power | nvidia-smi | sudden dropouts under load |
26
+ | GPU Clock | nvidia-smi | dropping to idle mid-load can mean a TDR hang |
27
+ | SYS Temp | ACPI thermal zone | red >97 °C |
28
+ | CPU Temp | LibreHardwareMonitor | real CPU package temp (needs LHM, see below) |
29
+ | Fan | LibreHardwareMonitor | fan RPM (needs LHM, see below) |
30
+ | CPU | psutil | red >95 % |
31
+ | RAM | psutil | red >95 % |
32
+ | Network | psutil | MB/s in+out |
33
+ | Disk | psutil | MB/s read+write |
34
+
35
+ Everything comes from first-party or open-source sources and runs without admin
36
+ rights. The bar only shows tiles your machine can actually provide (details
37
+ below).
38
+
39
+ ## Supported hardware and platforms
40
+
41
+ Windows 10/11 only. It uses Win32 APIs, so there's no macOS or Linux build.
42
+
43
+ CPU, RAM, disk and network work on any Windows PC, via `psutil`.
44
+
45
+ GPU support is detected at launch:
46
+
47
+ | GPU | Load | VRAM | Temp | Clock | Power | Source |
48
+ |-----|:----:|:----:|:----:|:-----:|:-----:|--------|
49
+ | NVIDIA | ✅ | ✅ | ✅ | ✅ | ✅ | `nvidia-smi` (driver) |
50
+ | AMD / Intel / other | ✅ | — | — | — | — | Windows GPU perf counters |
51
+ | No GPU / headless | — | — | — | — | — | GPU tiles hidden |
52
+
53
+ AMD and Intel GPUs get load only. Temp, clock, power and VRAM need NVIDIA. The
54
+ settings panel greys out anything your hardware can't provide.
55
+
56
+ ### Privilege levels: it works without admin, and does more with it
57
+
58
+ GlintBar itself never needs administrator rights, and it runs fine on its own. At
59
+ normal privilege you get everything except real CPU temperature and fan speed:
60
+ CPU, RAM, disk, network, GPU (per the table above), and the SYS Temp tile.
61
+
62
+ The SYS Temp tile reads the ACPI thermal zone through a Windows performance
63
+ counter, so it needs no admin. It's a generic zone though, so treat it as a rough
64
+ system-heat reading, not the exact CPU-package sensor, and some machines block it.
65
+
66
+ Real per-core CPU temperature and fan RPM need kernel-level access, which no
67
+ no-admin tool can do. For those, run a helper that already does the low-level
68
+ reads with admin, and GlintBar reads from it while staying at normal privilege.
69
+ It picks up the extra **CPU Temp** and **Fan** tiles automatically when one is
70
+ present. Two options, whichever you already use:
71
+
72
+ **HWiNFO** (via shared memory): in HWiNFO, open Settings and tick **Shared Memory
73
+ Support**. That's it. GlintBar reads the shared-memory block directly (no network
74
+ at all). This is easiest if you already run HWiNFO.
75
+
76
+ **LibreHardwareMonitor** (open source, via a local web server): in its Options
77
+ menu turn on **Remote Web Server** (default port 8085). GlintBar reads only from
78
+ `http://127.0.0.1:8085` on your own machine, never the internet. Use
79
+ `GLINTBAR_LHM_PORT` if you change the port.
80
+
81
+ Either way, start the helper before GlintBar (or restart GlintBar) and the tiles
82
+ appear on their own. HWiNFO is preferred if both are running.
83
+
84
+ ### Requirements
85
+
86
+ Python 3.8+ and the WebView2 runtime, which already ships with Windows 11. No
87
+ admin, no compiled binaries, just Python source. The only dependencies are
88
+ `pywebview` and `psutil`, installed automatically.
89
+
90
+ ## Install and run
91
+
92
+ The simplest one-command install (isolated, adds a `glintbar` command):
93
+
94
+ ```
95
+ pipx install git+https://github.com/paone9/GlintBar
96
+ glintbar
97
+ ```
98
+
99
+ Once it is released on PyPI this becomes `pip install glintbar`.
100
+
101
+ From a clone, for hacking on it:
102
+
103
+ ```
104
+ pip install -r requirements.txt
105
+ python -m glintbar
106
+ ```
107
+
108
+ For a launch with no console window, double-click `start_glintbar.vbs` (or
109
+ `start_glintbar.cmd` if Windows Script Host is blocked). To start it on login,
110
+ drop a shortcut to the `glintbar` command (or to `start_glintbar.vbs`) into
111
+ `shell:startup` (Win+R, then `shell:startup`).
112
+
113
+ Controls sit on the right: the gear opens settings, ● toggles CSV logging (it
114
+ turns red while recording), ▤ opens the log folder, and ✕ closes.
115
+
116
+ Settings and logs live in `%LOCALAPPDATA%\GlintBar` (`config.json` and `logs\`),
117
+ so they survive reinstalls and stay out of the install folder.
118
+
119
+ ## Settings
120
+
121
+ Open the gear to change:
122
+
123
+ - Metrics: pick the ones you want, with presets for Essentials, GPU focus, or
124
+ All. The bar rebuilds and resizes to fit only what you keep.
125
+ - Size: Compact, Normal, or Large.
126
+ - Align: Left, Center, or Right within the taskbar gap.
127
+ - Sparklines, critical alerts, and alert sound: on or off.
128
+
129
+ Your choices are saved to `config.json` in `%LOCALAPPDATA%\GlintBar`.
130
+
131
+ ## Placement
132
+
133
+ Set `DOCK` at the top of `glintbar/monitor.py`:
134
+
135
+ - `"taskbar"` (default) floats a topmost bar over the empty part of the taskbar,
136
+ between your app buttons and the system tray. It's a normal top-level window
137
+ that sits above the Windows 11 taskbar's input layer, so its hovers and clicks
138
+ work and it uses no extra screen space. A small background thread keeps it
139
+ fitted to the gap and on top as apps open and close.
140
+ - `"bottom"` is a thin full-width strip just above the taskbar.
141
+ - `"top"` is a thin full-width strip at the top of the screen.
142
+
143
+ It lives on your **primary monitor's** taskbar (the one Windows treats as
144
+ primary), and expects that taskbar along the bottom. On a multi-monitor setup with
145
+ different scaling per monitor it stays correctly placed and sized, since the app is
146
+ per-monitor-DPI aware. Only one copy runs at a time; launching it again just hands
147
+ back to the running one. Letting you pick a different monitor is on the
148
+ [roadmap](ROADMAP.md).
149
+
150
+ When a video, game, or slideshow goes fullscreen on the same screen, the bar
151
+ hides itself so it's not in the way, and it comes back when you exit fullscreen.
152
+
153
+ ## Hover to expand
154
+
155
+ Hover a metric and a bigger popup appears just above it with a full 60-second
156
+ graph, the current value, and min/max/avg. It disappears when you move off.
157
+
158
+ ## Critical alerts
159
+
160
+ When a metric crosses a critical threshold (GPU temp over 87 °C, or CPU, RAM or
161
+ VRAM over 95%), an extra tile appears at the end of the bar. It's added rather
162
+ than swapped in, so your other tiles don't move, and it stays until things settle
163
+ (about two seconds to appear, with a short cooldown before it clears). There's an
164
+ optional beep on a new alert so you notice without watching. Both are toggles in
165
+ settings.
166
+
167
+ ## Away watch
168
+
169
+ If you step away and the machine keeps working hard, GlintBar figures out what was
170
+ responsible and tells you when you get back. It counts you as away as soon as the
171
+ screen is locked, or after a few minutes with no keyboard or mouse activity. While
172
+ you're away and the CPU stays high, it records which processes are behind it. When
173
+ you return it shows a short summary (how long you were away, the peak CPU and
174
+ temperature, and the busiest processes) and appends a line to `logs/away.csv`. If
175
+ nothing unusual happened, it stays quiet.
176
+
177
+ Toggle it in settings. The idle delay and CPU threshold are in `config.json`
178
+ (`away_after_min`, `away_cpu_pct`), defaulting to 5 minutes and 25%.
179
+
180
+ ## CSV logging
181
+
182
+ Press ● to write one row per second to `logs/glintbar_<timestamp>.csv` with every
183
+ metric. Open it in pandas or Excel and line it up against a workload timeline to
184
+ see how throttling, thermal drift or power dropouts track with load.
185
+
186
+ ```python
187
+ import pandas as pd
188
+ df = pd.read_csv("logs/glintbar_<timestamp>.csv", parse_dates=["timestamp"])
189
+ df.set_index("timestamp")[["gpu_temp", "gpu_clock", "gpu_power"]].plot()
190
+ ```
191
+
192
+ ## Notes
193
+
194
+ - It adapts to any resolution and display scaling; DPI is read at runtime.
195
+ - Light footprint: CPU, RAM, disk and network update every second, while the GPU
196
+ (which means spawning `nvidia-smi`) is polled every two seconds and cached in
197
+ between, so the monitor itself stays cheap.
198
+ - Thresholds live in the `META` map at the top of `glintbar/ui.html`, so they're
199
+ easy to change.
200
+ - Ideas that aren't built yet (attention rotation, anomaly detection, toast
201
+ notifications, AMD/Intel temperatures) are written up in [ROADMAP.md](ROADMAP.md).
202
+
203
+ ## Security
204
+
205
+ GlintBar is meant to be easy to review. It's plain Python with no binaries and no
206
+ obfuscation, it needs no admin, and it makes no network connections at all: no
207
+ telemetry, no update checks, nothing. It only reads system metrics and writes
208
+ `config.json` and logs inside its own folder.
209
+
210
+ CI runs `ruff` and `bandit` on every push, and a `CodeQL` workflow runs semantic
211
+ static analysis of the Python, the UI JavaScript, and the workflows themselves.
212
+ GitHub Actions are pinned to commit SHAs and the two dependencies are pinned to
213
+ their tested versions, so the build can't shift under you. An
214
+ [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/paone9/GlintBar)
215
+ grades the repo's security posture (badge above). See the repo's Security tab, and
216
+ [SECURITY.md](SECURITY.md) for exactly what the app touches.
217
+
218
+ ## License
219
+
220
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """GlintBar - a slim always-on-top hardware monitor for the Windows taskbar gap."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m glintbar`."""
2
+
3
+ from .monitor import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,69 @@
1
+ <meta charset="utf-8">
2
+ <style>
3
+ :root{ --bg:#12161c; --line:#2b3542; --text:#e6edf3; --dim:#9aa4af;
4
+ --ok:#58a6ff; --warn:#d29922; --crit:#f85149; }
5
+ *{ box-sizing:border-box; margin:0; padding:0; }
6
+ html,body{ height:100%; }
7
+ body{ background:transparent; color:var(--text);
8
+ font:12px/1.35 "Segoe UI",system-ui,sans-serif; overflow:hidden; }
9
+ .card{ height:100vh; background:var(--bg); border:1px solid var(--line);
10
+ border-radius:10px; padding:12px 14px; box-shadow:0 8px 26px #000a;
11
+ display:flex; flex-direction:column; }
12
+ .hdr{ font-size:14px; font-weight:700; }
13
+ .sub{ font-size:11px; color:var(--dim); margin:1px 0 10px; }
14
+ .row{ display:flex; justify-content:space-between; font-size:12px; padding:2px 0; }
15
+ .row .k{ color:var(--dim); }
16
+ .row .v{ font-weight:600; font-variant-numeric:tabular-nums; }
17
+ .v.hot{ color:var(--crit); }
18
+ .lbl{ font-size:10px; text-transform:uppercase; letter-spacing:.06em;
19
+ color:var(--dim); margin:10px 0 4px; }
20
+ .proc{ display:flex; justify-content:space-between; font-size:12px; padding:2px 0;
21
+ border-top:1px solid #ffffff10; }
22
+ .proc .p{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
23
+ .proc .c{ color:var(--warn); font-weight:600; font-variant-numeric:tabular-nums;
24
+ padding-left:8px; }
25
+ .actions{ display:flex; gap:8px; margin-top:auto; padding-top:10px; }
26
+ .actions button{ flex:1; padding:7px; border-radius:6px; font-size:12px; cursor:pointer;
27
+ border:1px solid var(--line); background:#1b2430; color:var(--text); }
28
+ .actions .go{ border-color:var(--ok); color:var(--ok); }
29
+ </style>
30
+
31
+ <div class="card">
32
+ <div class="hdr">While you were away</div>
33
+ <div class="sub" id="sub">—</div>
34
+ <div class="row"><span class="k">Peak CPU</span><span class="v" id="cpu">—</span></div>
35
+ <div class="row"><span class="k">Peak system temp</span><span class="v" id="temp">—</span></div>
36
+ <div class="lbl">Busiest processes</div>
37
+ <div id="procs"></div>
38
+ <div class="actions">
39
+ <button id="logs">Open log</button>
40
+ <button class="go" id="dismiss">Got it</button>
41
+ </div>
42
+ </div>
43
+
44
+ <script>
45
+ async function tick(){
46
+ let r; try{ r = await window.pywebview.api.get(); }catch(e){ return; }
47
+ if(!r) return;
48
+ document.getElementById("sub").textContent = `${r.when}, away for ${r.duration_min} min`;
49
+ const cpu = document.getElementById("cpu");
50
+ cpu.textContent = r.peak_cpu + "%"; cpu.className = "v" + (r.peak_cpu >= 60 ? " hot" : "");
51
+ const temp = document.getElementById("temp");
52
+ temp.textContent = r.peak_temp ? r.peak_temp + " °C" : "n/a";
53
+ temp.className = "v" + (r.peak_temp >= 90 ? " hot" : "");
54
+ const host = document.getElementById("procs"); host.innerHTML = "";
55
+ const list = r.offenders || [];
56
+ if(list.length){
57
+ for(const [name, c] of list){
58
+ const d = document.createElement("div"); d.className = "proc";
59
+ d.innerHTML = `<span class="p">${name}</span><span class="c">${c}%</span>`;
60
+ host.appendChild(d);
61
+ }
62
+ } else {
63
+ host.innerHTML = `<div class="proc"><span class="p">nothing stood out</span></div>`;
64
+ }
65
+ }
66
+ document.getElementById("logs").onclick = ()=> window.pywebview.api.open_logs();
67
+ document.getElementById("dismiss").onclick = ()=> window.pywebview.api.dismiss();
68
+ window.addEventListener("pywebviewready", ()=>{ tick(); setInterval(tick, 1000); });
69
+ </script>
@@ -0,0 +1,88 @@
1
+ <meta charset="utf-8">
2
+ <style>
3
+ :root{ --bg:#12161c; --line:#2b3542; --text:#e6edf3; --dim:#9aa4af;
4
+ --ok:#58a6ff; --warn:#d29922; --crit:#f85149; }
5
+ *{ box-sizing:border-box; margin:0; padding:0; }
6
+ html,body{ height:100%; }
7
+ body{ background:transparent; color:var(--text);
8
+ font:12px/1.2 "Segoe UI",system-ui,sans-serif; overflow:hidden; }
9
+ .card{ height:100vh; background:var(--bg); border:1px solid var(--line);
10
+ border-radius:10px; padding:9px 11px; box-shadow:0 6px 22px #000a; }
11
+ .top{ display:flex; justify-content:space-between; align-items:baseline; }
12
+ .lbl{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--dim); }
13
+ .val{ font-size:22px; font-weight:700; font-variant-numeric:tabular-nums; }
14
+ .val .u{ font-size:12px; color:var(--dim); font-weight:400; }
15
+ .stats{ font-size:11px; color:var(--dim); margin-top:1px; }
16
+ canvas{ width:100%; height:96px; margin-top:6px; display:block; }
17
+ .val.ok{ color:var(--text); } .val.warn{ color:var(--warn); } .val.crit{ color:var(--crit); }
18
+ </style>
19
+ <div class="card">
20
+ <div class="top"><span class="lbl" id="lbl">—</span>
21
+ <span class="val ok" id="val">—</span></div>
22
+ <div class="stats" id="stats"></div>
23
+ <canvas id="cv" width="600" height="220"></canvas>
24
+ </div>
25
+
26
+ <script>
27
+ const META = {
28
+ gpu_temp:{lbl:"GPU Temperature",unit:"°C",warn:80,crit:87,max:100},
29
+ gpu_util:{lbl:"GPU Load",unit:"%",warn:1e9,crit:1e9,max:100},
30
+ gpu_mem_pct:{lbl:"VRAM Used",unit:"%",warn:85,crit:95,max:100},
31
+ gpu_power:{lbl:"GPU Power",unit:"W",warn:1e9,crit:1e9,max:130},
32
+ gpu_clock:{lbl:"GPU Clock",unit:"MHz",warn:1e9,crit:1e9,max:2200},
33
+ sys_temp:{lbl:"System Temp (ACPI zone)",unit:"°C",warn:85,crit:97,max:110},
34
+ cpu_temp:{lbl:"CPU Temperature",unit:"°C",warn:85,crit:97,max:110},
35
+ fan_rpm:{lbl:"Fan Speed",unit:"RPM",warn:1e9,crit:1e9,max:6000},
36
+ cpu:{lbl:"CPU Load",unit:"%",warn:85,crit:95,max:100},
37
+ ram_pct:{lbl:"RAM Used",unit:"%",warn:85,crit:95,max:100},
38
+ net_mbps:{lbl:"Network",unit:"MB/s",warn:1e9,crit:1e9,max:50,dyn:true},
39
+ disk_mbps:{lbl:"Disk",unit:"MB/s",warn:1e9,crit:1e9,max:200,dyn:true},
40
+ };
41
+ function cls(m,v){ if(v==null)return"ok"; if(v>=m.crit)return"crit"; if(v>=m.warn)return"warn"; return"ok"; }
42
+ function fmt(v,id){ if(v==null)return"—";
43
+ if(id==="net_mbps"||id==="disk_mbps"||id==="gpu_power")return v.toFixed(1); return Math.round(v); }
44
+ function gv(n){ return getComputedStyle(document.documentElement).getPropertyValue(n).trim(); }
45
+ function color(c){ return c==="crit"?gv("--crit"):c==="warn"?gv("--warn"):gv("--ok"); }
46
+
47
+ function draw(cv,data,m,colr){
48
+ const ctx=cv.getContext("2d"), w=cv.width, h=cv.height, pad=6;
49
+ ctx.clearRect(0,0,w,h);
50
+ // baseline grid
51
+ ctx.strokeStyle="#ffffff14"; ctx.lineWidth=1;
52
+ for(let i=0;i<=2;i++){ const y=pad+(h-2*pad)*i/2; ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(w,y); ctx.stroke(); }
53
+ if(!data.length) return;
54
+ let top = m.dyn ? Math.max(...data,1)*1.2 : m.max;
55
+ const n=data.length, sx=w/Math.max(n-1,1);
56
+ ctx.beginPath();
57
+ data.forEach((v,i)=>{ const y=h-pad-(Math.min((v==null?0:v),top)/top)*(h-2*pad); const x=i*sx;
58
+ i===0?ctx.moveTo(x,y):ctx.lineTo(x,y); });
59
+ ctx.strokeStyle=colr; ctx.lineWidth=2.5; ctx.lineJoin="round"; ctx.stroke();
60
+ ctx.lineTo((n-1)*sx,h); ctx.lineTo(0,h); ctx.closePath(); ctx.fillStyle=colr+"22"; ctx.fill();
61
+ }
62
+
63
+ let lastMetric = null;
64
+ async function tick(){
65
+ let d; try{ d=await window.pywebview.api.get(); }catch(e){ return; }
66
+ if(!d || !d.metric){ lastMetric=null; return; }
67
+ const m=META[d.metric]; if(!m) return;
68
+ if(d.metric !== lastMetric){ // new tile: wipe the previous frame before drawing this one
69
+ lastMetric = d.metric;
70
+ document.getElementById("stats").textContent = "";
71
+ const c0=document.getElementById("cv"); c0.getContext("2d").clearRect(0,0,c0.width,c0.height);
72
+ }
73
+ const v=d.latest, c=cls(m,v);
74
+ document.getElementById("lbl").textContent = m.lbl;
75
+ const val=document.getElementById("val"); val.className="val "+c;
76
+ val.innerHTML = fmt(v,d.metric)+` <span class="u">${m.unit}</span>`;
77
+ const s=d.stats||{};
78
+ let extra="";
79
+ if(d.metric==="gpu_mem_pct"&&d.extra&&d.extra.gpu_mem_used!=null)
80
+ extra=` · ${Math.round(d.extra.gpu_mem_used)}/${Math.round(d.extra.gpu_mem_total)} MB`;
81
+ if(d.metric==="ram_pct"&&d.extra&&d.extra.ram_used_gb!=null)
82
+ extra=` · ${d.extra.ram_used_gb.toFixed(1)}/${d.extra.ram_total_gb.toFixed(0)} GB`;
83
+ document.getElementById("stats").textContent =
84
+ (s.min!=null?`min ${fmt(s.min,d.metric)} · max ${fmt(s.max,d.metric)} · avg ${fmt(s.avg,d.metric)} · last 60s`:"") + extra;
85
+ draw(document.getElementById("cv"), d.hist||[], m, color(c));
86
+ }
87
+ window.addEventListener("pywebviewready", ()=>{ tick(); setInterval(tick, 100); });
88
+ </script>