#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>
#include "xapp-status-icon.h"
#include "xapp-statusicon-interface.h"
#include "xapp-enums.h"
#define FDO_DBUS_NAME "org.freedesktop.DBus"
#define FDO_DBUS_PATH "/org/freedesktop/DBus"
#define ICON_PATH "/org/x/StatusIcon"
#define ICON_NAME "org.x.StatusIcon"
#define STATUS_ICON_MONITOR_MATCH "org.x.StatusIconMonitor"
#define MAX_NAME_FAILS 3
static gint unique_id = 0;
enum
{
BUTTON_PRESS,
BUTTON_RELEASE,
ACTIVATE,
STATE_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = {0, };
enum
{
PROP_0,
PROP_PRIMARY_MENU,
PROP_SECONDARY_MENU,
N_PROPERTIES
};
/**
* SECTION:xapp-status-icon
* @Short_description: Broadcasts status information over DBUS
* @Title: XAppStatusIcon
*
* The XAppStatusIcon allows applications to share status info
* about themselves. It replaces the obsolete and very similar
* Gtk.StatusIcon widget.
*
* If used in an environment where no applet is handling XAppStatusIcons,
* the XAppStatusIcon delegates its calls to a Gtk.StatusIcon.
*/
typedef struct
{
XAppStatusIconInterface *skeleton;
GDBusConnection *connection;
GCancellable *cancellable;
GtkStatusIcon *gtk_status_icon;
GtkWidget *primary_menu;
GtkWidget *secondary_menu;
XAppStatusIconState state;
gchar *name;
gchar *icon_name;
gchar *tooltip_text;
gchar *label;
gboolean visible;
guint owner_id;
guint listener_id;
gint fail_counter;
gboolean have_button_press;
} XAppStatusIconPrivate;
struct _XAppStatusIcon
{
GObject parent_instance;
XAppStatusIconPrivate *priv;
};
G_DEFINE_TYPE_WITH_PRIVATE (XAppStatusIcon, xapp_status_icon, G_TYPE_OBJECT)
static void refresh_icon (XAppStatusIcon *self);
static void use_gtk_status_icon (XAppStatusIcon *self);
static void tear_down_dbus (XAppStatusIcon *self);
static void
cancellable_reset (XAppStatusIcon *self)
{
if (self->priv->cancellable)
{
g_cancellable_cancel (self->priv->cancellable);
g_object_unref (self->priv->cancellable);
}
self->priv->cancellable = g_cancellable_new ();
}
static const gchar *
panel_position_to_str (GtkPositionType type)
{
switch (type)
{
case GTK_POS_LEFT:
return "Left";
case GTK_POS_RIGHT:
return "Right";
case GTK_POS_TOP:
return "Top";
case GTK_POS_BOTTOM:
default:
return "Bottom";
}
}
static const gchar *
button_to_str (guint button)
{
switch (button)
{
case GDK_BUTTON_PRIMARY:
return "Left";
case GDK_BUTTON_SECONDARY:
return "Right";
case GDK_BUTTON_MIDDLE:
return "Middle";
default:
return "Unknown";
}
}
static const gchar *
state_to_str (XAppStatusIconState state)
{
switch (state)
{
case XAPP_STATUS_ICON_STATE_NATIVE:
return "Native";
case XAPP_STATUS_ICON_STATE_FALLBACK:
return "Fallback";
case XAPP_STATUS_ICON_STATE_NO_SUPPORT:
return "NoSupport";
default:
return "Unknown";
}
}
static gint
adjust_y_for_monitor_bounds (gint init_x,
gint init_y,
gint menu_height)
{
GdkDisplay *display = gdk_display_get_default ();
GdkMonitor *monitor;
GdkRectangle mrect;
gint bottom_edge_y;
gint ret_y;
ret_y = init_y;
monitor = gdk_display_get_monitor_at_point (display,
init_x,
init_y);
gdk_monitor_get_workarea (monitor, &mrect);
bottom_edge_y = mrect.y + mrect.height;
if ((init_y + menu_height) > bottom_edge_y)
{
ret_y = bottom_edge_y - menu_height;
}
return ret_y;
}
typedef struct {
gint x;
gint y;
gint position;
guint32 t;
} PositionData;
static void
position_menu_cb (GtkMenu *menu,
gint *x,
gint *y,
gboolean *push_in,
gpointer user_data)
{
GtkAllocation alloc;
PositionData *position_data = (PositionData *) user_data;
*x = position_data->x;
*y = position_data->y;
gtk_widget_get_allocation (GTK_WIDGET (menu), &alloc);
switch (position_data->position) {
case GTK_POS_BOTTOM:
*y = *y - alloc.height;
break;
case GTK_POS_RIGHT:
*x = *x - alloc.width;
*y = adjust_y_for_monitor_bounds (position_data->x, position_data->y, alloc.height);
break;
case GTK_POS_LEFT:
*y = adjust_y_for_monitor_bounds (position_data->x, position_data->y, alloc.height);
break;
}
*push_in = TRUE;
}
static void
popup_menu (XAppStatusIcon *self,
GtkMenu *menu,
gint x,
gint y,
guint button,
guint _time,
gint panel_position)
{
GdkDisplay *display;
GdkDevice *pointer;
g_debug ("XAppStatusIcon: Popup menu on behalf of application");
PositionData position_data = {
x, y, panel_position, _time
};
display = gdk_display_get_default ();
pointer = gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display));
gtk_menu_popup_for_device (menu,
pointer,
NULL,
NULL,
position_menu_cb,
&position_data,
NULL,
button,
_time);
}
static gboolean
should_send_activate (XAppStatusIcon *icon,
guint button)
{
gboolean do_activate = TRUE;
switch (button)
{
case GDK_BUTTON_PRIMARY:
if (icon->priv->primary_menu)
{
do_activate = FALSE;
}
break;
case GDK_BUTTON_SECONDARY:
if (icon->priv->secondary_menu)
{
do_activate = FALSE;
}
break;
default:
break;
}
return do_activate;
}
static gboolean
appindicator_can_activate (XAppStatusIcon *icon)
{
gpointer ptr;
gboolean has_activate = FALSE;
ptr = g_object_get_data (G_OBJECT (icon), "app-indicator-has-secondary-activate");
if (ptr && GPOINTER_TO_INT (ptr))
{
has_activate = TRUE;
}
return has_activate;
}
static gboolean
handle_appindicator_button_press (XAppStatusIcon *icon,
guint button,
guint _time)
{
if (g_object_get_data (G_OBJECT (icon), "app-indicator"))
{
if (button == GDK_BUTTON_MIDDLE || (button == GDK_BUTTON_PRIMARY && appindicator_can_activate (icon)))
{
g_debug ("XAppStatusIcon: sending activate for left- or middle-click event (libappindicator)");
g_signal_emit (icon, signals[ACTIVATE], 0,
button,
_time);
}
else
{
g_debug ("XAppStatusIcon: GtkStatusIcon (app-indicator) ignoring %s button press.", button_to_str (button));
}
return TRUE;
}
return FALSE;
}
static GtkWidget *
get_menu_to_use (XAppStatusIcon *icon,
guint button)
{
GtkWidget *menu_to_use = NULL;
switch (button)
{
case GDK_BUTTON_PRIMARY:
if (g_object_get_data (G_OBJECT (icon), "app-indicator"))
{
if (!appindicator_can_activate (icon))
{
menu_to_use = icon->priv->secondary_menu;
break;
}
}
menu_to_use = icon->priv->primary_menu;
break;
case GDK_BUTTON_SECONDARY:
menu_to_use = icon->priv->secondary_menu;
break;
}
return menu_to_use;
}
static gboolean
handle_click_method (XAppStatusIconInterface *skeleton,
GDBusMethodInvocation *invocation,
gint x,
gint y,
guint button,
guint _time,
gint panel_position,
XAppStatusIcon *icon)
{
const gchar *name = g_dbus_method_invocation_get_method_name (invocation);
if (g_strcmp0 (name, "ButtonPress") == 0)
{
g_debug ("XAppStatusIcon: received ButtonPress from monitor %s: "
"pos:%d,%d , button: %s , time: %u , orientation: %s",
g_dbus_method_invocation_get_sender (invocation),
x, y, button_to_str (button), _time, panel_position_to_str (panel_position));
if (!handle_appindicator_button_press (icon, button, _time))
{
if (should_send_activate (icon, button))
{
g_debug ("XAppStatusIcon: native sending 'activate' for %s button", button_to_str (button));
g_signal_emit (icon, signals[ACTIVATE], 0,
button,
_time);
}
}
icon->priv->have_button_press = TRUE;
g_signal_emit (icon, signals[BUTTON_PRESS], 0,
x, y,
button,
_time,
panel_position);
xapp_status_icon_interface_complete_button_press (skeleton,
invocation);
}
else
if (g_strcmp0 (name, "ButtonRelease") == 0)
{
g_debug ("XAppStatusIcon: received ButtonRelease from monitor %s: "
"pos:%d,%d , button: %s , time: %u , orientation: %s",
g_dbus_method_invocation_get_sender (invocation),
x, y, button_to_str (button), _time, panel_position_to_str (panel_position));
if (icon->priv->have_button_press)
{
GtkWidget *menu_to_use = get_menu_to_use (icon, button);
if (menu_to_use)
{
popup_menu (icon,
GTK_MENU (menu_to_use),
x, y,
button,
_time,
panel_position);
}
g_signal_emit (icon, signals[BUTTON_RELEASE], 0,
x, y,
button,
_time,
panel_position);
}
icon->priv->have_button_press = FALSE;
xapp_status_icon_interface_complete_button_release (skeleton,
invocation);
}
return TRUE;
}
static void
popup_gtk_status_icon_with_menu (XAppStatusIcon *icon,
GtkMenu *menu,
GtkStatusIcon *gtk_status_icon,
guint button,
guint activate_time)
{
gtk_menu_popup (menu,
NULL,
NULL,
gtk_status_icon_position_menu,
gtk_status_icon,
button,
activate_time);
}
static void
calculate_gtk_status_icon_position_and_orientation (XAppStatusIcon *icon,
GtkStatusIcon *status_icon,
gint *x, gint *y, gint *orientation)
{
GdkScreen *screen;
GdkRectangle irect;
GtkOrientation iorientation;
gint final_x, final_y, final_o;
final_x = 0;
final_y = 0;
final_o = 0;
if (gtk_status_icon_get_geometry (status_icon,
&screen,
&irect,
&iorientation))
{
GdkDisplay *display = gdk_screen_get_display (screen);
GdkMonitor *monitor;
GdkRectangle mrect;
monitor = gdk_display_get_monitor_at_point (display,
irect.x + (irect.width / 2),
irect.y + (irect.height / 2));
gdk_monitor_get_workarea (monitor, &mrect);
switch (iorientation)
{
case GTK_ORIENTATION_HORIZONTAL:
final_x = irect.x;
if (irect.y + irect.height + 100 < mrect.y + mrect.height)
{
final_y = irect.y + irect.height;
final_o = GTK_POS_TOP;
}
else
{
final_y = irect.y;
final_o = GTK_POS_BOTTOM;
}
break;
case GTK_ORIENTATION_VERTICAL:
final_y = irect.y;
if (irect.x + irect.width + 100 < mrect.x + mrect.width)
{
final_x = irect.x + irect.width;
final_o = GTK_POS_LEFT;
}
else
{
final_x = irect.x;
final_o = GTK_POS_RIGHT;
}
}
}
*x = final_x;
*y = final_y;
*orientation = final_o;
}
static gboolean
on_gtk_status_icon_button_press (GtkStatusIcon *status_icon,
GdkEvent *event,
gpointer user_data)
{
XAppStatusIcon *icon = user_data;
guint _time;
guint button;
gint x, y, orientation;
button = event->button.button;
_time = event->button.time;
/* Button press equates to activate when there's no menu,
* but for appindicator, left click and right click will always
* bring up the same menu, and only a middle click should activate. */
g_debug ("XAppStatusIcon: GtkStatusIcon button-press-event with %s button", button_to_str (button));
if (!handle_appindicator_button_press (icon, button, _time))
{
/* We always send 'activate' for a button that has no corresponding menu,
* and for middle clicks. */
if (should_send_activate (icon, button))
{
g_debug ("XAppStatusIcon: GtkStatusIcon activated by %s button", button_to_str (button));
g_signal_emit (icon, signals[ACTIVATE], 0,
button,
_time);
}
}
calculate_gtk_status_icon_position_and_orientation (icon,
status_icon,
&x,
&y,
&orientation);
icon->priv->have_button_press = TRUE;
g_signal_emit (icon, signals[BUTTON_PRESS], 0,
x, y,
button,
_time,
orientation);
}
static void
on_gtk_status_icon_button_release (GtkStatusIcon *status_icon,
GdkEvent *event,
gpointer user_data)
{
XAppStatusIcon *icon = user_data;
guint _time;
guint button;
gint x, y, orientation;
button = event->button.button;
_time = event->button.time;
g_debug ("XAppStatusIcon: GtkStatusIcon button-release-event with %s button", button_to_str (button));
/* Icons originated in app-indicator have only a single menu, which displays on either
* right or left-click. */
if (g_object_get_data (G_OBJECT (status_icon), "app-indicator"))
{
if (button == GDK_BUTTON_PRIMARY || button == GDK_BUTTON_SECONDARY)
{
popup_gtk_status_icon_with_menu (icon,
GTK_MENU (icon->priv->primary_menu),
status_icon,
button,
_time);
}
}
else
{
/* Native icons can have two menus, so we must determine which to use based
* on the gtk icon event's button. */
GtkWidget *menu_to_use;
menu_to_use = get_menu_to_use (icon, button);
if (menu_to_use)
{
g_debug ("XAppStatusIcon: GtkStatusIcon popup menu for %s button", button_to_str (button));
popup_gtk_status_icon_with_menu (icon,
GTK_MENU (menu_to_use),
status_icon,
button,
_time);
}
}
calculate_gtk_status_icon_position_and_orientation (icon,
status_icon,
&x,
&y,
&orientation);
icon->priv->have_button_press = FALSE;
g_signal_emit (icon, signals[BUTTON_RELEASE], 0,
x, y,
button,
_time,
orientation);
}
static void
name_owner_changed (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
XAppStatusIcon *self = XAPP_STATUS_ICON (user_data);
g_debug("XAppStatusIcon: NameOwnerChanged signal received, refreshing icon");
refresh_icon (self);
}
static void
add_name_listener (XAppStatusIcon *self)
{
g_debug ("XAppStatusIcon: Adding NameOwnerChanged listener for status monitors");
self->priv->listener_id = g_dbus_connection_signal_subscribe (self->priv->connection,
FDO_DBUS_NAME,
FDO_DBUS_NAME,
"NameOwnerChanged",
FDO_DBUS_PATH,
STATUS_ICON_MONITOR_MATCH,
G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
name_owner_changed,
self,
NULL);
}
static void
on_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
XAppStatusIcon *self = XAPP_STATUS_ICON (user_data);
g_warning ("XAppStatusIcon: lost or could not acquire presence on dbus. Refreshing.");
self->priv->fail_counter++;
refresh_icon (self);
}
static void
sync_skeleton (XAppStatusIcon *self)
{
XAppStatusIconPrivate *priv = self->priv;
priv->fail_counter = 0;
g_clear_object (&self->priv->gtk_status_icon);
g_object_set (G_OBJECT (priv->skeleton),
"name", priv->name,
"label", priv->label,
"icon-name", priv->icon_name,
"tooltip-text", priv->tooltip_text,
"visible", priv->visible,
NULL);
g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (priv->skeleton));
}
static void
on_name_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
XAppStatusIcon *self = XAPP_STATUS_ICON (user_data);
self->priv->state = XAPP_STATUS_ICON_STATE_NATIVE;
sync_skeleton (self);
g_debug ("XAppStatusIcon: name acquired on dbus, syncing icon properties. State is now: %s",
state_to_str (self->priv->state));
g_signal_emit (self, signals[STATE_CHANGED], 0, self->priv->state);
}
typedef struct
{
const gchar *signal_name;
gpointer callback;
} SkeletonSignal;
static SkeletonSignal skeleton_signals[] = {
// signal name callback
{ "handle-button-press", handle_click_method },
{ "handle-button-release", handle_click_method }
};
static gboolean
export_icon_interface (XAppStatusIcon *self)
{
GError *error = NULL;
gint i;
gchar *unique_path;
if (self->priv->skeleton) {
return TRUE;
}
self->priv->skeleton = xapp_status_icon_interface_skeleton_new ();
unique_path = g_strdup_printf (ICON_PATH "/%d", unique_id);
g_debug ("XAppStatusIcon: exporting StatusIcon dbus interface to %s", unique_path);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->skeleton),
self->priv->connection,
unique_path,
&error);
g_free (unique_path);
if (error != NULL) {
g_critical ("XAppStatusIcon: could not export StatusIcon interface: %s", error->message);
g_error_free (error);
return FALSE;
}
for (i = 0; i < G_N_ELEMENTS (skeleton_signals); i++) {
SkeletonSignal sig = skeleton_signals[i];
g_signal_connect (self->priv->skeleton,
sig.signal_name,
G_CALLBACK (sig.callback),
self);
}
return TRUE;
}
static void
connect_with_status_applet (XAppStatusIcon *self)
{
char *owner_name = g_strdup_printf("%s.PID-%d-%d", ICON_NAME, getpid (), unique_id);
unique_id++;
g_debug ("XAppStatusIcon: Attempting to acquire presence on dbus as %s", owner_name);
self->priv->owner_id = g_bus_own_name_on_connection (self->priv->connection,
owner_name,
G_DBUS_CONNECTION_FLAGS_NONE,
on_name_acquired,
on_name_lost,
self,
NULL);
g_free(owner_name);
}
static void
update_fallback_icon (XAppStatusIcon *self)
{
XAppStatusIconPrivate *priv = self->priv;
if (!priv->gtk_status_icon)
{
return;
}
gtk_status_icon_set_tooltip_text (priv->gtk_status_icon, priv->tooltip_text);
gtk_status_icon_set_name (priv->gtk_status_icon, priv->name);
if (priv->icon_name)
{
gtk_status_icon_set_visible (priv->gtk_status_icon, priv->visible);
if (g_path_is_absolute (priv->icon_name))
{
gtk_status_icon_set_from_file (priv->gtk_status_icon, priv->icon_name);
}
else
{
gtk_status_icon_set_from_icon_name (priv->gtk_status_icon, priv->icon_name);
}
}
else
{
gtk_status_icon_set_visible (priv->gtk_status_icon, FALSE);
}
}
static void
on_gtk_status_icon_embedded_changed (GtkStatusIcon *icon,
GParamSpec *pspec,
gpointer user_data)
{
g_return_if_fail (GTK_IS_STATUS_ICON (icon));
XAppStatusIcon *self = XAPP_STATUS_ICON (user_data);
XAppStatusIconPrivate *priv = self->priv;
if (gtk_status_icon_is_embedded (icon))
{
priv->state = XAPP_STATUS_ICON_STATE_FALLBACK;
}
else
{
priv->state = XAPP_STATUS_ICON_STATE_NO_SUPPORT;
}
g_debug ("XAppStatusIcon fallback icon embedded_changed. State is now %s",
state_to_str (priv->state));
g_signal_emit (self, signals[STATE_CHANGED], 0, priv->state);
}
static void
use_gtk_status_icon (XAppStatusIcon *self)
{
XAppStatusIconPrivate *priv = self->priv;
g_debug ("XAppStatusIcon: falling back to GtkStatusIcon");
tear_down_dbus (self);
// Make sure there wasn't already one
g_clear_object (&self->priv->gtk_status_icon);
self->priv->gtk_status_icon = gtk_status_icon_new ();
g_signal_connect (priv->gtk_status_icon,
"button-press-event",
G_CALLBACK (on_gtk_status_icon_button_press),
self);
g_signal_connect (priv->gtk_status_icon,
"button-release-event",
G_CALLBACK (on_gtk_status_icon_button_release),
self);
g_signal_connect (priv->gtk_status_icon,
"notify::embedded",
G_CALLBACK (on_gtk_status_icon_embedded_changed),
self);
update_fallback_icon (self);
}
static void
on_list_names_completed (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
XAppStatusIcon *self = XAPP_STATUS_ICON(user_data);
GVariant *result;
GVariantIter *iter;
gchar *str;
GError *error;
gboolean found;
error = NULL;
result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
res,
&error);
if (error != NULL)
{
if (error->code != G_IO_ERROR_CANCELLED)
{
g_critical ("XAppStatusIcon: attempt to ListNames failed: %s\n", error->message);
use_gtk_status_icon (self);
}
else
{
g_debug ("XAppStatusIcon: attempt to ListNames cancelled");
}
g_error_free (error);
return;
}
g_variant_get (result, "(as)", &iter);
found = FALSE;
while (g_variant_iter_loop (iter, "s", &str))
{
if (g_str_has_prefix (str, STATUS_ICON_MONITOR_MATCH))
{
g_debug ("XAppStatusIcon: Discovered active status monitor (%s)", str);
found = TRUE;
}
}
g_variant_iter_free (iter);
g_variant_unref (result);
if (found && export_icon_interface (self))
{
if (self->priv->owner_id > 0)
{
g_debug ("XAppStatusIcon: We already exist on the bus, syncing icon properties");
sync_skeleton (self);
}
else
{
connect_with_status_applet (self);
return;
}
}
else
{
use_gtk_status_icon (self);
}
}
static void
look_for_status_applet (XAppStatusIcon *self)
{
// Check that there is at least one applet on DBUS
g_debug("XAppStatusIcon: Looking for status monitors");
cancellable_reset (self);
g_dbus_connection_call (self->priv->connection,
FDO_DBUS_NAME,
FDO_DBUS_PATH,
FDO_DBUS_NAME,
"ListNames",
NULL,
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
self->priv->cancellable,
on_list_names_completed,
self);
}
static void
complete_icon_setup (XAppStatusIcon *self)
{
if (self->priv->listener_id == 0)
{
add_name_listener (self);
}
/* There is a potential loop in the g_bus_own_name sequence -
* if we fail to acquire a name, we refresh again and potentially
* fail again. If we fail more than MAX_NAME_FAILS, then quit trying
* and just use the fallback icon It's pretty unlikely for*/
if (self->priv->fail_counter == MAX_NAME_FAILS)
{
use_gtk_status_icon (self);
return;
}
look_for_status_applet (self);
}
static void
on_session_bus_connected (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
XAppStatusIcon *self = XAPP_STATUS_ICON (user_data);
GError *error;
error = NULL;
self->priv->connection = g_bus_get_finish (res, &error);
if (error != NULL)
{
if (error->code != G_IO_ERROR_CANCELLED)
{
g_critical ("XAppStatusIcon: Unable to acquire session bus: %s", error->message);
/* If we never get a connection, we use the Gtk icon exclusively, and will never
* re-try. FIXME? this is unlikely to happen, so I don't see the point in trying
* later, as there are probably bigger problems in this case. */
use_gtk_status_icon (self);
}
else
{
g_debug ("XAppStatusIcon: Cancelled session bus acquire");
}
g_error_free (error);
return;
}
if (self->priv->connection)
{
complete_icon_setup (self);
return;
}
use_gtk_status_icon (self);
}
static void
refresh_icon (XAppStatusIcon *self)
{
if (!self->priv->connection)
{
g_debug ("XAppStatusIcon: Trying to acquire session bus connection");
cancellable_reset (self);
g_bus_get (G_BUS_TYPE_SESSION,
self->priv->cancellable,
on_session_bus_connected,
self);
}
else
{
complete_icon_setup (self);
}
}
static void
xapp_status_icon_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id)
{
case PROP_PRIMARY_MENU:
xapp_status_icon_set_primary_menu (XAPP_STATUS_ICON (object), g_value_get_object (value));
break;
case PROP_SECONDARY_MENU:
xapp_status_icon_set_secondary_menu (XAPP_STATUS_ICON (object), g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
xapp_status_icon_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
XAppStatusIcon *icon = XAPP_STATUS_ICON (object);
switch (prop_id)
{
case PROP_PRIMARY_MENU:
g_value_set_object (value, icon->priv->primary_menu);
break;
case PROP_SECONDARY_MENU:
g_value_set_object (value, icon->priv->secondary_menu);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
xapp_status_icon_init (XAppStatusIcon *self)
{
self->priv = xapp_status_icon_get_instance_private (self);
self->priv->name = g_strdup_printf("%s", g_get_application_name());
self->priv->state = XAPP_STATUS_ICON_STATE_NO_SUPPORT;
g_debug ("XAppStatusIcon: init: application name: '%s'", self->priv->name);
// Default to visible (the same behavior as GtkStatusIcon)
self->priv->visible = TRUE;
refresh_icon (self);
}
static void
tear_down_dbus (XAppStatusIcon *self)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (self));
if (self->priv->owner_id > 0)
{
g_debug ("XAppStatusIcon: removing dbus presence (%p)", self);
g_bus_unown_name(self->priv->owner_id);
self->priv->owner_id = 0;
}
if (self->priv->skeleton)
{
g_debug ("XAppStatusIcon: removing dbus interface (%p)", self);
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->priv->skeleton));
g_object_unref (self->priv->skeleton);
self->priv->skeleton = NULL;
}
}
static void
xapp_status_icon_dispose (GObject *object)
{
XAppStatusIcon *self = XAPP_STATUS_ICON (object);
g_debug ("XAppStatusIcon dispose (%p)", object);
g_free (self->priv->name);
g_free (self->priv->icon_name);
g_free (self->priv->tooltip_text);
g_free (self->priv->label);
g_clear_object (&self->priv->cancellable);
g_clear_object (&self->priv->primary_menu);
g_clear_object (&self->priv->secondary_menu);
g_signal_handlers_disconnect_by_func (self->priv->gtk_status_icon, on_gtk_status_icon_button_press, self);
g_signal_handlers_disconnect_by_func (self->priv->gtk_status_icon, on_gtk_status_icon_button_release, self);
g_clear_object (&self->priv->gtk_status_icon);
tear_down_dbus (self);
if (self->priv->listener_id > 0)
{
g_dbus_connection_signal_unsubscribe (self->priv->connection, self->priv->listener_id);
}
g_clear_object (&self->priv->connection);
G_OBJECT_CLASS (xapp_status_icon_parent_class)->dispose (object);
}
static void
xapp_status_icon_finalize (GObject *object)
{
g_debug ("XAppStatusIcon finalize (%p)", object);
g_clear_object (&XAPP_STATUS_ICON (object)->priv->cancellable);
G_OBJECT_CLASS (xapp_status_icon_parent_class)->finalize (object);
}
static void
xapp_status_icon_class_init (XAppStatusIconClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = xapp_status_icon_dispose;
gobject_class->finalize = xapp_status_icon_finalize;
gobject_class->set_property = xapp_status_icon_set_property;
gobject_class->get_property = xapp_status_icon_get_property;
/**
* XAppStatusIcon:primary-menu:
*
* A #GtkMenu to use when requested by the remote monitor via a left (or primary) click.
*
* When this property is not %NULL, the menu will be automatically positioned and
* displayed during a primary button release.
*
* When this property IS %NULL, the #XAppStatusIcon::activate will be sent for primary
* button presses.
*
* In both cases, the #XAppStatusIcon::button-press-event and #XAppStatusIcon::button-release-events
* will be fired like normal.
*
* Setting this will remove any floating reference to the menu and assume ownership.
* As a result, it is not necessary to maintain a reference to it in the parent
* application (or unref it when finished with it - if you wish to replace the menu,
* simply call this method again with a new menu.
*
* The same #GtkMenu widget can be set as both the primary and secondary.
*/
g_object_class_install_property (gobject_class, PROP_PRIMARY_MENU,
g_param_spec_object ("primary-menu",
"Status icon primary (left-click) menu",
"A menu to bring up when the status icon is left-clicked",
GTK_TYPE_WIDGET,
G_PARAM_READWRITE));
/**
* XAppStatusIcon:secondary-menu:
*
* A #GtkMenu to use when requested by the remote monitor via a right (or secondary) click.
*
* When this property is not %NULL, the menu will be automatically positioned and
* displayed during a secondary button release.
*
* When this property IS %NULL, the #XAppStatusIcon::activate will be sent for secondary
* button presses.
*
* In both cases, the #XAppStatusIcon::button-press-event and #XAppStatusIcon::button-release-events
* will be fired like normal.
*
* Setting this will remove any floating reference to the menu and assume ownership.
* As a result, it is not necessary to maintain a reference to it in the parent
* application (or unref it when finished with it - if you wish to replace the menu,
* simply call this method again with a new menu.
*
* The same #GtkMenu widget can be set as both the primary and secondary.
*/
g_object_class_install_property (gobject_class, PROP_SECONDARY_MENU,
g_param_spec_object ("secondary-menu",
"Status icon secondary (right-click) menu",
"A menu to bring up when the status icon is right-clicked",
GTK_TYPE_WIDGET,
G_PARAM_READWRITE));
/**
* XAppStatusIcon::button-press-event:
* @icon: The #XAppStatusIcon
* @x: The absolute x position to use for menu positioning
* @y: The absolute y position to use for menu positioning
* @button: The button that was pressed
* @time: The time supplied by the event, or 0
* @panel_position: The #GtkPositionType to use for menu positioning
*
* Gets emitted when there is a button press received from an applet
*/
signals[BUTTON_PRESS] =
g_signal_new ("button-press-event",
XAPP_TYPE_STATUS_ICON,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 5, G_TYPE_INT, G_TYPE_INT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT);
/**
* XAppStatusIcon::button-release-event:
* @icon: The #XAppStatusIcon
* @x: The absolute x position to use for menu positioning
* @y: The absolute y position to use for menu positioning
* @button: The button that was released
* @time: The time supplied by the event, or 0
* @panel_position: The #GtkPositionType to use for menu positioning
*
* Gets emitted when there is a button release received from an applet
*/
signals[BUTTON_RELEASE] =
g_signal_new ("button-release-event",
XAPP_TYPE_STATUS_ICON,
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 5, G_TYPE_INT, G_TYPE_INT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT);
/**
* XAppStatusIcon::activate:
* @icon: The #XAppStatusIcon
* @button: The button that was pressed
* @time: The time supplied by the event, or 0
*
* Gets emitted when the user activates the status icon. If the XAppStatusIcon:primary-menu or
* XAppStatusIcon:secondary-menu is not %NULL, this signal is skipped for the respective button
* presses. A middle button click will always send this signal when pressed.
*/
signals [ACTIVATE] =
g_signal_new ("activate",
XAPP_TYPE_STATUS_ICON,
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
/**
* XAppStatusIcon::state-changed:
* @icon: The #XAppStatusIcon
* @new_state: The new #XAppStatusIconState of the icon
*
* Gets emitted when the state of the icon changes. If you wish
* to react to changes in how the status icon is being handled
* (perhaps to alter the menu or other click behavior), you should
* connect to this - see #XAppStatusIconState for more details.
*/
signals [STATE_CHANGED] =
g_signal_new ("state-changed",
XAPP_TYPE_STATUS_ICON,
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, XAPP_TYPE_STATUS_ICON_STATE);
}
/**
* xapp_status_icon_set_name:
* @icon: a #XAppStatusIcon
* @name: a name (this defaults to the name of the application, if not set)
*
* Sets the status icon name. This is not shown to users.
*
* Since: 1.6
*/
void
xapp_status_icon_set_name (XAppStatusIcon *icon, const gchar *name)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_pointer (&icon->priv->name, g_free);
icon->priv->name = g_strdup (name);
g_debug ("XAppStatusIcon set_name: %s", name);
if (icon->priv->skeleton)
{
xapp_status_icon_interface_set_name (icon->priv->skeleton, name);
}
update_fallback_icon (icon);
}
/**
* xapp_status_icon_set_icon_name:
* @icon: a #XAppStatusIcon
* @icon_name: An icon name or absolute path to an icon.
*
* Sets the icon name or local path to use.
*
* Since: 1.6
*/
void
xapp_status_icon_set_icon_name (XAppStatusIcon *icon, const gchar *icon_name)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_pointer (&icon->priv->icon_name, g_free);
icon->priv->icon_name = g_strdup (icon_name);
g_debug ("XAppStatusIcon set_icon_name: %s", icon_name);
if (icon->priv->skeleton)
{
xapp_status_icon_interface_set_icon_name (icon->priv->skeleton, icon_name);
}
update_fallback_icon (icon);
}
/**
* xapp_status_icon_set_tooltip_text:
* @icon: a #XAppStatusIcon
* @tooltip_text: the text to show in the tooltip
*
* Sets the tooltip text
*
* Since: 1.6
*/
void
xapp_status_icon_set_tooltip_text (XAppStatusIcon *icon, const gchar *tooltip_text)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_pointer (&icon->priv->tooltip_text, g_free);
icon->priv->tooltip_text = g_strdup (tooltip_text);
g_debug ("XAppStatusIcon set_tooltip_text: %s", tooltip_text);
if (icon->priv->skeleton)
{
xapp_status_icon_interface_set_tooltip_text (icon->priv->skeleton, tooltip_text);
}
update_fallback_icon (icon);
}
/**
* xapp_status_icon_set_label:
* @icon: a #XAppStatusIcon
* @label: some text
*
* Sets a label, shown beside the icon
*
* Since: 1.6
*/
void
xapp_status_icon_set_label (XAppStatusIcon *icon, const gchar *label)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_pointer (&icon->priv->label, g_free);
icon->priv->label = g_strdup (label);
g_debug ("XAppStatusIcon set_label: '%s'", label);
if (icon->priv->skeleton)
{
xapp_status_icon_interface_set_label (icon->priv->skeleton, label);
}
}
/**
* xapp_status_icon_set_visible:
* @icon: a #XAppStatusIcon
* @visible: whether or not the status icon should be visible
*
* Sets the visibility of the status icon
*
* Since: 1.6
*/
void
xapp_status_icon_set_visible (XAppStatusIcon *icon, const gboolean visible)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
icon->priv->visible = visible;
g_debug ("XAppStatusIcon set_visible: %s", visible ? "TRUE" : "FALSE");
if (icon->priv->skeleton)
{
xapp_status_icon_interface_set_visible (icon->priv->skeleton, visible);
}
update_fallback_icon (icon);
}
/**
* xapp_status_icon_set_primary_menu:
* @icon: an #XAppStatusIcon
* @menu: (nullable): A #GtkMenu to display when the primary mouse button is released.
*
* See the #XAppStatusIcon:primary-menu property for details
*
* Since: 1.6
*/
void
xapp_status_icon_set_primary_menu (XAppStatusIcon *icon,
GtkMenu *menu)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_object (&icon->priv->primary_menu);
g_debug ("XAppStatusIcon set_primary_menu: %p", menu);
if (menu)
{
icon->priv->primary_menu = GTK_WIDGET (g_object_ref_sink (menu));
}
}
/**
* xapp_status_icon_get_primary_menu:
* @icon: an #XAppStatusIcon
*
* Returns a pointer to a #GtkMenu that was set previously for the
* primary mouse button. If no menu was set, this returns %NULL.
*
* Returns: (transfer none): the #GtkMenu or %NULL if none was set.
* Since: 1.6
*/
GtkWidget *
xapp_status_icon_get_primary_menu (XAppStatusIcon *icon)
{
g_return_val_if_fail (XAPP_IS_STATUS_ICON (icon), NULL);
g_debug ("XAppStatusIcon get_menu: %p", icon->priv->primary_menu);
return icon->priv->primary_menu;
}
/**
* xapp_status_icon_set_secondary_menu:
* @icon: an #XAppStatusIcon
* @menu: (nullable): A #GtkMenu to display when the primary mouse button is released.
*
* See the #XAppStatusIcon:secondary-menu property for details
*
* Since: 1.6
*/
void
xapp_status_icon_set_secondary_menu (XAppStatusIcon *icon,
GtkMenu *menu)
{
g_return_if_fail (XAPP_IS_STATUS_ICON (icon));
g_clear_object (&icon->priv->secondary_menu);
g_debug ("XAppStatusIcon set_secondary_menu: %p", menu);
if (menu)
{
icon->priv->secondary_menu = GTK_WIDGET (g_object_ref_sink (menu));
}
}
/**
* xapp_status_icon_get_secondary_menu:
* @icon: an #XAppStatusIcon
*
* Returns a pointer to a #GtkMenu that was set previously for the
* secondary mouse button. If no menu was set, this returns %NULL.
*
* Returns: (transfer none): the #GtkMenu or %NULL if none was set.
* Since: 1.6
*/
GtkWidget *
xapp_status_icon_get_secondary_menu (XAppStatusIcon *icon)
{
g_return_val_if_fail (XAPP_IS_STATUS_ICON (icon), NULL);
g_debug ("XAppStatusIcon get_menu: %p", icon->priv->secondary_menu);
return icon->priv->secondary_menu;
}
/**
* xapp_status_icon_new:
*
* Creates a new #XAppStatusIcon instance
*
* Returns: (transfer full): a new #XAppStatusIcon. Use g_object_unref when finished.
*
* Since: 1.6
*/
XAppStatusIcon *
xapp_status_icon_new (void)
{
return g_object_new (XAPP_TYPE_STATUS_ICON, NULL);
}
/**
* xapp_status_icon_get_state:
* @icon: an #XAppStatusIcon
*
* Gets the current #XAppStatusIconState of icon. The state is determined by whether
* the icon is being displayed by an #XAppStatusMonitor client, a fallback tray icon,
* or not being displayed at all.
*
* See #XAppStatusIconState for more details.
*
* Returns: the icon's state.
*
* Since: 1.6
*/
XAppStatusIconState
xapp_status_icon_get_state (XAppStatusIcon *icon)
{
g_return_val_if_fail (XAPP_IS_STATUS_ICON (icon), XAPP_STATUS_ICON_STATE_NO_SUPPORT);
g_debug ("XAppStatusIcon get_state: %s", state_to_str (icon->priv->state));
return icon->priv->state;
}
/**
* xapp_status_icon_any_monitors:
*
* Looks for the existence of any active #XAppStatusIconMonitors on the bus.
*
* Returns: %TRUE if at least one monitor was found.
*
* Since: 1.6
*/
gboolean
xapp_status_icon_any_monitors (void)
{
GDBusConnection *connection;
GError *error;
gboolean found;
g_debug("XAppStatusIcon: any_monitors: Looking for status monitors");
error = NULL;
found = FALSE;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (connection)
{
GVariant *result;
result = g_dbus_connection_call_sync (connection,
FDO_DBUS_NAME,
FDO_DBUS_PATH,
FDO_DBUS_NAME,
"ListNames",
NULL,
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
/* 10 seconds */ 10 * 1000, NULL, &error);
if (result)
{
GVariantIter *iter;
gchar *str;
g_variant_get (result, "(as)", &iter);
found = FALSE;
while (g_variant_iter_loop (iter, "s", &str))
{
if (g_str_has_prefix (str, STATUS_ICON_MONITOR_MATCH))
{
g_debug ("XAppStatusIcon: any_monitors: discovered active status monitor (%s)", str);
found = TRUE;
g_free (str);
break;
}
}
g_variant_iter_free (iter);
g_variant_unref (result);
}
g_object_unref (connection);
}
if (error)
{
g_warning ("XAppStatusIcon: any_monitors: Unable to check for monitors: %s", error->message);
g_error_free (error);
}
g_debug ("XAppStatusIcon: any_monitors: %s", found ? "TRUE" : "FALSE");
return found;
}