/* * $Id: ede-panel.cpp 3330 2012-05-28 10:57:50Z karijes $ * * Copyright (C) 2012-2014 Sanel Zukan * * 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; either version 2 * of the License, or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef EDELIB_HAVE_DBUS #include #include #include #include #include #include #include #include #include #include #include "NotifyWindow.h" /* server info sent via GetServerInformation */ #define EDE_NOTIFY_DAEMON_NAME "EDE Notification Daemon" #define EDE_NOTIFY_DAEMON_VENDOR "ede" #define EDE_NOTIFY_DAEMON_VERSION "0.2" #define EDE_NOTIFY_DAEMON_SPEC_VERSION "1.2" #define NOTIFICATIONS_DBUS_PATH "/org/freedesktop/Notifications" #define NOTIFICATIONS_DBUS_SERVICE_NAME "org.freedesktop.Notifications" /* types of urgency levels */ #define URGENCY_LOW 0 #define URGENCY_NORMAL 1 #define URGENCY_CRITICAL 2 /* from FLTK */ #define FOREVER 1e20 /* space between shown windows (on both side) */ #define WINDOWS_PADDING 10 #define IS_MEMBER(m, s1) (strcmp((m->member()), (s1)) == 0) EDELIB_NS_USING(EdbusConnection) EDELIB_NS_USING(EdbusMessage) EDELIB_NS_USING(EdbusData) EDELIB_NS_USING(EdbusList) EDELIB_NS_USING(EdbusDict) EDELIB_NS_USING(EdbusVariant) EDELIB_NS_USING(IconLoader) EDELIB_NS_USING(netwm_workarea_get_size) EDELIB_NS_USING(EDBUS_SESSION) EDELIB_NS_USING(EDBUS_TYPE_INVALID) static bool server_running; /* * list of server capabilities * check all available on: http://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html */ // static const char *server_caps[] = {"actions", "body", "icon-static", 0}; static const char *server_caps[] = {"body", "icon-static", 0}; /* increased every time new notification window is shown; must be less than UINT_MAX */ static unsigned int notify_id; static bool empty_str(const char *s) { if(!s || !strlen(s)) return true; return false; } static byte_t get_urgency_level(EdbusDict &hints) { EdbusData ret = hints.find(EdbusData::from_string("urgency")); /* default */ E_RETURN_VAL_IF_FAIL(ret.type() != EDBUS_TYPE_INVALID, URGENCY_NORMAL); /* FIXME: spec said this is byte by I'm getting variant here ??? */ E_RETURN_VAL_IF_FAIL(ret.is_variant(), URGENCY_NORMAL); EdbusVariant v = ret.to_variant(); /* must be byte */ E_RETURN_VAL_IF_FAIL(v.value.is_byte(), URGENCY_NORMAL); return v.value.to_byte(); } static bool get_int_coordinate(const char *n, EdbusDict &hints, int &c) { EdbusData ret = hints.find(EdbusData::from_string(n)); /* it is fine to be not present */ if(ret.type() == EDBUS_TYPE_INVALID) return false; /* coordinates are 'int'; but which fucking 'int'; int32, int16, int64 ??? */ if(ret.is_int32()) c = (int)ret.to_int32(); else if(ret.is_int16()) c = (int)ret.to_int16(); else if(ret.is_int64()) c = (int)ret.to_int64(); else return false; return true; } static void show_or_update_window(bool update_only, unsigned int id, const char *app_name, const char *app_icon, const char *summary, const char *body, int expire_timeout, EdbusDict &hints) { byte_t u = get_urgency_level(hints); NotifyWindow *win = NULL; if(update_only) { E_DEBUG(E_STRLOC ": Requesting update\n"); /* try to find existing window with given id */ Fl_Window *wi; NotifyWindow *tmp; for(wi = Fl::first_window(); wi; wi = Fl::next_window(wi)) { if(wi->type() != NOTIFYWINDOW_TYPE) continue; tmp = (NotifyWindow*)wi; if(tmp->get_id() == id) { E_DEBUG(E_STRLOC ": Requesting update - win found\n"); win = tmp; break; } } } /* window not found or new window requested */ if(!win) win = new NotifyWindow(); if(!empty_str(summary)) win->set_summary(summary); if(!empty_str(body)) win->set_body(body); if(empty_str(app_icon)) { switch(u) { case URGENCY_CRITICAL: app_icon = "dialog-error"; break; case URGENCY_LOW: case URGENCY_NORMAL: default: app_icon = "dialog-information"; } } win->set_icon(app_icon); win->set_id(id); win->set_expire(expire_timeout, update_only); /* according to spec, both coordinates must exist so window can be positioned as desired */ int X, Y; if(get_int_coordinate("x", hints, X) && get_int_coordinate("y", hints, Y)) { win->position(X, Y); } else { int sx, sy, sw, sh; if(!netwm_workarea_get_size(sx, sy, sw, sh)) Fl::screen_xywh(sx, sy, sw, sh); /* default positions */ int px, py; px = sw - WINDOWS_PADDING - win->w(); py = sh - WINDOWS_PADDING - win->h(); /* * iterate through shown windows and find position where to put our one * FIXME: this is quite primitive window position deducing facility * * TODO: add locking here */ Fl_Window *wi; for(wi = Fl::first_window(); wi; wi = Fl::next_window(wi)) { if(wi->type() != NOTIFYWINDOW_TYPE) continue; py -= wi->h() + WINDOWS_PADDING; if(py < sy) { px -= wi->w() + WINDOWS_PADDING; py = sh - WINDOWS_PADDING - wi->h(); } } win->position(px, py); } /* we are already running loop, so window will handle events */ if(!win->shown()) win->show(); } static int handle_notify(EdbusConnection *dbus, const EdbusMessage *m) { if(m->size() != 8) { E_WARNING(E_STRLOC ": Received malformed 'Notify'. Ignoring...\n"); return 0; } const char *app_name, *app_icon, *summary, *body; app_name = app_icon = summary = body = NULL; unsigned int replaces_id, id; int expire_timeout; EdbusMessage::const_iterator it = m->begin(); E_RETURN_VAL_IF_FAIL(it->is_string(), 0); app_name = it->to_string(); ++it; E_RETURN_VAL_IF_FAIL(it->is_uint32(), 0); replaces_id = it->to_uint32(); ++it; E_RETURN_VAL_IF_FAIL(it->is_string(), 0); app_icon = it->to_string(); ++it; E_RETURN_VAL_IF_FAIL(it->is_string(), 0); summary = it->to_string(); ++it; E_RETURN_VAL_IF_FAIL(it->is_string(), 0); body = it->to_string(); ++it; E_RETURN_VAL_IF_FAIL(it->is_array(), 0); EdbusList array = it->to_array(); ++it; E_RETURN_VAL_IF_FAIL(it->is_dict(), 0); /* we are supporting only 'urgency' and x/y hints here */ EdbusDict hints = it->to_dict(); ++it; E_RETURN_VAL_IF_FAIL(it->is_int32(), 0); expire_timeout = it->to_int32(); if(replaces_id > 0) { id = replaces_id; } else { /* by the spec, if we got MAX, reversing will be just fine */ if(++notify_id == UINT_MAX) notify_id = 1; id = notify_id; } /* this should never happen */ E_RETURN_VAL_IF_FAIL(id != 0, 0); show_or_update_window(replaces_id > 0, id, app_name, app_icon, summary, body, expire_timeout, hints); #if 0 if(replaces_id) { update_window(replaces_id, app_name, app_icon, summary, body, expire_timeout, hints); sent_id = replaces_id; } else { show_window(notify_id, app_name, app_icon, summary, body, expire_timeout, hints); sent_id = notify_id; } #endif /* reply sent to client, with used window id */ EdbusMessage reply; reply.create_reply(*m); reply << EdbusData::from_uint32(id); dbus->send(reply); return 1; } static int notifications_dbus_method_cb(const EdbusMessage *m, void *d) { EdbusConnection *dbus = (EdbusConnection*)d; /* void org.freedesktop.Notifications.GetServerInformation (out STRING name, out STRING vendor, out STRING version) */ if(IS_MEMBER(m, "GetServerInformation")) { EdbusMessage reply; reply.create_reply(*m); reply << EdbusData::from_string(EDE_NOTIFY_DAEMON_NAME) << EdbusData::from_string(EDE_NOTIFY_DAEMON_VENDOR) << EdbusData::from_string(EDE_NOTIFY_DAEMON_VERSION) << EdbusData::from_string(EDE_NOTIFY_DAEMON_SPEC_VERSION); /* without this notify-send will not work */ dbus->send(reply); return 1; } /* STRING_ARRAY org.freedesktop.Notifications.GetCapabilities (void) */ if(IS_MEMBER(m, "GetCapabilities")) { EdbusList array = EdbusList::create_array(); for(int i = 0; server_caps[i]; i++) array << EdbusData::from_string(server_caps[i]); EdbusMessage reply; reply.create_reply(*m); reply << array; dbus->send(reply); return 1; } /* UINT32 org.freedesktop.Notifications.Notify (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body, ARRAY actions, DICT hints, INT32 expire_timeout) */ if(IS_MEMBER(m, "Notify")) return handle_notify(dbus, m); return 1; } #endif /* EDELIB_HAVE_DBUS */ #if 0 static int notifications_dbus_signal_cb(const EdbusMessage *m, void *d) { E_DEBUG("+=> %s\n", m->member()); return 1; } #endif static void help(void) { puts("Usage: ede-notify-daemon [OPTIONS]"); puts("Background service responsible for displaying various notifications"); puts("Options:"); puts(" -h, --help this help"); puts(" -n, --no-daemon do not run in background"); } #define CHECK_ARGV(argv, pshort, plong) ((strcmp(argv, pshort) == 0) || (strcmp(argv, plong) == 0)) int main(int argc, char **argv) { /* daemon behaves as GUI app, as will use icon theme and etc. */ EDE_APPLICATION("ede-notify-daemon"); bool daemonize = true; if(argc > 1) { if(CHECK_ARGV(argv[1], "-h", "--help")) { help(); return 0; } if(CHECK_ARGV(argv[1], "-n", "--no-daemon")) { daemonize = false; } } #ifdef EDELIB_HAVE_DBUS server_running = false; notify_id = 0; EdbusConnection dbus; IconLoader::init(); fl_register_images(); fl_open_display(); if(!dbus.connect(EDBUS_SESSION)) { E_WARNING(E_STRLOC ": Unable to connect to session bus. Is your dbus daemon running?"); return 1; } if(!dbus.request_name(NOTIFICATIONS_DBUS_SERVICE_NAME)) { E_WARNING(E_STRLOC ": Seems notification daemon is already running. Quitting...\n"); return 1; } if(daemonize) edelib_daemon(0, 0); dbus.register_object(NOTIFICATIONS_DBUS_PATH); dbus.method_callback(notifications_dbus_method_cb, &dbus); dbus.setup_listener_with_fltk(); server_running = true; while(server_running) Fl::wait(FOREVER); IconLoader::shutdown(); #else E_WARNING(E_STRLOC ": edelib is compiled without DBus so notification daemon is not able to receive notification messages\n"); #endif return 0; }