diff --git a/ede-notify-daemon/Jamfile b/ede-notify-daemon/Jamfile new file mode 100644 index 0000000..83f5572 --- /dev/null +++ b/ede-notify-daemon/Jamfile @@ -0,0 +1,18 @@ +# +# $Id: Jamfile 2331 2008-09-22 12:49:55Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2012 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP ede-notify-daemon ; + +SOURCE = ede-notify-daemon.cpp NotifyWindow.cpp ; + +EdeProgram ede-notify-daemon : $(SOURCE) ; +LinkAgainst ede-notify-daemon : $(EDELIB_DBUS_LIB) ; + +TranslationStrings locale : $(SOURCE) ; diff --git a/ede-notify-daemon/NotifyWindow.cpp b/ede-notify-daemon/NotifyWindow.cpp new file mode 100644 index 0000000..d2d4289 --- /dev/null +++ b/ede-notify-daemon/NotifyWindow.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include "NotifyWindow.h" + +/* default sizes for window */ +#define DEFAULT_W 280 +#define DEFAULT_H 75 +#define DEFAULT_EXPIRE 2000 + +EDELIB_NS_USING(IconLoader) +EDELIB_NS_USING(ICON_SIZE_MEDIUM) + +extern int FL_NORMAL_SIZE; + +static void close_cb(Fl_Widget*, void *w) { + NotifyWindow *win = (NotifyWindow*)w; + win->hide(); +} + +static void timeout_cb(void *w) { + close_cb(0, w); +} + +NotifyWindow::NotifyWindow() : Fl_Window(DEFAULT_W, DEFAULT_H) { + FL_NORMAL_SIZE = 12; + + type(NOTIFYWINDOW_TYPE); + color(FL_BACKGROUND2_COLOR); + box(FL_THIN_UP_BOX); + begin(); + closeb = new Fl_Button(255, 10, 20, 20, "x"); + closeb->box(FL_NO_BOX); + closeb->color(FL_BACKGROUND2_COLOR); + closeb->labelsize(12); + closeb->tooltip(_("Close this notification")); + closeb->callback(close_cb, this); + + imgbox = new Fl_Box(10, 10, 48, 48); + imgbox->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); + + summary = new Fl_Output(65, 10, 185, 25); + /* use flat box so text can be drawn correctly */ + summary->box(FL_FLAT_BOX); + summary->cursor_color(FL_BACKGROUND2_COLOR); + + body = new Fl_Output(65, 31, 185, 25); + /* use flat box so text can be drawn correctly */ + body->box(FL_FLAT_BOX); + body->cursor_color(FL_BACKGROUND2_COLOR); + end(); + border(0); +} + +void NotifyWindow::set_icon(const char *img) { + E_RETURN_IF_FAIL(IconLoader::inited()); + E_RETURN_IF_FAIL(img != NULL); + + IconLoader::set(imgbox, img, ICON_SIZE_MEDIUM); +} + +void NotifyWindow::set_summary(const char *s) { + E_ASSERT(s != NULL && "Got NULL string?"); + summary->value(s); + + int W = 0, H = 0; + fl_measure(summary->value(), W, H); +} + +void NotifyWindow::set_body(const char *s) { + E_ASSERT(s != NULL && "Got NULL string?"); + body->value(s); + + int W = 0, H = 0; + fl_measure(summary->value(), W, H); +} + +void NotifyWindow::show(void) { + if(exp != 0) { + if(exp == -1) exp = DEFAULT_EXPIRE; + Fl::add_timeout((double)exp / (double)1000, timeout_cb, this); + } + + Fl_Window::show(); +} diff --git a/ede-notify-daemon/NotifyWindow.h b/ede-notify-daemon/NotifyWindow.h new file mode 100644 index 0000000..8e01517 --- /dev/null +++ b/ede-notify-daemon/NotifyWindow.h @@ -0,0 +1,39 @@ +#ifndef __NOTIFYWINDOW_H__ +#define __NOTIFYWINDOW_H__ + +#include +#include +#include +#include + +/* just keep it greater than FL_WINDOW or FL_DOUBLE_WINDOW */ +#define NOTIFYWINDOW_TYPE 0xF9 + +class NotifyWindow : public Fl_Window { +private: + int id; + int exp; + Fl_Button *closeb; + Fl_Box *imgbox; + Fl_Output *summary; + Fl_Output *body; + +public: + NotifyWindow(); + + void set_id(int i) { id = i; } + int get_id(void) { return id; } + + void set_icon(const char *img); + void set_summary(const char *s); + void set_body(const char *s); + + /* + * match to spec: if is -1, then we handle it, if is 0, then window will not be closed and + * the rest is sender specific + */ + void set_expire(int t) { exp = t; } + void show(void); +}; + +#endif diff --git a/ede-notify-daemon/ede-notify-daemon.cpp b/ede-notify-daemon/ede-notify-daemon.cpp new file mode 100644 index 0000000..1b720c0 --- /dev/null +++ b/ede-notify-daemon/ede-notify-daemon.cpp @@ -0,0 +1,311 @@ +#include +#include +#include + +#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.1" +#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}; + +/* 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_window(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 = 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); + + /* 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(); + + Fl::lock(); + + /* + * iterate through shown windows and find position where to put our one + * FIXME: this is quite primitive window position deducing facility + */ + 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(); + } + } + + Fl::unlock(); + win->position(px, py); + } + + /* we are already running loop, so window will handle events */ + 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; + 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(); + + /* specification dumb stuff: what if we got UINT_MAX?? here we will reverse to first ID */ + if(++notify_id == UINT_MAX) notify_id = 1; + + if(replaces_id) { + //replaces_id == notify_id; + } else { + show_window(notify_id, app_name, app_icon, summary, body, expire_timeout, hints); + } + + /* reply sent to client */ + EdbusMessage reply; + reply.create_reply(*m); + reply << EdbusData::from_uint32(replaces_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; +} + +#if 0 +static int notifications_dbus_signal_cb(const EdbusMessage *m, void *d) { + E_DEBUG("+=> %s\n", m->member()); + return 1; +} +#endif + +int main(int argc, char **argv) { + 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; + } + + dbus.register_object(NOTIFICATIONS_DBUS_PATH); + dbus.method_callback(notifications_dbus_method_cb, &dbus); + //dbus.signal_callback(notifications_dbus_signal_cb, &dbus); + dbus.setup_listener_with_fltk(); + server_running = true; + + while(server_running) + Fl::wait(FOREVER); + + IconLoader::shutdown(); + return 0; +} diff --git a/ede-notify-daemon/test-notifications.sh b/ede-notify-daemon/test-notifications.sh new file mode 100755 index 0000000..1dd637c --- /dev/null +++ b/ede-notify-daemon/test-notifications.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# simple script that will fire set of events so daemon window positioning (and other) can be tested + +N_EVENTS=5 + +notify_cmd="notify-send --expire-time=10000" + +for((i=0;i<$N_EVENTS;i++)) do + $notify_cmd "title $i" "this is content" +done diff --git a/ede-notify-daemon/window.fl b/ede-notify-daemon/window.fl new file mode 100644 index 0000000..d5d8f3e --- /dev/null +++ b/ede-notify-daemon/window.fl @@ -0,0 +1,24 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0300 +header_name {.h} +code_name {.cxx} +Function {} {open +} { + Fl_Window {} {open selected + xywh {1219 684 280 75} type Double color 7 visible + } { + Fl_Button {} { + label x + xywh {255 10 20 20} box NO_BOX color 7 labelsize 12 + } + Fl_Box {} { + image {../../../../.icons/edeneu/32x32/status/dialog-information.png} xywh {10 10 48 48} align 20 + } + Fl_Output {} { + xywh {65 10 185 24} box NO_BOX labelsize 12 + } + Fl_Output {} { + xywh {65 31 185 24} box NO_BOX labelsize 12 + } + } +}