291 lines
6.9 KiB
C
291 lines
6.9 KiB
C
/*
|
|
* SpaceAPI based tray applet for BSD* / Linux / UNIX etc. using GTK2
|
|
*
|
|
* Author: hanez <you@hanez.org>
|
|
* Website: https://git.xw3.org/hanez/spacetray
|
|
*
|
|
* Licensed under the terms of the MIT License.
|
|
*/
|
|
|
|
#include <curl/curl.h>
|
|
#include <gtk/gtk.h>
|
|
#include <json.h>
|
|
|
|
#if LIBNOTIFY
|
|
#include <libnotify/notify.h>
|
|
#endif
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
|
|
#include "themes/lock_new.h"
|
|
|
|
struct string {
|
|
char *ptr;
|
|
size_t len;
|
|
};
|
|
|
|
static char *agent = "Spacetray/0.42";
|
|
static char *name = "SpaceAPI widget for UNIX";
|
|
static char *statusurl = NULL;
|
|
|
|
// The delay for polling the dooris service. Adjust this before compiling
|
|
static int delay = 300; // ms. aka 15 minutes
|
|
|
|
bool door_open = false;
|
|
bool old_door_open = false;
|
|
int door_last_change = 0;
|
|
char space_name[128] = "Unknown Space";
|
|
|
|
void on_quit_menu_item_activated(GtkMenuItem *item, gpointer data) {
|
|
gtk_main_quit();
|
|
}
|
|
|
|
void on_popup_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer data) {
|
|
GtkWidget *menu, *quit_item;
|
|
|
|
menu = gtk_menu_new();
|
|
quit_item = gtk_menu_item_new_with_label("Quit");
|
|
|
|
g_signal_connect(G_OBJECT(quit_item), "activate",
|
|
G_CALLBACK(on_quit_menu_item_activated), NULL);
|
|
|
|
gtk_menu_shell_append(GTK_MENU_SHELL(menu), quit_item);
|
|
gtk_widget_show_all(menu);
|
|
|
|
gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
|
|
gtk_status_icon_position_menu, status_icon,
|
|
button, activate_time);
|
|
}
|
|
|
|
void do_it();
|
|
void invoke_notification();
|
|
|
|
gboolean do_it_wrapper(gpointer data) {
|
|
do_it();
|
|
return FALSE;
|
|
}
|
|
|
|
void *status_update_thread(void *arg) {
|
|
while (1) {
|
|
sleep(delay);
|
|
g_idle_add(do_it_wrapper, NULL);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
GtkStatusIcon *tray_icon;
|
|
|
|
void init_string(struct string *s) {
|
|
s->len = 0;
|
|
s->ptr = malloc(s->len+1);
|
|
if (s->ptr == NULL) {
|
|
fprintf(stderr, "malloc() failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
s->ptr[0] = '\0';
|
|
}
|
|
|
|
size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *s) {
|
|
size_t new_len = s->len + size*nmemb;
|
|
s->ptr = realloc(s->ptr, new_len+1);
|
|
if (s->ptr == NULL) {
|
|
fprintf(stderr, "realloc() failed\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memcpy(s->ptr+s->len, ptr, size*nmemb);
|
|
s->ptr[new_len] = '\0';
|
|
s->len = new_len;
|
|
|
|
return size*nmemb;
|
|
}
|
|
|
|
void get_bouncer_data() {
|
|
CURL *curl;
|
|
CURLcode res;
|
|
|
|
struct json_object *response_json_object;
|
|
struct json_object *door_json_object;
|
|
struct json_object *leaf_json_object;
|
|
|
|
bool door_status;
|
|
struct string s;
|
|
|
|
printf("Called get_bouncer_data()\n");
|
|
|
|
old_door_open = door_open;
|
|
|
|
init_string(&s);
|
|
|
|
curl = curl_easy_init();
|
|
if(!curl) {
|
|
fprintf(stderr, "curl_easy_init() failed.\n");
|
|
return;
|
|
}
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, statusurl);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, agent);
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
if(res != CURLE_OK) {
|
|
fprintf(stderr, "curl_easy_perform() failed: %s\n",
|
|
curl_easy_strerror(res));
|
|
return;
|
|
}
|
|
|
|
response_json_object = json_tokener_parse(s.ptr);
|
|
free(s.ptr);
|
|
|
|
// door objects
|
|
door_json_object = json_object_object_get(response_json_object, "state");
|
|
|
|
leaf_json_object = json_object_object_get(door_json_object, "open");
|
|
door_status = json_object_get_int(leaf_json_object);
|
|
//door_status = true;
|
|
leaf_json_object = json_object_object_get(door_json_object, "lastchange");
|
|
door_last_change = json_object_get_int(leaf_json_object);
|
|
|
|
leaf_json_object = json_object_object_get(response_json_object, "space");
|
|
if (leaf_json_object != NULL) {
|
|
const char *s_name = json_object_get_string(leaf_json_object);
|
|
if (s_name != NULL) {
|
|
strncpy(space_name, s_name, sizeof(space_name) - 1);
|
|
space_name[sizeof(space_name) - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
// free response object
|
|
json_object_put(response_json_object);
|
|
|
|
printf("Door status: = %d\n", door_status);
|
|
printf("Door last change: = %d\n", door_last_change);
|
|
|
|
if (door_status == true) {
|
|
door_open = true;
|
|
} else {
|
|
door_open = false;
|
|
}
|
|
}
|
|
|
|
GdkPixbuf *icon_open = NULL;
|
|
GdkPixbuf *icon_closed = NULL;
|
|
|
|
static GdkPixbuf *pixbuf_new_from_data(const guint8 *data) {
|
|
return gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, 8, 52, 48, 208, NULL, NULL);
|
|
}
|
|
|
|
void update_tooltip() {
|
|
char door[256];
|
|
char date_str[64];
|
|
time_t last_change_time = (time_t)door_last_change;
|
|
struct tm *tm_info = localtime(&last_change_time);
|
|
|
|
strftime(date_str, sizeof(date_str), "%Y-%m-%d %H:%M:%S", tm_info);
|
|
|
|
if (door_open == true) {
|
|
snprintf(door, sizeof(door), "%s: Door open since %s", space_name, date_str);
|
|
} else {
|
|
snprintf(door, sizeof(door), "%s: Door closed since %s", space_name, date_str);
|
|
}
|
|
|
|
gtk_status_icon_set_tooltip(tray_icon, door);
|
|
}
|
|
|
|
void invoke_notification() {
|
|
char door[256];
|
|
char date_str[64];
|
|
time_t last_change_time = (time_t)door_last_change;
|
|
struct tm *tm_info = localtime(&last_change_time);
|
|
|
|
strftime(date_str, sizeof(date_str), "%Y-%m-%d %H:%M:%S", tm_info);
|
|
|
|
if (door_open == true) {
|
|
snprintf(door, sizeof(door), "%s: Door open since %s", space_name, date_str);
|
|
} else {
|
|
snprintf(door, sizeof(door), "%s: Door closed since %s", space_name, date_str);
|
|
}
|
|
|
|
#if LIBNOTIFY
|
|
NotifyNotification *n;
|
|
|
|
n = notify_notification_new(name, door, NULL);
|
|
|
|
if (door_open == true) {
|
|
notify_notification_set_icon_from_pixbuf(n, icon_open);
|
|
} else {
|
|
notify_notification_set_icon_from_pixbuf(n, icon_closed);
|
|
}
|
|
notify_notification_show(n, NULL);
|
|
g_object_unref(G_OBJECT(n));
|
|
#endif
|
|
}
|
|
|
|
void set_status() {
|
|
if (door_open == true) {
|
|
printf("Set to status: open\n");
|
|
gtk_status_icon_set_from_pixbuf(tray_icon, icon_open);
|
|
} else {
|
|
printf("Set to status: closed\n");
|
|
gtk_status_icon_set_from_pixbuf(tray_icon, icon_closed);
|
|
}
|
|
}
|
|
|
|
void do_it() {
|
|
static bool first_run = true;
|
|
get_bouncer_data();
|
|
set_status();
|
|
update_tooltip();
|
|
|
|
if (!first_run && door_open != old_door_open) {
|
|
invoke_notification();
|
|
}
|
|
first_run = false;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
pthread_t thread_id;
|
|
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s <statusurl>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
statusurl = argv[1];
|
|
|
|
gtk_init(&argc, &argv);
|
|
|
|
icon_open = pixbuf_new_from_data(icon_open_data);
|
|
icon_closed = pixbuf_new_from_data(icon_closed_data);
|
|
|
|
#if LIBNOTIFY
|
|
notify_init(name);
|
|
#endif
|
|
|
|
tray_icon = gtk_status_icon_new();
|
|
g_signal_connect(G_OBJECT(tray_icon), "activate",
|
|
G_CALLBACK(do_it), NULL);
|
|
g_signal_connect(G_OBJECT(tray_icon), "popup-menu",
|
|
G_CALLBACK(on_popup_menu), NULL);
|
|
|
|
gtk_status_icon_set_visible(tray_icon, true);
|
|
|
|
do_it();
|
|
|
|
if (pthread_create(&thread_id, NULL, status_update_thread, NULL) != 0) {
|
|
fprintf(stderr, "Error creating thread\n");
|
|
return 1;
|
|
}
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|