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;
}