Skip to content

Commit 8c9e890

Browse files
committed
breeze: add humidity, touch support, and burn-in prevention
Add relative humidity from Open-Meteo API, displayed on the temperature line as "5°C 💧 52%". Fix touch input on RPi 7" touchscreen by handling GDK_TOUCH_MASK events in addition to button-press. Fix off-center rendering on non-1024x600 displays (e.g., RPi 800x480) by updating animation dimensions from the drawing area's size-allocate signal instead of using hardcoded values. Add slow circular drift (15px radius, 5min cycle) of the text overlay to prevent screen burn-in on kiosk displays. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent 9357fe5 commit 8c9e890

3 files changed

Lines changed: 74 additions & 14 deletions

File tree

breeze/breeze.c

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ typedef struct {
3434
GtkWidget *wind_label;
3535
GtkWidget *sun_label;
3636
GtkWidget *web_view;
37+
GtkWidget *overlay_vbox;
3738

3839
/* State */
3940
WeatherData weather;
4041
AnimState anim;
4142
gboolean fullscreen;
43+
double drift_time;
4244

4345
/* Configuration */
4446
double latitude;
@@ -114,8 +116,9 @@ static void update_weather_labels(void)
114116
return;
115117
}
116118

117-
char temp_buf[32];
118-
snprintf(temp_buf, sizeof(temp_buf), "%.0f\u00B0C", app.weather.temperature);
119+
char temp_buf[64];
120+
snprintf(temp_buf, sizeof(temp_buf), "%.0f\u00B0C \U0001F4A7 %d%%",
121+
app.weather.temperature, app.weather.humidity);
119122
gtk_label_set_text(GTK_LABEL(app.temp_label), temp_buf);
120123

121124
gtk_label_set_text(GTK_LABEL(app.desc_label),
@@ -150,6 +153,17 @@ static gboolean on_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
150153
return FALSE;
151154
}
152155

156+
static void on_drawing_area_size_allocate(GtkWidget *widget,
157+
GdkRectangle *allocation,
158+
gpointer data)
159+
{
160+
(void)widget;
161+
(void)data;
162+
163+
app.anim.width = allocation->width;
164+
app.anim.height = allocation->height;
165+
}
166+
153167
/* ------------------------------------------------------------------ */
154168
/* Timers */
155169
/* ------------------------------------------------------------------ */
@@ -168,6 +182,22 @@ static gboolean on_clock_tick(gpointer data)
168182
{
169183
(void)data;
170184
update_clock_label();
185+
186+
/* Slow circular drift of text overlay to prevent screen burn-in.
187+
* Full cycle ~5 minutes, radius ~15 pixels -- barely noticeable.
188+
* Use opposing margins so the total stays constant and GTK
189+
* never sees a negative value or an out-of-bounds allocation. */
190+
app.drift_time += 1.0;
191+
double period = 300.0; /* seconds per full circle */
192+
double radius = 15.0; /* pixels */
193+
int dx = (int)(sin(app.drift_time * 2.0 * M_PI / period) * radius);
194+
int dy = (int)(cos(app.drift_time * 2.0 * M_PI / period) * radius);
195+
196+
gtk_widget_set_margin_start(app.overlay_vbox, (int)radius + dx);
197+
gtk_widget_set_margin_end(app.overlay_vbox, (int)radius - dx);
198+
gtk_widget_set_margin_top(app.overlay_vbox, (int)radius + dy);
199+
gtk_widget_set_margin_bottom(app.overlay_vbox, (int)radius - dy);
200+
171201
return G_SOURCE_CONTINUE;
172202
}
173203

@@ -209,15 +239,10 @@ static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event,
209239
return FALSE;
210240
}
211241

212-
static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
213-
gpointer data)
242+
static void toggle_web_view(void)
214243
{
215-
(void)widget;
216-
(void)event;
217-
(void)data;
218-
219244
if (!app.web_url || !app.web_url[0])
220-
return FALSE;
245+
return;
221246

222247
const gchar *current = gtk_stack_get_visible_child_name(GTK_STACK(app.stack));
223248

@@ -228,7 +253,7 @@ static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
228253
app.webview_timeout = 0;
229254
}
230255
gtk_stack_set_visible_child_name(GTK_STACK(app.stack), "weather");
231-
return TRUE;
256+
return;
232257
}
233258

234259
/* Switch to web view */
@@ -238,6 +263,27 @@ static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
238263
if (app.webview_timeout)
239264
g_source_remove(app.webview_timeout);
240265
app.webview_timeout = g_timeout_add_seconds(30, on_webview_timeout, NULL);
266+
}
267+
268+
static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
269+
gpointer data)
270+
{
271+
(void)widget;
272+
(void)event;
273+
(void)data;
274+
275+
toggle_web_view();
276+
return TRUE;
277+
}
278+
279+
static gboolean on_touch_event(GtkWidget *widget, GdkEventTouch *event,
280+
gpointer data)
281+
{
282+
(void)widget;
283+
(void)data;
284+
285+
if (event->type == GDK_TOUCH_END)
286+
toggle_web_view();
241287

242288
return TRUE;
243289
}
@@ -251,6 +297,8 @@ static GtkWidget *create_weather_view(void)
251297
/* Drawing area as the background */
252298
app.drawing_area = gtk_drawing_area_new();
253299
g_signal_connect(app.drawing_area, "draw", G_CALLBACK(on_draw), NULL);
300+
g_signal_connect(app.drawing_area, "size-allocate",
301+
G_CALLBACK(on_drawing_area_size_allocate), NULL);
254302

255303
/* Overlay labels */
256304
app.time_label = gtk_label_new("--:--");
@@ -283,6 +331,7 @@ static GtkWidget *create_weather_view(void)
283331
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
284332
gtk_widget_set_halign(vbox, GTK_ALIGN_CENTER);
285333
gtk_widget_set_valign(vbox, GTK_ALIGN_CENTER);
334+
app.overlay_vbox = vbox;
286335
gtk_box_pack_start(GTK_BOX(vbox), app.time_label, FALSE, FALSE, 0);
287336
gtk_box_pack_start(GTK_BOX(vbox), app.temp_label, FALSE, FALSE, 0);
288337
gtk_box_pack_start(GTK_BOX(vbox), app.desc_label, FALSE, FALSE, 0);
@@ -418,9 +467,13 @@ int main(int argc, char *argv[])
418467
gtk_window_fullscreen(GTK_WINDOW(app.window));
419468

420469
/* Enable input events on the window */
421-
gtk_widget_add_events(app.window, GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK);
470+
gtk_widget_add_events(app.window,
471+
GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK |
472+
GDK_TOUCH_MASK);
422473
g_signal_connect(app.window, "button-press-event",
423474
G_CALLBACK(on_button_press), NULL);
475+
g_signal_connect(app.window, "touch-event",
476+
G_CALLBACK(on_touch_event), NULL);
424477
g_signal_connect(app.window, "key-press-event",
425478
G_CALLBACK(on_key_press), NULL);
426479

@@ -439,8 +492,8 @@ int main(int argc, char *argv[])
439492

440493
gtk_container_add(GTK_CONTAINER(app.window), app.stack);
441494

442-
/* Initialize animation */
443-
anim_init(&app.anim, 1024, 600);
495+
/* Initialize animation -- actual size comes from size-allocate */
496+
anim_init(&app.anim, 1024, 600); /* defaults, overridden on realize */
444497

445498
/* Initial weather fetch */
446499
app.weather = weather_fetch(app.latitude, app.longitude);

breeze/weather.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ WeatherData weather_fetch(double latitude, double longitude)
175175
"https://api.open-meteo.com/v1/forecast?"
176176
"latitude=%.4f&longitude=%.4f"
177177
"&current_weather=true"
178-
"&hourly=cloudcover,precipitation",
178+
"&hourly=cloudcover,precipitation,relative_humidity_2m",
179179
latitude, longitude);
180180

181181
session = soup_session_new();
@@ -251,6 +251,12 @@ WeatherData weather_fetch(double latitude, double longitude)
251251
cJSON *pr = cJSON_GetArrayItem(pr_arr, current_hour);
252252
if (pr) data.precipitation = pr->valuedouble;
253253
}
254+
255+
cJSON *rh_arr = cJSON_GetObjectItem(hourly, "relative_humidity_2m");
256+
if (rh_arr && cJSON_GetArraySize(rh_arr) > current_hour) {
257+
cJSON *rh = cJSON_GetArrayItem(rh_arr, current_hour);
258+
if (rh) data.humidity = rh->valueint;
259+
}
254260
}
255261

256262
cJSON_Delete(root);

breeze/weather.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ typedef struct {
2222
WeatherType type;
2323
double intensity; /* 0.0 - 1.0 */
2424
int cloudcover; /* 0 - 100 percent */
25+
int humidity; /* 0 - 100 percent (relative) */
2526
double precipitation; /* mm */
2627
bool is_day;
2728
double sunrise; /* hours (e.g. 6.5 = 06:30) */

0 commit comments

Comments
 (0)