Skip to content

Commit eafd33c

Browse files
committed
boring: add --location by country,city
Also, add missing usage() text and catch Esc to exit the program when run from a Linux PC. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent 51c1744 commit eafd33c

3 files changed

Lines changed: 185 additions & 3 deletions

File tree

boring/boring.c

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ static gboolean on_webview_timeout(gpointer data)
185185
return G_SOURCE_REMOVE;
186186
}
187187

188+
static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event,
189+
gpointer data)
190+
{
191+
(void)widget;
192+
(void)data;
193+
194+
if (event->keyval == GDK_KEY_Escape) {
195+
gtk_main_quit();
196+
return TRUE;
197+
}
198+
return FALSE;
199+
}
200+
188201
static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
189202
gpointer data)
190203
{
@@ -277,24 +290,54 @@ static GtkWidget *create_web_view(void)
277290
/* Argument parsing */
278291
/* ------------------------------------------------------------------ */
279292

293+
static void usage(const char *name)
294+
{
295+
printf("Usage: %s [OPTIONS]\n"
296+
"\n"
297+
"Options:\n"
298+
" -f, --fullscreen Run in fullscreen mode\n"
299+
" -l, --location LOCATION City or Country,City (e.g., \"Stockholm\"\n"
300+
" or \"Sweden,Stockholm\"), geocoded via Open-Meteo\n"
301+
" --lat LATITUDE Latitude for weather (default: 59.3293)\n"
302+
" --lon LONGITUDE Longitude for weather (default: 18.0686)\n"
303+
" --url URL Web page URL shown on touch/click\n"
304+
" -h, --help Show this help message\n"
305+
"\n"
306+
"Environment variables LATITUDE, LONGITUDE, LOCATION, and WEB_URL\n"
307+
"are used as fallbacks when options are not given.\n"
308+
"\n"
309+
"Press Escape to exit.\n", name);
310+
}
311+
280312
static void parse_args(int argc, char *argv[])
281313
{
282314
/* Defaults from environment, then fallback */
283315
const char *env;
316+
const char *location = NULL;
284317

285318
env = getenv("LATITUDE");
286319
app.latitude = env ? atof(env) : 59.3293; /* Stockholm */
287320

288321
env = getenv("LONGITUDE");
289322
app.longitude = env ? atof(env) : 18.0686;
290323

324+
env = getenv("LOCATION");
325+
if (env) location = env;
326+
291327
env = getenv("WEB_URL");
292328
app.web_url = env ? strdup(env) : NULL;
293329

294330
app.fullscreen = FALSE;
295331

296332
for (int i = 1; i < argc; i++) {
297-
if ((strcmp(argv[i], "--lat") == 0) && i + 1 < argc) {
333+
if (strcmp(argv[i], "-h") == 0 ||
334+
strcmp(argv[i], "--help") == 0) {
335+
usage(argv[0]);
336+
exit(0);
337+
} else if ((strcmp(argv[i], "-l") == 0 ||
338+
strcmp(argv[i], "--location") == 0) && i + 1 < argc) {
339+
location = argv[++i];
340+
} else if ((strcmp(argv[i], "--lat") == 0) && i + 1 < argc) {
298341
app.latitude = atof(argv[++i]);
299342
} else if ((strcmp(argv[i], "--lon") == 0) && i + 1 < argc) {
300343
app.longitude = atof(argv[++i]);
@@ -306,6 +349,20 @@ static void parse_args(int argc, char *argv[])
306349
app.fullscreen = TRUE;
307350
}
308351
}
352+
353+
if (location) {
354+
double lat, lon;
355+
356+
if (weather_geocode(location, &lat, &lon)) {
357+
app.latitude = lat;
358+
app.longitude = lon;
359+
fprintf(stderr, "Location \"%s\" -> %.4f, %.4f\n",
360+
location, lat, lon);
361+
} else {
362+
fprintf(stderr, "Could not geocode \"%s\", using default coordinates\n",
363+
location);
364+
}
365+
}
309366
}
310367

311368
/* ------------------------------------------------------------------ */
@@ -343,10 +400,12 @@ int main(int argc, char *argv[])
343400
if (app.fullscreen)
344401
gtk_window_fullscreen(GTK_WINDOW(app.window));
345402

346-
/* Enable button press events on the window */
347-
gtk_widget_add_events(app.window, GDK_BUTTON_PRESS_MASK);
403+
/* Enable input events on the window */
404+
gtk_widget_add_events(app.window, GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK);
348405
g_signal_connect(app.window, "button-press-event",
349406
G_CALLBACK(on_button_press), NULL);
407+
g_signal_connect(app.window, "key-press-event",
408+
G_CALLBACK(on_key_press), NULL);
350409

351410
/* Stack with two children */
352411
app.stack = gtk_stack_new();

boring/weather.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,119 @@ WeatherData weather_fetch(double latitude, double longitude)
234234

235235
return data;
236236
}
237+
238+
bool weather_geocode(const char *location, double *latitude, double *longitude)
239+
{
240+
SoupSession *session;
241+
SoupMessage *msg;
242+
char url[512];
243+
char *city = NULL;
244+
char *country = NULL;
245+
246+
/* Parse "Country,City" or just "City" */
247+
const char *comma = strchr(location, ',');
248+
if (comma) {
249+
country = strndup(location, comma - location);
250+
city = strdup(comma + 1);
251+
/* Trim leading spaces from city */
252+
while (*city == ' ')
253+
memmove(city, city + 1, strlen(city));
254+
} else {
255+
city = strdup(location);
256+
}
257+
258+
/* URL-encode spaces as + for the query */
259+
for (char *p = city; *p; p++)
260+
if (*p == ' ') *p = '+';
261+
if (country)
262+
for (char *p = country; *p; p++)
263+
if (*p == ' ') *p = '+';
264+
265+
snprintf(url, sizeof(url),
266+
"https://geocoding-api.open-meteo.com/v1/search?name=%s&count=5",
267+
city);
268+
269+
session = soup_session_new();
270+
msg = soup_message_new("GET", url);
271+
if (!msg) {
272+
g_object_unref(session);
273+
free(city);
274+
free(country);
275+
return false;
276+
}
277+
278+
GError *error = NULL;
279+
GInputStream *stream = soup_session_send(session, msg, NULL, &error);
280+
if (!stream || soup_message_get_status(msg) != 200) {
281+
g_clear_error(&error);
282+
g_clear_object(&stream);
283+
g_object_unref(msg);
284+
g_object_unref(session);
285+
free(city);
286+
free(country);
287+
return false;
288+
}
289+
290+
GByteArray *buf = g_byte_array_new();
291+
guint8 chunk[4096];
292+
gssize n;
293+
294+
while ((n = g_input_stream_read(stream, chunk, sizeof(chunk), NULL, NULL)) > 0)
295+
g_byte_array_append(buf, chunk, n);
296+
g_object_unref(stream);
297+
298+
g_byte_array_append(buf, (const guint8 *)"\0", 1);
299+
cJSON *root = cJSON_Parse((const char *)buf->data);
300+
g_byte_array_free(buf, TRUE);
301+
302+
bool found = false;
303+
if (root) {
304+
cJSON *results = cJSON_GetObjectItem(root, "results");
305+
int count = results ? cJSON_GetArraySize(results) : 0;
306+
307+
/* If country filter given, prefer a match; otherwise take first */
308+
for (int i = 0; i < count && !found; i++) {
309+
cJSON *item = cJSON_GetArrayItem(results, i);
310+
if (country) {
311+
cJSON *ctry = cJSON_GetObjectItem(item, "country");
312+
if (!ctry || !ctry->valuestring)
313+
continue;
314+
if (strcasecmp(ctry->valuestring, country) != 0) {
315+
/* Also try country_code (e.g. "SE") */
316+
cJSON *code = cJSON_GetObjectItem(item, "country_code");
317+
if (!code || !code->valuestring ||
318+
strcasecmp(code->valuestring, country) != 0)
319+
continue;
320+
}
321+
}
322+
cJSON *lat = cJSON_GetObjectItem(item, "latitude");
323+
cJSON *lon = cJSON_GetObjectItem(item, "longitude");
324+
if (lat && lon) {
325+
*latitude = lat->valuedouble;
326+
*longitude = lon->valuedouble;
327+
found = true;
328+
}
329+
}
330+
331+
/* Fall back to first result if country filter didn't match */
332+
if (!found && count > 0 && !country) {
333+
cJSON *item = cJSON_GetArrayItem(results, 0);
334+
cJSON *lat = cJSON_GetObjectItem(item, "latitude");
335+
cJSON *lon = cJSON_GetObjectItem(item, "longitude");
336+
if (lat && lon) {
337+
*latitude = lat->valuedouble;
338+
*longitude = lon->valuedouble;
339+
found = true;
340+
}
341+
}
342+
343+
cJSON_Delete(root);
344+
}
345+
346+
g_object_unref(msg);
347+
g_object_unref(session);
348+
free(city);
349+
free(country);
350+
351+
return found;
352+
}

boring/weather.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,11 @@ const char *weather_description(WeatherType type);
4040
/* Format sunrise/sunset hours as HH:MM string into buf (>= 6 bytes) */
4141
void weather_format_time(double hours, char *buf, int bufsize);
4242

43+
/*
44+
* Geocode a location string to lat/lon using Open-Meteo's geocoding API.
45+
* Accepts "City" or "Country,City" (e.g., "Stockholm" or "Sweden,Stockholm").
46+
* Returns true on success and fills in *latitude and *longitude.
47+
*/
48+
bool weather_geocode(const char *location, double *latitude, double *longitude);
49+
4350
#endif /* WEATHER_H */

0 commit comments

Comments
 (0)