Panel
/* $Id$ */
							/* Copyright (c) 2014-2022 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Desktop Panel */
							/* This program is free software: you can redistribute it and/or modify
							 * it under the terms of the GNU General Public License as published by
							 * the Free Software Foundation, version 3 of the License.
							 *
							 * This program is distributed in the hope that it will be useful,
							 * but WITHOUT ANY WARRANTY; without even the implied warranty of
							 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
							 * GNU General Public License for more details.
							 *
							 * You should have received a copy of the GNU General Public License
							 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
							#include <sys/types.h>
							#include <sys/ioctl.h>
							#include <sys/socket.h>
							#include <stdlib.h>
							#ifdef DEBUG
							# include <stdio.h>
							#endif
							#include <string.h>
							#include <errno.h>
							#include <libintl.h>
							#include <net/if.h>
							#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
							# include <ifaddrs.h>
							#endif
							#include <System.h>
							#include "Panel/applet.h"
							#define _(string) gettext(string)
							#define N_(string) string
							/* Network */
							/* private */
							/* types */
							typedef struct _NetworkInterface
							{
								String * name;
								unsigned int flags;
								unsigned long ipackets;
								unsigned long opackets;
								unsigned long ibytes;
								unsigned long obytes;
								GtkWidget * widget;
								gboolean updated;
							} NetworkInterface;
							typedef struct _PanelApplet
							{
								PanelAppletHelper * helper;
								guint source;
								int fd;
								NetworkInterface * interfaces;
								size_t interfaces_cnt;
								/* widgets */
								GtkWidget * widget;
								GtkIconSize iconsize;
								GtkWidget * pr_box;
							#ifdef IFF_LOOPBACK
								GtkWidget * pr_loopback;
							#endif
							#ifdef IFF_UP
								GtkWidget * pr_showdown;
							#endif
							} Network;
							/* prototypes */
							/* plug-in */
							static Network * _network_init(PanelAppletHelper * helper, GtkWidget ** widget);
							static void _network_destroy(Network * network);
							static GtkWidget * _network_settings(Network * network, gboolean apply,
									gboolean reset);
							/* useful */
							static void _network_refresh(Network * network);
							/* callbacks */
							static gboolean _network_on_timeout(gpointer data);
							/* NetworkInterface */
							static int _networkinterface_init(NetworkInterface * ni, char const * name,
									unsigned int flags);
							static void _networkinterface_destroy(NetworkInterface * ni);
							static void _networkinterface_update(NetworkInterface * ni, char const * icon,
									GtkIconSize iconsize, gboolean active, unsigned int flags,
									gboolean updated, char const * tooltip);
							/* public */
							/* variables */
							PanelAppletDefinition applet =
							{
								N_("Network"),
								"network-idle",
								NULL,
								_network_init,
								_network_destroy,
								_network_settings,
								FALSE,
								TRUE
							};
							/* private */
							/* functions */
							/* network_init */
							static Network * _network_init(PanelAppletHelper * helper, GtkWidget ** widget)
							{
								const unsigned int timeout = 500;
								Network * network;
								GtkOrientation orientation;
								if((network = object_new(sizeof(*network))) == NULL)
									return NULL;
								network->helper = helper;
								orientation = panel_window_get_orientation(helper->window);
							#if GTK_CHECK_VERSION(3, 0, 0)
								network->widget = gtk_box_new(orientation, 0);
							#else
								network->widget = (orientation == GTK_ORIENTATION_HORIZONTAL)
									? gtk_hbox_new(TRUE, 0) : gtk_vbox_new(TRUE, 0);
							#endif
								network->iconsize = panel_window_get_icon_size(helper->window);
								network->pr_box = NULL;
								gtk_widget_show(network->widget);
								network->source = g_timeout_add(timeout, _network_on_timeout, network);
								if((network->fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
								{
									error_set("%s: %s: %s", applet.name, "socket", strerror(errno));
									network->helper->error(NULL, error_get(NULL), 1);
								}
								network->interfaces = NULL;
								network->interfaces_cnt = 0;
								*widget = network->widget;
								_network_refresh(network);
								return network;
							}
							/* network_destroy */
							static void _network_destroy(Network * network)
							{
								size_t i;
								for(i = 0; i < network->interfaces_cnt; i++)
									_networkinterface_destroy(&network->interfaces[i]);
								free(network->interfaces);
								if(network->fd >= 0)
									close(network->fd);
								if(network->source != 0)
									g_source_remove(network->source);
								gtk_widget_destroy(network->widget);
								object_delete(network);
							}
							/* useful */
							/* network_refresh */
							static void _refresh_interface(Network * network, char const * name,
									unsigned int flags);
							static int _refresh_interface_add(Network * network, char const * name,
									unsigned int flags);
							static void _refresh_interface_flags(Network * network, NetworkInterface * ni,
									unsigned int flags);
							static void _refresh_purge(Network * network);
							static void _refresh_reset(Network * network);
							static void _network_refresh(Network * network)
							{
								char const * p;
							#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
								struct ifaddrs * ifa;
								struct ifaddrs * ifp;
							#endif
								if((p = network->helper->config_get(network->helper->panel, "network",
												"interface")) != NULL)
								{
									/* FIXME obtain some flags if possible */
							#ifdef IFF_UP
									_refresh_interface(network, p, IFF_UP);
							#else
									_refresh_interface(network, p, 0);
							#endif
									return;
								}
							#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
								if(getifaddrs(&ifa) != 0)
									return;
								_refresh_reset(network);
								for(ifp = ifa; ifp != NULL; ifp = ifp->ifa_next)
								{
									_refresh_interface(network, ifp->ifa_name, ifp->ifa_flags);
									/* XXX avoid repeated entries */
									for(; ifp->ifa_next != NULL && strcmp(ifp->ifa_name,
												ifp->ifa_next->ifa_name) == 0;
											ifp = ifp->ifa_next);
								}
								/* FIXME also remove/disable the interfaces not listed */
								freeifaddrs(ifa);
								_refresh_purge(network);
							#endif
							}
							static void _refresh_interface(Network * network, char const * name,
									unsigned int flags)
							{
								size_t i;
								int res;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, name);
							#endif
								for(i = 0; i < network->interfaces_cnt; i++)
									if(strcmp(network->interfaces[i].name, name) == 0)
										break;
								/* FIXME really implement */
								if(i == network->interfaces_cnt)
									/* XXX assumes network->interfaces[i] will be correct */
									if((res = _refresh_interface_add(network, name, flags)) != 0)
									{
										if(res < 0)
											network->helper->error(NULL, error_get(NULL),
													1);
										return;
									}
								_refresh_interface_flags(network, &network->interfaces[i], flags);
							}
							static int _refresh_interface_add(Network * network, char const * name,
									unsigned int flags)
							{
								NetworkInterface * p;
							#if defined(IFF_LOOPBACK) || defined(IFF_UP)
								char const * q;
							#endif
							#ifdef IFF_LOOPBACK
								if(flags & IFF_LOOPBACK)
								{
									q = network->helper->config_get(network->helper->panel,
											"network", "loopback");
									if(q == NULL || strtol(q, NULL, 10) == 0)
										/* ignore the interface */
										return 1;
								}
							#endif
							#ifdef IFF_UP
								if((flags & IFF_UP) == 0)
								{
									q = network->helper->config_get(network->helper->panel,
											"network", "showdown");
									if(q != NULL && strtol(q, NULL, 10) == 0)
										/* ignore the interface */
										return 1;
								}
							#endif
								if((p = realloc(network->interfaces, sizeof(*p)
												* (network->interfaces_cnt + 1)))
										== NULL)
									return -error_set_code(1, "%s: %s", applet.name,
											strerror(errno));
								network->interfaces = p;
								p = &network->interfaces[network->interfaces_cnt];
								if(_networkinterface_init(p, name, flags) != 0)
									return -1;
								_refresh_interface_flags(network, p, flags);
								gtk_box_pack_start(GTK_BOX(network->widget), p->widget, FALSE, TRUE, 0);
								gtk_widget_show(p->widget);
								network->interfaces_cnt++;
								return 0;
							}
							static void _refresh_interface_delete(Network * network, size_t i)
							{
								NetworkInterface * ni = &network->interfaces[i];
								_networkinterface_destroy(ni);
								network->interfaces_cnt--;
								memmove(&network->interfaces[i], &network->interfaces[i + 1],
										sizeof(*ni) * (network->interfaces_cnt - i));
								/* XXX realloc() network->interfaces to free some memory */
							}
							static void _refresh_interface_flags(Network * network, NetworkInterface * ni,
									unsigned int flags)
							{
								gboolean active = TRUE;
								char const * icon = "network-offline";
							#ifdef SIOCGIFDATA
							# if defined(__NetBSD__)
								struct ifdatareq ifdr;
								struct if_data * pifdr = &ifdr.ifdr_data;
							# else
								struct ifreq ifdr;
								struct if_data ifd;
								struct if_data * pifdr = &ifd;
							# endif
							# if GTK_CHECK_VERSION(2, 12, 0)
								unsigned long ibytes;
								unsigned long obytes;
							# endif
							#endif
								char tooltip[128] = "";
							#ifdef IFF_UP
								if((flags & IFF_UP) != IFF_UP)
									active = FALSE;
								else
							#endif
								{
							#ifdef SIOCGIFDATA
									/* XXX ignore errors */
									memset(&ifdr, 0, sizeof(ifdr));
							# if defined(__NetBSD__)
									strncpy(ifdr.ifdr_name, ni->name, sizeof(ifdr.ifdr_name));
							# else
									strncpy(ifdr.ifr_name, ni->name, sizeof(ifdr.ifr_name));
									ifdr.ifr_data = (caddr_t)pifdr;
							# endif
									if(ioctl(network->fd, SIOCGIFDATA, &ifdr) == -1)
										network->helper->error(NULL, "SIOCGIFDATA", 1);
									else
									{
										if(pifdr->ifi_ipackets > ni->ipackets)
											icon = (pifdr->ifi_opackets > ni->opackets)
												? "network-transmit-receive"
												: "network-receive";
										else if(pifdr->ifi_opackets > ni->opackets)
											icon = "network-transmit";
							# ifdef LINK_STATE_DOWN
										else if(pifdr->ifi_link_state == LINK_STATE_DOWN)
											icon = "network-offline";
							# endif
										else
											icon = "network-idle";
							# if GTK_CHECK_VERSION(2, 12, 0)
										ibytes = (pifdr->ifi_ibytes >= ni->ibytes)
											? pifdr->ifi_ibytes - ni->ibytes
											: ULONG_MAX - ni->ibytes + pifdr->ifi_ibytes;
										obytes = (pifdr->ifi_obytes >= ni->obytes)
											? pifdr->ifi_obytes - ni->obytes
											: ULONG_MAX - ni->obytes + pifdr->ifi_obytes;
										snprintf(tooltip, sizeof(tooltip),
												_("%s\nIn: %lu kB/s\nOut: %lu kB/s"),
												ni->name, ibytes / 512, obytes / 512);
							# endif
										ni->ipackets = pifdr->ifi_ipackets;
										ni->opackets = pifdr->ifi_opackets;
										ni->ibytes = pifdr->ifi_ibytes;
										ni->obytes = pifdr->ifi_obytes;
									}
							#endif
								}
								_networkinterface_update(ni, icon, network->iconsize, active, flags,
										TRUE, (tooltip[0] != '\0') ? tooltip : NULL);
							}
							static void _refresh_purge(Network * network)
							{
								size_t i;
								for(i = 0; i < network->interfaces_cnt;)
									if(network->interfaces[i].updated == FALSE)
										_refresh_interface_delete(network, i);
									else
										i++;
							}
							static void _refresh_reset(Network * network)
							{
								size_t i;
								for(i = 0; i < network->interfaces_cnt; i++)
									network->interfaces[i].updated = FALSE;
							}
							/* network_settings */
							static void _settings_apply(Network * network, PanelAppletHelper * helper);
							static void _settings_reset(Network * network, PanelAppletHelper * helper);
							static GtkWidget * _network_settings(Network * network, gboolean apply,
									gboolean reset)
							{
								PanelAppletHelper * helper = network->helper;
								if(network->pr_box == NULL)
								{
							#if GTK_CHECK_VERSION(3, 0, 0)
									network->pr_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
							#else
									network->pr_box = gtk_vbox_new(TRUE, 4);
							#endif
							#ifdef IFF_LOOPBACK
									network->pr_loopback = gtk_check_button_new_with_label(
											_("Show local interfaces"));
									gtk_box_pack_start(GTK_BOX(network->pr_box),
											network->pr_loopback, FALSE, TRUE, 0);
							#endif
							#ifdef IFF_UP
									network->pr_showdown = gtk_check_button_new_with_label(
											_("Show the interfaces disabled"));
									gtk_box_pack_start(GTK_BOX(network->pr_box),
											network->pr_showdown, FALSE, TRUE, 0);
							#endif
									gtk_widget_show_all(network->pr_box);
									reset = TRUE;
								}
								if(reset == TRUE)
									_settings_reset(network, helper);
								if(apply == TRUE)
									_settings_apply(network, helper);
								return network->pr_box;
							}
							static void _settings_apply(Network * network, PanelAppletHelper * helper)
							{
							#if defined(IFF_LOOPBACK) || defined(IFF_UP)
								gboolean active;
							#endif
							#ifdef IFF_LOOPBACK
								active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
											network->pr_loopback));
								helper->config_set(helper->panel, "network", "loopback",
										active ? "1" : "0");
							#endif
							#ifdef IFF_UP
								active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
											network->pr_showdown));
								helper->config_set(helper->panel, "network", "showdown",
										active ? "1" : "0");
							#endif
								_network_refresh(network);
							}
							static void _settings_reset(Network * network, PanelAppletHelper * helper)
							{
							#ifndef EMBEDDED
							# ifdef IFF_LOOPBACK
								gboolean loopback = TRUE;
							# endif
							# ifdef IFF_UP
								gboolean showdown = TRUE;
							# endif
							#else
							# ifdef IFF_LOOPBACK
								gboolean loopback = FALSE;
							# endif
							# ifdef IFF_UP
								gboolean showdown = FALSE;
							# endif
							#endif
							#if defined(IFF_LOOPBACK) || defined(IFF_UP)
								char const * p;
							#endif
							#ifdef IFF_LOOPBACK
								if((p = helper->config_get(helper->panel, "network", "loopback"))
										!= NULL)
									loopback = strtol(p, NULL, 10) ? TRUE : FALSE;
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(network->pr_loopback),
										loopback);
							#endif
							#ifdef IFF_UP
								if((p = helper->config_get(helper->panel, "network", "showdown"))
										!= NULL)
									showdown = strtol(p, NULL, 10) ? TRUE : FALSE;
								gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(network->pr_showdown),
										showdown);
							#endif
							}
							/* callbacks */
							/* network_on_timeout */
							static gboolean _network_on_timeout(gpointer data)
							{
								const unsigned int timeout = 500;
								Network * network = data;
								_network_refresh(network);
								network->source = g_timeout_add(timeout, _network_on_timeout, network);
								return FALSE;
							}
							/* NetworkInterface */
							/* networkinterface_init */
							static int _networkinterface_init(NetworkInterface * ni, char const * name,
									unsigned int flags)
							{
								if((ni->name = string_new(name)) == NULL)
									return -1;
								ni->flags = flags;
								ni->ipackets = 0;
								ni->opackets = 0;
								ni->ibytes = 0;
								ni->obytes = 0;
								ni->widget = gtk_image_new();
							#if GTK_CHECK_VERSION(2, 12, 0)
								gtk_widget_set_tooltip_text(ni->widget, name);
							#endif
								ni->updated = FALSE;
								return 0;
							}
							/* networkinterface_destroy */
							static void _networkinterface_destroy(NetworkInterface * ni)
							{
								string_delete(ni->name);
								gtk_widget_destroy(ni->widget);
							}
							/* networkinterface_update */
							static void _networkinterface_update(NetworkInterface * ni, char const * icon,
									GtkIconSize iconsize, gboolean active, unsigned int flags,
									gboolean updated, char const * tooltip)
							{
								gtk_image_set_from_icon_name(GTK_IMAGE(ni->widget), icon, iconsize);
							#ifdef EMBEDDED
								if(active)
									gtk_widget_show(ni->widget);
								else
									gtk_widget_hide(ni->widget);
							#else
								gtk_widget_set_sensitive(ni->widget, active);
							#endif
							#if GTK_CHECK_VERSION(2, 12, 0)
								if(tooltip != NULL)
									gtk_widget_set_tooltip_text(ni->widget, tooltip);
							#endif
								ni->flags = flags;
								ni->updated = updated;
							}
							