glucoglance 0.3.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.
Files changed (48) hide show
  1. glucoglance-0.3.0/LICENSE +21 -0
  2. glucoglance-0.3.0/PKG-INFO +327 -0
  3. glucoglance-0.3.0/README.md +295 -0
  4. glucoglance-0.3.0/pyproject.toml +77 -0
  5. glucoglance-0.3.0/setup.cfg +4 -0
  6. glucoglance-0.3.0/src/glucoglance/__init__.py +11 -0
  7. glucoglance-0.3.0/src/glucoglance/alerts/__init__.py +1 -0
  8. glucoglance-0.3.0/src/glucoglance/alerts/base.py +7 -0
  9. glucoglance-0.3.0/src/glucoglance/assets/icon.png +0 -0
  10. glucoglance-0.3.0/src/glucoglance/client/__init__.py +1 -0
  11. glucoglance-0.3.0/src/glucoglance/client/errors.py +28 -0
  12. glucoglance-0.3.0/src/glucoglance/client/librelinkup.py +275 -0
  13. glucoglance-0.3.0/src/glucoglance/config/__init__.py +1 -0
  14. glucoglance-0.3.0/src/glucoglance/config/autostart.py +64 -0
  15. glucoglance-0.3.0/src/glucoglance/config/credentials.py +27 -0
  16. glucoglance-0.3.0/src/glucoglance/config/settings.py +102 -0
  17. glucoglance-0.3.0/src/glucoglance/domain/__init__.py +1 -0
  18. glucoglance-0.3.0/src/glucoglance/domain/range.py +38 -0
  19. glucoglance-0.3.0/src/glucoglance/domain/reading.py +29 -0
  20. glucoglance-0.3.0/src/glucoglance/domain/trend.py +41 -0
  21. glucoglance-0.3.0/src/glucoglance/domain/units.py +29 -0
  22. glucoglance-0.3.0/src/glucoglance/main.py +205 -0
  23. glucoglance-0.3.0/src/glucoglance/poller/__init__.py +1 -0
  24. glucoglance-0.3.0/src/glucoglance/poller/poller.py +136 -0
  25. glucoglance-0.3.0/src/glucoglance/ui/__init__.py +1 -0
  26. glucoglance-0.3.0/src/glucoglance/ui/about_dialog.py +49 -0
  27. glucoglance-0.3.0/src/glucoglance/ui/app_icon.py +17 -0
  28. glucoglance-0.3.0/src/glucoglance/ui/credential_prompt.py +80 -0
  29. glucoglance-0.3.0/src/glucoglance/ui/disclaimer.py +14 -0
  30. glucoglance-0.3.0/src/glucoglance/ui/display.py +33 -0
  31. glucoglance-0.3.0/src/glucoglance/ui/icon_renderer.py +87 -0
  32. glucoglance-0.3.0/src/glucoglance/ui/tray.py +243 -0
  33. glucoglance-0.3.0/src/glucoglance.egg-info/PKG-INFO +327 -0
  34. glucoglance-0.3.0/src/glucoglance.egg-info/SOURCES.txt +46 -0
  35. glucoglance-0.3.0/src/glucoglance.egg-info/dependency_links.txt +1 -0
  36. glucoglance-0.3.0/src/glucoglance.egg-info/entry_points.txt +2 -0
  37. glucoglance-0.3.0/src/glucoglance.egg-info/requires.txt +12 -0
  38. glucoglance-0.3.0/src/glucoglance.egg-info/top_level.txt +1 -0
  39. glucoglance-0.3.0/tests/test_about_dialog.py +16 -0
  40. glucoglance-0.3.0/tests/test_app_icon.py +9 -0
  41. glucoglance-0.3.0/tests/test_autostart.py +60 -0
  42. glucoglance-0.3.0/tests/test_client_parsing.py +79 -0
  43. glucoglance-0.3.0/tests/test_icon_renderer.py +37 -0
  44. glucoglance-0.3.0/tests/test_poller.py +116 -0
  45. glucoglance-0.3.0/tests/test_range.py +29 -0
  46. glucoglance-0.3.0/tests/test_settings.py +52 -0
  47. glucoglance-0.3.0/tests/test_trend.py +18 -0
  48. glucoglance-0.3.0/tests/test_units.py +18 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Angel Blanco
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,327 @@
1
+ Metadata-Version: 2.4
2
+ Name: glucoglance
3
+ Version: 0.3.0
4
+ Summary: Ubuntu-only tray widget showing live FreeStyle Libre glucose readings (requires a LibreLinkUp account)
5
+ Author-email: Angel Blanco <toabm@yahoo.es>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/toabm/glucoglance-ubuntu
8
+ Project-URL: Issues, https://github.com/toabm/glucoglance-ubuntu/issues
9
+ Keywords: glucose,freestyle-libre,librelinkup,cgm,tray,gnome,ubuntu
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: X11 Applications :: GTK
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: requests>=2.31
22
+ Requires-Dist: keyring>=24
23
+ Requires-Dist: tomli-w>=1.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
27
+ Requires-Dist: responses>=0.25; extra == "dev"
28
+ Requires-Dist: ruff>=0.6; extra == "dev"
29
+ Provides-Extra: ide
30
+ Requires-Dist: PyGObject-stubs>=2.10; extra == "ide"
31
+ Dynamic: license-file
32
+
33
+ # GlucoGlance for Ubuntu
34
+
35
+ [![CI](https://github.com/toabm/glucoglance-ubuntu/actions/workflows/ci.yml/badge.svg)](https://github.com/toabm/glucoglance-ubuntu/actions/workflows/ci.yml)
36
+
37
+ A tray widget that shows your current glucose reading (from a FreeStyle
38
+ Libre sensor, via LibreLinkUp) in the Ubuntu top bar. Phase 1: just the
39
+ number and trend arrow, refreshed roughly every minute. Threshold alarms are
40
+ planned as a phase 2.
41
+
42
+ > **Compatibility - please check before installing.** GlucoGlance only
43
+ > works with:
44
+ >
45
+ > - **Ubuntu** (developed and tested on Ubuntu 24.04 with GNOME Shell 46).
46
+ > Windows, macOS, and other Linux distributions/desktops are not
47
+ > supported.
48
+ > - **FreeStyle Libre sensors** whose readings are shared through
49
+ > **LibreLinkUp**. Other CGMs (Dexcom, Medtronic, Eversense, etc.) are
50
+ > not supported.
51
+ > - A **LibreLinkUp account** - see Requirements below.
52
+
53
+ ## ⚠️ Disclaimer
54
+
55
+ **GlucoGlance is not a medical device.** It has not been reviewed or
56
+ approved by the FDA, the EMA, or any other regulator.
57
+
58
+ - It is an **unofficial, independent** project. It is not affiliated
59
+ with, endorsed by, or supported by Abbott. FreeStyle Libre and
60
+ LibreLinkUp are trademarks of Abbott, used here only to describe what
61
+ the app works with.
62
+ - Readings may be **delayed, missing or wrong**. **Do not use this app to
63
+ make treatment decisions** (e.g. insulin dosing). Always confirm with
64
+ your sensor's official app or a fingerstick blood glucose meter.
65
+ - It relies on an **undocumented LibreLinkUp API** that Abbott can change
66
+ or block at any time, so the app may stop working without warning.
67
+ - It is provided "as is", without warranty of any kind (see
68
+ [LICENSE](https://github.com/toabm/glucoglance-ubuntu/blob/master/LICENSE)). **Use it at your own risk.**
69
+
70
+ ## Requirements
71
+
72
+ - **Ubuntu** with the default GNOME desktop (see Compatibility above).
73
+ - A **FreeStyle Libre sensor** whose wearer shares their readings to
74
+ **LibreLinkUp** from the official Libre app.
75
+ - A LibreLinkUp **follower** account that has accepted that share: you
76
+ log in here with the follower account's email and password.
77
+
78
+ This app cannot talk to the sensor directly - it only reads what the
79
+ official Libre app has already uploaded to LibreLinkUp.
80
+
81
+ ## Installing on Ubuntu
82
+
83
+ ### Quick install (from PyPI, with pipx)
84
+
85
+ ```bash
86
+ sudo apt install pipx python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1
87
+ pipx ensurepath
88
+ pipx install --system-site-packages glucoglance
89
+ ```
90
+
91
+ `--system-site-packages` is required: it lets the app reuse the GTK
92
+ bindings installed by `apt`, which PyPI doesn't distribute. Open a new
93
+ terminal (so `pipx ensurepath` takes effect) and run `glucoglance` - then
94
+ skip to "Running it for the first time" below. Upgrade later with
95
+ `pipx upgrade glucoglance`.
96
+
97
+ ### From source
98
+
99
+ These steps assume a fresh Ubuntu machine with nothing set up yet - if
100
+ you've already done part of this, skip ahead.
101
+
102
+ **1. Get the code:**
103
+
104
+ ```bash
105
+ git clone https://github.com/toabm/glucoglance-ubuntu.git
106
+ cd glucoglance-ubuntu
107
+ ```
108
+
109
+ **2. Install the system packages** the tray icon needs (these come from
110
+ `apt`, not `pip` - they're GTK/AppIndicator bindings that Python's package
111
+ index doesn't distribute):
112
+
113
+ ```bash
114
+ sudo apt install python3-venv python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1
115
+ ```
116
+
117
+ (If your Ubuntu/distro version ships classic `AppIndicator3` instead of
118
+ the Ayatana fork, install `gir1.2-appindicator3-0.1` instead - the app
119
+ tries Ayatana first and falls back automatically, so either works.)
120
+
121
+ **3. Create a Python virtual environment and install the app into it.**
122
+ `--system-site-packages` is required here - it lets the venv reuse the
123
+ `python3-gi` bindings you just installed with `apt`, instead of trying
124
+ (and failing) to build them from PyPI:
125
+
126
+ ```bash
127
+ python3 -m venv --system-site-packages .venv
128
+ source .venv/bin/activate
129
+ pip install .
130
+ ```
131
+
132
+ That's the whole install. The `glucoglance` command now exists at
133
+ `.venv/bin/glucoglance` (and on your `PATH` while the venv is
134
+ activated).
135
+
136
+ ## Running it for the first time
137
+
138
+ With the venv activated (`source .venv/bin/activate`, if you're in a new
139
+ terminal):
140
+
141
+ ```bash
142
+ glucoglance
143
+ ```
144
+
145
+ A small dialog pops up asking for your LibreLinkUp email and password (see
146
+ Requirements above). Enter them and press OK. The email is saved to
147
+ `~/.config/glucoglance/config.toml`; the password is stored in your
148
+ system keyring (GNOME Keyring), never in plaintext. A number should then
149
+ appear in your top bar within a few seconds - that's your current glucose
150
+ reading.
151
+
152
+ You won't need to repeat this - the credentials are remembered, and (see
153
+ below) the app is set to start automatically at every login by default.
154
+
155
+ Right-click the tray icon for:
156
+ - **Start at login** - checkbox, on by default (see "Running automatically
157
+ at login" below).
158
+ - **Restart** - reloads the app, e.g. after editing `config.toml`.
159
+ - **Quit**.
160
+
161
+ ## Adding it to your Applications menu
162
+
163
+ This is separate from "start at login" below: this makes GlucoGlance
164
+ show up as a proper icon in GNOME's Activities overview and app search
165
+ (so you can launch it manually, or pin it to the Dock), rather than only
166
+ starting silently in the background. Run this once, from the repo
167
+ directory, with the venv already created as above:
168
+
169
+ ```bash
170
+ test -x .venv/bin/glucoglance || { echo "Run this from the glucoglance-ubuntu directory (the venv wasn't found here)"; exit 1; }
171
+ ICON_PATH=$(.venv/bin/python3 -c "from glucoglance.ui.app_icon import app_icon_path; print(app_icon_path())")
172
+ mkdir -p ~/.local/share/applications
173
+ cat > ~/.local/share/applications/glucoglance.desktop <<EOF
174
+ [Desktop Entry]
175
+ Type=Application
176
+ Name=GlucoGlance
177
+ Exec=$(pwd)/.venv/bin/glucoglance
178
+ Icon=$ICON_PATH
179
+ Comment=Shows current blood glucose reading in the top bar
180
+ Terminal=false
181
+ Categories=Utility;
182
+ EOF
183
+ ```
184
+
185
+ It should now appear if you search for "GlucoGlance" in GNOME's
186
+ Activities overview (press the Super/Windows key and start typing). From
187
+ there you can drag it onto the Dock to pin it.
188
+
189
+ (This installs into `~/.local/share/applications`, the standard location
190
+ for manually-installed apps - not `/usr/share/applications`, which is
191
+ where apt/snap-installed apps land since that directory is shared across
192
+ all users on the machine. GNOME scans both identically, so this works the
193
+ same either way; no need to move it.)
194
+
195
+ (The `Exec=` line uses an absolute path to the venv you just created,
196
+ rather than the bare `glucoglance` command, since a graphical launcher
197
+ doesn't necessarily have your venv on its `PATH`. If you ever move the
198
+ `glucoglance-ubuntu` folder, re-run the command above to update it.)
199
+
200
+ ## Configuration
201
+
202
+ Settings live at `~/.config/glucoglance/config.toml` (created with
203
+ defaults on first run). Notable options:
204
+
205
+ ```toml
206
+ interval_seconds = 60 # how often to poll, in seconds
207
+ unit = "mgdl" # "mgdl" or "mmol"
208
+ low_threshold_mgdl = 80 # below this, the icon turns red
209
+ high_threshold_mgdl = 180 # above this, the icon turns yellow (green in between)
210
+ color_low = "#ED4343"
211
+ color_normal = "#4DCC66"
212
+ color_high = "#F5D334"
213
+ autostart_enabled = true # start automatically at login (see below)
214
+ ```
215
+
216
+ Edit the file and restart `glucoglance` for changes to take effect.
217
+
218
+ ## Running automatically at login
219
+
220
+ Enabled by default - on every startup the app installs (or removes) a
221
+ `~/.config/autostart/glucoglance.desktop` entry to match the
222
+ `autostart_enabled` setting. That path is the standard XDG autostart
223
+ location on Ubuntu (and GNOME/most other Linux desktops generally), but
224
+ this project is only built and tested against Ubuntu - see "Installing on
225
+ Ubuntu" above. Toggle it anytime from the tray icon's right-click menu
226
+ ("Start at login"), or by editing `autostart_enabled` in `config.toml` and
227
+ restarting.
228
+
229
+ That autostart entry always waits 6 seconds before actually launching the
230
+ app (only on this path - running from a terminal or the Applications-menu
231
+ icon starts immediately). This isn't configurable, and it's not about
232
+ sensor timing: it exists purely to influence where the icon lands in the
233
+ top bar. GNOME Shell's AppIndicator extension inserts each new tray icon
234
+ at a fixed position rather than appending, so indicators that register
235
+ *later* tend to end up ahead of ones that registered earlier. The delay
236
+ gives your other autostart tray apps a head start, with the goal of
237
+ landing this icon at the left edge of the right-side icon group (i.e.
238
+ immediately after the clock, ahead of things like wifi/bluetooth/volume) -
239
+ though the exact result depends on how many other apps you have and how
240
+ long they take to start, so it may need tuning (edit
241
+ `_STARTUP_DELAY_SECONDS` in `config/autostart.py`) to get exactly there.
242
+
243
+ ## Tests & linting
244
+
245
+ Tests and linting need the extra dev dependencies, which the regular
246
+ install above skips:
247
+
248
+ ```bash
249
+ pip install -e ".[dev]"
250
+ ruff check . # lint
251
+ pytest --cov=glucoglance --cov-report=term-missing # tests + coverage
252
+ ```
253
+
254
+ Overall coverage sits around 49% by design, not by accident: GTK/keyring-
255
+ dependent code (`main.py`, `ui/tray.py`, `ui/credential_prompt.py`,
256
+ `ui/display.py`, `config/credentials.py`) is deliberately manual-only -
257
+ see CLAUDE.md's "Testing philosophy" section.
258
+
259
+ Coverage includes branches (`[tool.coverage.run] branch = true` in
260
+ `pyproject.toml`, applied automatically - no extra flag needed), not
261
+ just lines: a line can show as "covered" while one of its branches
262
+ (e.g. one side of an `if`) was never actually exercised, so branch
263
+ coverage catches real gaps line coverage hides.
264
+
265
+ If your IDE flags `gi.repository` symbols (e.g. `GLib`, `Gtk`) as
266
+ unresolved, that's expected - PyGObject generates those modules
267
+ dynamically from typelibs at runtime, not from real `.py` files, so
268
+ static analyzers can't see them without type stubs. Install those
269
+ separately from `dev` (pip otherwise tries to rebuild the real
270
+ PyGObject/pycairo from source to satisfy this package's declared
271
+ dependency, and fails without cairo/girepository dev headers):
272
+
273
+ ```bash
274
+ pip install --no-deps PyGObject-stubs
275
+ ```
276
+
277
+ ## Continuous integration
278
+
279
+ Every push to `develop`/`master` and every pull request runs `ruff` and
280
+ the full test suite (`.github/workflows/ci.yml`), publishing a test
281
+ report and a coverage summary (including diff/patch coverage on PRs) as
282
+ a PR comment. `master` is protected: it only accepts changes through a
283
+ pull request, and the "Lint & Test" check must pass before merging.
284
+
285
+ For PRs opened from a fork, the test/coverage report job is skipped -
286
+ GitHub gives fork PRs a read-only token, so it couldn't post its comment
287
+ anyway. "Lint & Test" still runs, and its log shows the full results.
288
+
289
+ ## Project layout
290
+
291
+ - `client/` - talks to the LibreLinkUp API.
292
+ - `domain/` - plain data types (readings, trend, unit conversion), no I/O.
293
+ - `poller/` - background polling loop; publishes events on an `EventBus`.
294
+ - `alerts/` - placeholder for phase 2 threshold alarms.
295
+ - `ui/` - the tray display, built behind a small `Display` interface so
296
+ other frontends (e.g. a floating window) can be added later.
297
+ - `config/` - settings (TOML) and credentials (system keyring).
298
+
299
+ ## Why the number is drawn as an icon, not a tray label
300
+
301
+ AppIndicator has a built-in feature for showing text next to the tray icon
302
+ (a "label"). On this project's target environment (GNOME Shell 46 +
303
+ `ubuntu-appindicators`), that feature silently doesn't render anything,
304
+ even though the app publishes it correctly - a Shell-extension bug, not
305
+ something fixable from the app side. Instead, `ui/icon_renderer.py` draws
306
+ the reading as a small bitmap and that image becomes the tray icon itself,
307
+ which does render reliably. If a future Shell/extension update fixes label
308
+ rendering, this could be simplified back to `set_label()`.
309
+
310
+ ## Privacy
311
+
312
+ - Your LibreLinkUp password is stored only in your system keyring
313
+ (GNOME Keyring), never in a plain file. Your email is stored in
314
+ `~/.config/glucoglance/config.toml`.
315
+ - The app talks only to Abbott's LibreLinkUp servers. It has no server of
316
+ its own, no analytics and no telemetry, so your readings and credentials
317
+ never go anywhere else.
318
+ - Tray menu → **Log out** deletes the stored password and email.
319
+
320
+ ## Contributing
321
+
322
+ Bug reports and pull requests are welcome - see
323
+ [CONTRIBUTING.md](https://github.com/toabm/glucoglance-ubuntu/blob/master/CONTRIBUTING.md).
324
+
325
+ ## License
326
+
327
+ MIT - see [LICENSE](https://github.com/toabm/glucoglance-ubuntu/blob/master/LICENSE).
@@ -0,0 +1,295 @@
1
+ # GlucoGlance for Ubuntu
2
+
3
+ [![CI](https://github.com/toabm/glucoglance-ubuntu/actions/workflows/ci.yml/badge.svg)](https://github.com/toabm/glucoglance-ubuntu/actions/workflows/ci.yml)
4
+
5
+ A tray widget that shows your current glucose reading (from a FreeStyle
6
+ Libre sensor, via LibreLinkUp) in the Ubuntu top bar. Phase 1: just the
7
+ number and trend arrow, refreshed roughly every minute. Threshold alarms are
8
+ planned as a phase 2.
9
+
10
+ > **Compatibility - please check before installing.** GlucoGlance only
11
+ > works with:
12
+ >
13
+ > - **Ubuntu** (developed and tested on Ubuntu 24.04 with GNOME Shell 46).
14
+ > Windows, macOS, and other Linux distributions/desktops are not
15
+ > supported.
16
+ > - **FreeStyle Libre sensors** whose readings are shared through
17
+ > **LibreLinkUp**. Other CGMs (Dexcom, Medtronic, Eversense, etc.) are
18
+ > not supported.
19
+ > - A **LibreLinkUp account** - see Requirements below.
20
+
21
+ ## ⚠️ Disclaimer
22
+
23
+ **GlucoGlance is not a medical device.** It has not been reviewed or
24
+ approved by the FDA, the EMA, or any other regulator.
25
+
26
+ - It is an **unofficial, independent** project. It is not affiliated
27
+ with, endorsed by, or supported by Abbott. FreeStyle Libre and
28
+ LibreLinkUp are trademarks of Abbott, used here only to describe what
29
+ the app works with.
30
+ - Readings may be **delayed, missing or wrong**. **Do not use this app to
31
+ make treatment decisions** (e.g. insulin dosing). Always confirm with
32
+ your sensor's official app or a fingerstick blood glucose meter.
33
+ - It relies on an **undocumented LibreLinkUp API** that Abbott can change
34
+ or block at any time, so the app may stop working without warning.
35
+ - It is provided "as is", without warranty of any kind (see
36
+ [LICENSE](https://github.com/toabm/glucoglance-ubuntu/blob/master/LICENSE)). **Use it at your own risk.**
37
+
38
+ ## Requirements
39
+
40
+ - **Ubuntu** with the default GNOME desktop (see Compatibility above).
41
+ - A **FreeStyle Libre sensor** whose wearer shares their readings to
42
+ **LibreLinkUp** from the official Libre app.
43
+ - A LibreLinkUp **follower** account that has accepted that share: you
44
+ log in here with the follower account's email and password.
45
+
46
+ This app cannot talk to the sensor directly - it only reads what the
47
+ official Libre app has already uploaded to LibreLinkUp.
48
+
49
+ ## Installing on Ubuntu
50
+
51
+ ### Quick install (from PyPI, with pipx)
52
+
53
+ ```bash
54
+ sudo apt install pipx python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1
55
+ pipx ensurepath
56
+ pipx install --system-site-packages glucoglance
57
+ ```
58
+
59
+ `--system-site-packages` is required: it lets the app reuse the GTK
60
+ bindings installed by `apt`, which PyPI doesn't distribute. Open a new
61
+ terminal (so `pipx ensurepath` takes effect) and run `glucoglance` - then
62
+ skip to "Running it for the first time" below. Upgrade later with
63
+ `pipx upgrade glucoglance`.
64
+
65
+ ### From source
66
+
67
+ These steps assume a fresh Ubuntu machine with nothing set up yet - if
68
+ you've already done part of this, skip ahead.
69
+
70
+ **1. Get the code:**
71
+
72
+ ```bash
73
+ git clone https://github.com/toabm/glucoglance-ubuntu.git
74
+ cd glucoglance-ubuntu
75
+ ```
76
+
77
+ **2. Install the system packages** the tray icon needs (these come from
78
+ `apt`, not `pip` - they're GTK/AppIndicator bindings that Python's package
79
+ index doesn't distribute):
80
+
81
+ ```bash
82
+ sudo apt install python3-venv python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1
83
+ ```
84
+
85
+ (If your Ubuntu/distro version ships classic `AppIndicator3` instead of
86
+ the Ayatana fork, install `gir1.2-appindicator3-0.1` instead - the app
87
+ tries Ayatana first and falls back automatically, so either works.)
88
+
89
+ **3. Create a Python virtual environment and install the app into it.**
90
+ `--system-site-packages` is required here - it lets the venv reuse the
91
+ `python3-gi` bindings you just installed with `apt`, instead of trying
92
+ (and failing) to build them from PyPI:
93
+
94
+ ```bash
95
+ python3 -m venv --system-site-packages .venv
96
+ source .venv/bin/activate
97
+ pip install .
98
+ ```
99
+
100
+ That's the whole install. The `glucoglance` command now exists at
101
+ `.venv/bin/glucoglance` (and on your `PATH` while the venv is
102
+ activated).
103
+
104
+ ## Running it for the first time
105
+
106
+ With the venv activated (`source .venv/bin/activate`, if you're in a new
107
+ terminal):
108
+
109
+ ```bash
110
+ glucoglance
111
+ ```
112
+
113
+ A small dialog pops up asking for your LibreLinkUp email and password (see
114
+ Requirements above). Enter them and press OK. The email is saved to
115
+ `~/.config/glucoglance/config.toml`; the password is stored in your
116
+ system keyring (GNOME Keyring), never in plaintext. A number should then
117
+ appear in your top bar within a few seconds - that's your current glucose
118
+ reading.
119
+
120
+ You won't need to repeat this - the credentials are remembered, and (see
121
+ below) the app is set to start automatically at every login by default.
122
+
123
+ Right-click the tray icon for:
124
+ - **Start at login** - checkbox, on by default (see "Running automatically
125
+ at login" below).
126
+ - **Restart** - reloads the app, e.g. after editing `config.toml`.
127
+ - **Quit**.
128
+
129
+ ## Adding it to your Applications menu
130
+
131
+ This is separate from "start at login" below: this makes GlucoGlance
132
+ show up as a proper icon in GNOME's Activities overview and app search
133
+ (so you can launch it manually, or pin it to the Dock), rather than only
134
+ starting silently in the background. Run this once, from the repo
135
+ directory, with the venv already created as above:
136
+
137
+ ```bash
138
+ test -x .venv/bin/glucoglance || { echo "Run this from the glucoglance-ubuntu directory (the venv wasn't found here)"; exit 1; }
139
+ ICON_PATH=$(.venv/bin/python3 -c "from glucoglance.ui.app_icon import app_icon_path; print(app_icon_path())")
140
+ mkdir -p ~/.local/share/applications
141
+ cat > ~/.local/share/applications/glucoglance.desktop <<EOF
142
+ [Desktop Entry]
143
+ Type=Application
144
+ Name=GlucoGlance
145
+ Exec=$(pwd)/.venv/bin/glucoglance
146
+ Icon=$ICON_PATH
147
+ Comment=Shows current blood glucose reading in the top bar
148
+ Terminal=false
149
+ Categories=Utility;
150
+ EOF
151
+ ```
152
+
153
+ It should now appear if you search for "GlucoGlance" in GNOME's
154
+ Activities overview (press the Super/Windows key and start typing). From
155
+ there you can drag it onto the Dock to pin it.
156
+
157
+ (This installs into `~/.local/share/applications`, the standard location
158
+ for manually-installed apps - not `/usr/share/applications`, which is
159
+ where apt/snap-installed apps land since that directory is shared across
160
+ all users on the machine. GNOME scans both identically, so this works the
161
+ same either way; no need to move it.)
162
+
163
+ (The `Exec=` line uses an absolute path to the venv you just created,
164
+ rather than the bare `glucoglance` command, since a graphical launcher
165
+ doesn't necessarily have your venv on its `PATH`. If you ever move the
166
+ `glucoglance-ubuntu` folder, re-run the command above to update it.)
167
+
168
+ ## Configuration
169
+
170
+ Settings live at `~/.config/glucoglance/config.toml` (created with
171
+ defaults on first run). Notable options:
172
+
173
+ ```toml
174
+ interval_seconds = 60 # how often to poll, in seconds
175
+ unit = "mgdl" # "mgdl" or "mmol"
176
+ low_threshold_mgdl = 80 # below this, the icon turns red
177
+ high_threshold_mgdl = 180 # above this, the icon turns yellow (green in between)
178
+ color_low = "#ED4343"
179
+ color_normal = "#4DCC66"
180
+ color_high = "#F5D334"
181
+ autostart_enabled = true # start automatically at login (see below)
182
+ ```
183
+
184
+ Edit the file and restart `glucoglance` for changes to take effect.
185
+
186
+ ## Running automatically at login
187
+
188
+ Enabled by default - on every startup the app installs (or removes) a
189
+ `~/.config/autostart/glucoglance.desktop` entry to match the
190
+ `autostart_enabled` setting. That path is the standard XDG autostart
191
+ location on Ubuntu (and GNOME/most other Linux desktops generally), but
192
+ this project is only built and tested against Ubuntu - see "Installing on
193
+ Ubuntu" above. Toggle it anytime from the tray icon's right-click menu
194
+ ("Start at login"), or by editing `autostart_enabled` in `config.toml` and
195
+ restarting.
196
+
197
+ That autostart entry always waits 6 seconds before actually launching the
198
+ app (only on this path - running from a terminal or the Applications-menu
199
+ icon starts immediately). This isn't configurable, and it's not about
200
+ sensor timing: it exists purely to influence where the icon lands in the
201
+ top bar. GNOME Shell's AppIndicator extension inserts each new tray icon
202
+ at a fixed position rather than appending, so indicators that register
203
+ *later* tend to end up ahead of ones that registered earlier. The delay
204
+ gives your other autostart tray apps a head start, with the goal of
205
+ landing this icon at the left edge of the right-side icon group (i.e.
206
+ immediately after the clock, ahead of things like wifi/bluetooth/volume) -
207
+ though the exact result depends on how many other apps you have and how
208
+ long they take to start, so it may need tuning (edit
209
+ `_STARTUP_DELAY_SECONDS` in `config/autostart.py`) to get exactly there.
210
+
211
+ ## Tests & linting
212
+
213
+ Tests and linting need the extra dev dependencies, which the regular
214
+ install above skips:
215
+
216
+ ```bash
217
+ pip install -e ".[dev]"
218
+ ruff check . # lint
219
+ pytest --cov=glucoglance --cov-report=term-missing # tests + coverage
220
+ ```
221
+
222
+ Overall coverage sits around 49% by design, not by accident: GTK/keyring-
223
+ dependent code (`main.py`, `ui/tray.py`, `ui/credential_prompt.py`,
224
+ `ui/display.py`, `config/credentials.py`) is deliberately manual-only -
225
+ see CLAUDE.md's "Testing philosophy" section.
226
+
227
+ Coverage includes branches (`[tool.coverage.run] branch = true` in
228
+ `pyproject.toml`, applied automatically - no extra flag needed), not
229
+ just lines: a line can show as "covered" while one of its branches
230
+ (e.g. one side of an `if`) was never actually exercised, so branch
231
+ coverage catches real gaps line coverage hides.
232
+
233
+ If your IDE flags `gi.repository` symbols (e.g. `GLib`, `Gtk`) as
234
+ unresolved, that's expected - PyGObject generates those modules
235
+ dynamically from typelibs at runtime, not from real `.py` files, so
236
+ static analyzers can't see them without type stubs. Install those
237
+ separately from `dev` (pip otherwise tries to rebuild the real
238
+ PyGObject/pycairo from source to satisfy this package's declared
239
+ dependency, and fails without cairo/girepository dev headers):
240
+
241
+ ```bash
242
+ pip install --no-deps PyGObject-stubs
243
+ ```
244
+
245
+ ## Continuous integration
246
+
247
+ Every push to `develop`/`master` and every pull request runs `ruff` and
248
+ the full test suite (`.github/workflows/ci.yml`), publishing a test
249
+ report and a coverage summary (including diff/patch coverage on PRs) as
250
+ a PR comment. `master` is protected: it only accepts changes through a
251
+ pull request, and the "Lint & Test" check must pass before merging.
252
+
253
+ For PRs opened from a fork, the test/coverage report job is skipped -
254
+ GitHub gives fork PRs a read-only token, so it couldn't post its comment
255
+ anyway. "Lint & Test" still runs, and its log shows the full results.
256
+
257
+ ## Project layout
258
+
259
+ - `client/` - talks to the LibreLinkUp API.
260
+ - `domain/` - plain data types (readings, trend, unit conversion), no I/O.
261
+ - `poller/` - background polling loop; publishes events on an `EventBus`.
262
+ - `alerts/` - placeholder for phase 2 threshold alarms.
263
+ - `ui/` - the tray display, built behind a small `Display` interface so
264
+ other frontends (e.g. a floating window) can be added later.
265
+ - `config/` - settings (TOML) and credentials (system keyring).
266
+
267
+ ## Why the number is drawn as an icon, not a tray label
268
+
269
+ AppIndicator has a built-in feature for showing text next to the tray icon
270
+ (a "label"). On this project's target environment (GNOME Shell 46 +
271
+ `ubuntu-appindicators`), that feature silently doesn't render anything,
272
+ even though the app publishes it correctly - a Shell-extension bug, not
273
+ something fixable from the app side. Instead, `ui/icon_renderer.py` draws
274
+ the reading as a small bitmap and that image becomes the tray icon itself,
275
+ which does render reliably. If a future Shell/extension update fixes label
276
+ rendering, this could be simplified back to `set_label()`.
277
+
278
+ ## Privacy
279
+
280
+ - Your LibreLinkUp password is stored only in your system keyring
281
+ (GNOME Keyring), never in a plain file. Your email is stored in
282
+ `~/.config/glucoglance/config.toml`.
283
+ - The app talks only to Abbott's LibreLinkUp servers. It has no server of
284
+ its own, no analytics and no telemetry, so your readings and credentials
285
+ never go anywhere else.
286
+ - Tray menu → **Log out** deletes the stored password and email.
287
+
288
+ ## Contributing
289
+
290
+ Bug reports and pull requests are welcome - see
291
+ [CONTRIBUTING.md](https://github.com/toabm/glucoglance-ubuntu/blob/master/CONTRIBUTING.md).
292
+
293
+ ## License
294
+
295
+ MIT - see [LICENSE](https://github.com/toabm/glucoglance-ubuntu/blob/master/LICENSE).