/* vim:set et sts=4 sw=4:
*
* ibus - The Input Bus
*
* Copyright(c) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
* Copyright(c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
/* This class extends AppIndicator because
* AppIndicator misses "Activate" dbus method in the definition
* for left click on the indicator.
*/
public extern string _notification_item;
public extern string _notification_watcher;
class Indicator : IBus.Service
{
public string id { get; construct; }
public string category_s { get; construct; }
public string status_s { get; set; }
public string icon_name { get; set; }
public string icon_desc { get; set; }
public string attention_icon_name { get; set; }
public string attention_icon_desc { get; set; }
public string title { get; set; }
public string icon_theme_path { get; set; }
public bool connected { get; set; }
public string label_s { get; set; }
public string label_guide_s { get; set; }
public uint32 ordering_index { get; set; }
public GLib.Variant icon_vector { get; set; }
public enum Category {
APPLICATION_STATUS,
COMMUNICATIONS,
SYSTEM_SERVICES,
HARDWARE,
OTHER;
public string to_nick() {
switch(this) {
case APPLICATION_STATUS: return "ApplicationStatus";
case COMMUNICATIONS: return "Communications";
case SYSTEM_SERVICES: return "SystemServices";
case HARDWARE: return "Hardware";
case OTHER: return "Other";
default: assert_not_reached();
}
}
}
public enum Status {
PASSIVE,
ACTIVE,
ATTENTION;
public string to_nick() {
switch(this) {
case PASSIVE: return "Passive";
case ACTIVE: return "Active";
case ATTENTION: return "NeedsAttention";
default: assert_not_reached();
}
}
}
private const string DEFAULT_ITEM_PATH = "/org/ayatana/NotificationItem";
private const string NOTIFICATION_ITEM_DBUS_IFACE =
"org.kde.StatusNotifierItem";
private const string NOTIFICATION_WATCHER_DBUS_IFACE =
"org.kde.StatusNotifierWatcher";
private const string NOTIFICATION_WATCHER_DBUS_ADDR =
"org.kde.StatusNotifierWatcher";
private const string NOTIFICATION_WATCHER_DBUS_OBJ =
"/StatusNotifierWatcher";
private GLib.DBusNodeInfo m_watcher_node_info;
private unowned GLib.DBusInterfaceInfo m_watcher_interface_info;
private GLib.DBusProxy m_proxy;
private int m_context_menu_x;
private int m_context_menu_y;
private int m_activate_menu_x;
private int m_activate_menu_y;
private Gdk.Window m_indicator_window;
public Indicator(string id,
GLib.DBusConnection connection,
Category category = Category.OTHER) {
string path = DEFAULT_ITEM_PATH + "/" + id;
path = path.delimit("-", '_');
// AppIndicator.set_category() converts enum value to string internally.
GLib.Object(object_path: path,
id: id,
connection: connection,
category_s: category.to_nick());
this.status_s = Status.PASSIVE.to_nick();
this.icon_name = "";
this.icon_desc = "";
this.title = "";
this.icon_theme_path = "";
this.attention_icon_name = "";
this.attention_icon_desc = "";
this.label_s = "";
this.label_guide_s = "";
unregister(connection);
add_interfaces(_notification_item);
try {
if (!register(connection))
return;
} catch (GLib.Error e) {
warning("Failed to register the application indicator xml: " +
e.message);
return;
}
try {
m_watcher_node_info =
new GLib.DBusNodeInfo.for_xml(_notification_watcher);
} catch (GLib.Error e) {
warning("Failed to create dbus node info: " + e.message);
return;
}
m_watcher_interface_info =
m_watcher_node_info.lookup_interface(
NOTIFICATION_WATCHER_DBUS_IFACE);
check_connect();
}
private void check_connect() {
if (m_proxy == null) {
GLib.DBusProxy.new.begin(
connection,
GLib.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES |
GLib.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS,
m_watcher_interface_info,
NOTIFICATION_WATCHER_DBUS_ADDR,
NOTIFICATION_WATCHER_DBUS_OBJ,
NOTIFICATION_WATCHER_DBUS_IFACE,
null,
(obj, res) => {
bus_watcher_ready(obj, res);
});
} else {
bus_watcher_ready(null, null);
}
}
private void bus_watcher_ready(GLib.Object? obj, GLib.AsyncResult? res) {
if (res != null) {
try {
m_proxy = GLib.DBusProxy.new.end(res);
} catch (GLib.IOError e) {
warning("Failed to call dbus proxy: " + e.message);
return;
}
m_proxy.notify["g-name-owner"].connect((obj, pspec) => {
var name = m_proxy.get_name_owner();
if (name != null)
check_connect();
});
}
var name = m_proxy.get_name_owner();
// KDE panel does not run yet if name == null
if (name == null)
return;
m_proxy.call.begin("RegisterStatusNotifierItem",
new GLib.Variant("(s)", this.object_path),
GLib.DBusCallFlags.NONE,
-1,
null,
(p_obj, p_res) => {
try {
m_proxy.call.end(p_res);
registered_status_notifier_item();
} catch (GLib.Error e) {
warning("Failed to call " +
"RegisterStatusNotifierItem: " +
e.message);
}
});
}
private void _context_menu_cb(GLib.DBusConnection connection,
GLib.Variant parameters,
GLib.DBusMethodInvocation invocation) {
GLib.Variant var_x = parameters.get_child_value(0);
GLib.Variant var_y = parameters.get_child_value(1);
m_context_menu_x = var_x.get_int32();
m_context_menu_y = var_y.get_int32();
Gdk.Window window = query_gdk_window();
context_menu(m_context_menu_x, m_context_menu_y, window, 2, 0);
}
private void _activate_menu_cb(GLib.DBusConnection connection,
GLib.Variant parameters,
GLib.DBusMethodInvocation invocation) {
GLib.Variant var_x = parameters.get_child_value(0);
GLib.Variant var_y = parameters.get_child_value(1);
m_activate_menu_x = var_x.get_int32();
m_activate_menu_y = var_y.get_int32();
Gdk.Window window = query_gdk_window();
activate(m_activate_menu_x, m_activate_menu_y, window);
}
private Gdk.Window? query_gdk_window() {
if (m_indicator_window != null)
return m_indicator_window;
Gdk.Display display = Gdk.Display.get_default();
unowned X.Display xdisplay =
(display as Gdk.X11.Display).get_xdisplay();
X.Window current = xdisplay.default_root_window();
X.Window parent = 0;
X.Window child = 0;
int root_x, root_y, win_x, win_y;
uint mask = 0;
root_x = root_y = win_x = win_y = 0;
bool retval;
// Need XSetErrorHandler for BadWindow?
while ((retval = xdisplay.query_pointer(current,
out parent, out child,
out root_x, out root_y,
out win_x, out win_y,
out mask))) {
if (child == 0)
break;
current = child;
}
if (!retval) {
string format =
"XQueryPointer is failed: current: %x root: %x " +
"child: %x (%d, %d), (%d, %d), %u";
string message = format.printf((uint)current,
(uint)xdisplay.default_root_window(),
(uint)child,
root_x, root_y, win_x, win_y,
mask);
warning("XQueryPointer is failed: %s", message);
return null;
}
if (current == xdisplay.default_root_window())
warning("The query window is root window");
m_indicator_window = Gdk.X11.Window.lookup_for_display(
display as Gdk.X11.Display,
current);
if (m_indicator_window != null)
return m_indicator_window;
m_indicator_window = new Gdk.X11.Window.foreign_for_display(
display as Gdk.X11.Display,
current);
return m_indicator_window;
}
private GLib.Variant? _get_id(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.id);
}
private GLib.Variant? _get_category(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.category_s);
}
private GLib.Variant? _get_status(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.status_s);
}
private GLib.Variant? _get_icon_name(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.icon_name);
}
private GLib.Variant? _get_icon_vector(GLib.DBusConnection connection) {
return this.icon_vector;
}
private GLib.Variant? _get_icon_desc(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.icon_desc);
}
private GLib.Variant? _get_attention_icon_name(GLib.DBusConnection
connection) {
return new GLib.Variant.string(this.attention_icon_name);
}
private GLib.Variant? _get_attention_icon_desc(GLib.DBusConnection
connection) {
return new GLib.Variant.string(this.attention_icon_desc);
}
private GLib.Variant? _get_title(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.title);
}
private GLib.Variant? _get_icon_theme_path(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.icon_theme_path);
}
private GLib.Variant? _get_menu(GLib.DBusConnection connection) {
return null;
}
private GLib.Variant? _get_xayatana_label(GLib.DBusConnection connection) {
return new GLib.Variant.string(this.label_s);
}
private GLib.Variant? _get_xayatana_label_guide(GLib.DBusConnection
connection) {
return new GLib.Variant.string(this.label_guide_s);
}
private GLib.Variant? _get_xayatana_ordering_index(GLib.DBusConnection
connection) {
return new GLib.Variant.uint32(this.ordering_index);
}
public override void service_method_call(GLib.DBusConnection
connection,
string sender,
string object_path,
string interface_name,
string method_name,
GLib.Variant parameters,
GLib.DBusMethodInvocation
invocation) {
GLib.return_if_fail (object_path == this.object_path);
GLib.return_if_fail (interface_name == NOTIFICATION_ITEM_DBUS_IFACE);
if (method_name == "Activate") {
_activate_menu_cb(connection, parameters, invocation);
return;
}
if (method_name == "ContextMenu") {
_context_menu_cb(connection, parameters, invocation);
return;
}
warning("service_method_call() does not handle the method: " +
method_name);
}
public override GLib.Variant? service_get_property(GLib.DBusConnection
connection,
string sender,
string object_path,
string interface_name,
string property_name) {
GLib.return_val_if_fail (object_path == this.object_path, null);
GLib.return_val_if_fail (
interface_name == NOTIFICATION_ITEM_DBUS_IFACE,
null);
if (property_name == "Id")
return _get_id(connection);
if (property_name == "Category")
return _get_category(connection);
if (property_name == "Status")
return _get_status(connection);
if (property_name == "IconName")
return _get_icon_name(connection);
if (property_name == "IconPixmap")
return _get_icon_vector(connection);
if (property_name == "IconAccessibleDesc")
return _get_icon_desc(connection);
if (property_name == "AttentionIconName")
return _get_attention_icon_name(connection);
if (property_name == "AttentionAccessibleDesc")
return _get_attention_icon_desc(connection);
if (property_name == "Title")
return _get_title(connection);
if (property_name == "IconThemePath")
return _get_icon_theme_path(connection);
if (property_name == "Menu")
return _get_menu(connection);
if (property_name == "XAyatanaLabel")
return _get_xayatana_label(connection);
if (property_name == "XAyatanaLabelGuide")
return _get_xayatana_label_guide(connection);
if (property_name == "XAyatanaOrderingIndex")
return _get_xayatana_ordering_index(connection);
warning("service_get_property() does not handle the property: " +
property_name);
return null;
}
public override bool service_set_property(GLib.DBusConnection
connection,
string sender,
string object_path,
string interface_name,
string property_name,
GLib.Variant value) {
return false;
}
// AppIndicator.set_status() converts enum value to string internally.
public void set_status(Status status) {
string status_s = status.to_nick();
if (this.status_s == status_s)
return;
this.status_s = status_s;
/* This API does not require (this.connection != null)
* because service_get_property() can be called when
* this.connection emits the "NewStatus" signal or
* or m_proxy calls the "RegisterStatusNotifierItem" signal.
*/
if (this.connection == null)
return;
try {
this.connection.emit_signal(null,
this.object_path,
NOTIFICATION_ITEM_DBUS_IFACE,
"NewStatus",
new GLib.Variant("(s)", status_s));
} catch(GLib.Error e) {
warning("Unable to send signal for NewIcon: %s", e.message);
}
}
// AppIndicator.set_icon() is deprecated.
public void set_icon_full(string icon_name, string? icon_desc) {
bool changed = false;
if (this.icon_name != icon_name) {
this.icon_name = icon_name;
this.icon_vector = null;
changed = true;
}
if (this.icon_desc != icon_desc) {
this.icon_desc = icon_desc;
changed = true;
}
if (!changed)
return;
/* This API does not require (this.connection != null)
* because service_get_property() can be called when
* this.connection emits the "NewIcon" signal or
* or m_proxy calls the "RegisterStatusNotifierItem" signal.
*/
if (this.connection == null)
return;
try {
this.connection.emit_signal(null,
this.object_path,
NOTIFICATION_ITEM_DBUS_IFACE,
"NewIcon",
null);
} catch(GLib.Error e) {
warning("Unable to send signal for NewIcon: %s", e.message);
}
}
public void set_cairo_image_surface_full(Cairo.ImageSurface image,
string? icon_desc) {
int width = image.get_width();
int height = image.get_height();
int stride = image.get_stride();
unowned uint8[] data = (uint8[]) image.get_data();
data.length = stride * height;
GLib.Bytes bytes = new GLib.Bytes(data);
GLib.Variant bs =
new GLib.Variant.from_bytes(GLib.VariantType.BYTESTRING,
bytes,
true);
GLib.VariantBuilder builder = new GLib.VariantBuilder(
new GLib.VariantType("a(iiay)"));
builder.open(new GLib.VariantType("(iiay)"));
builder.add("i", width);
builder.add("i", height);
builder.add_value(bs);
builder.close();
this.icon_vector = new GLib.Variant("a(iiay)", builder);
this.icon_name = "";
if (this.icon_desc != icon_desc)
this.icon_desc = icon_desc;
/* This API does not require (this.connection != null)
* because service_get_property() can be called when
* this.connection emits the "NewIcon" signal or
* or m_proxy calls the "RegisterStatusNotifierItem" signal.
*/
if (this.connection == null)
return;
try {
this.connection.emit_signal(null,
this.object_path,
NOTIFICATION_ITEM_DBUS_IFACE,
"NewIcon",
null);
} catch(GLib.Error e) {
warning("Unable to send signal for NewIcon: %s", e.message);
}
}
public void position_context_menu(Gtk.Menu menu,
out int x,
out int y,
out bool push_in) {
x = m_context_menu_x;
y = m_context_menu_y;
push_in = false;
}
public void position_activate_menu(Gtk.Menu menu,
out int x,
out int y,
out bool push_in) {
x = m_activate_menu_x;
y = m_activate_menu_y;
push_in = false;
}
/**
* unregister_connection:
*
* "Destroy" dbus method is not called for the indicator's connection
* when panel's connection is disconneted because the dbus connection
* is a shared session bus so need to call
* g_dbus_connection_unregister_object() by manual here
* so that g_object_unref(m_panel) will be called later with an idle method,
* which was assigned in the arguments of
* g_dbus_connection_register_object()
*/
public void unregister_connection() {
unregister(get_connection());
}
public signal void context_menu(int x,
int y,
Gdk.Window window,
uint button,
uint activate_time);
public signal void activate(int x,
int y,
Gdk.Window window);
public signal void registered_status_notifier_item();
}