Add notification backend to support DockManagers
authorBas Pape <baspape@gmail.com>
Thu, 14 Feb 2013 21:55:43 +0000 (22:55 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 6 Mar 2013 19:24:57 +0000 (20:24 +0100)
DockManager is a DBus API that does fancy icons. Current support
includes progress reporting whilst connecting, requesting attention
when highlighted and showing pending highlight count in a label. Exact
support also depends on the dock.

dev-notes/DockManager-spec.txt [new file with mode: 0644]
interfaces/org.freedesktop.DockItem.xml [new file with mode: 0644]
interfaces/org.freedesktop.DockManager.xml [new file with mode: 0644]
src/qtui/CMakeLists.txt
src/qtui/dockmanagernotificationbackend.cpp [new file with mode: 0644]
src/qtui/dockmanagernotificationbackend.h [new file with mode: 0644]
src/qtui/mainwin.cpp

diff --git a/dev-notes/DockManager-spec.txt b/dev-notes/DockManager-spec.txt
new file mode 100644 (file)
index 0000000..a8376b6
--- /dev/null
@@ -0,0 +1,74 @@
+DBus Interface Specification
+
+Docky implements the DockManager specificiation as well as a custom DBus specification.
+DockManager DBus Interface Specification
+
+DBus unique path: net.launchpad.DockManager
+
+Object paths:
+    /net/launchpad/DockManager
+
+- Interface: net.launchpad.DockManager
+  * Methods:
+    * GetCapabilities       ()                         -> (Array of [String] capabilities)
+    * GetItems              ()                         -> (Array of [Object path])
+    * GetItemsByName        (String name               -> (Array of [Object path])
+    * GetItemsByDesktopFile (String desktop_file_name) -> (Array of [Object path])
+    * GetItemsByPID         (Int32 pid)                -> (Array of [Object path])
+    * GetItemByXid          (Int64 xid)                -> (Object path)
+  * Signals:
+    * ItemAdded             (Object path)
+    * ItemRemoved           (Object path)
+
+capabilities:
+
+- dock-item-attention
+- dock-item-badge
+- dock-item-icon-file
+- dock-item-message
+- dock-item-progress
+- dock-item-tooltip
+- dock-item-waiting
+
+- menu-item-container-title
+- menu-item-icon-file
+- menu-item-icon-name
+- menu-item-with-label
+- menu-item-with-uri
+
+
+    /net/launchpad/DockManager/Item[.+] (unspecified identifier)
+
+- Interface: net.launchpad.DockItem
+  * Methods:
+    * AddMenuItem       (Dict of {String key, Variant value} menu_hints) -> (Int32 id)
+    * RemoveMenuItem    (Int32 id)
+    * UpdateDockItem    (Dict of {String key, Variant value} hints)
+  * Properties (implementing org.freedesktop.DBus.Properties)
+    * string DesktopFile
+    * string Uri
+  * Signals:
+    * MenuItemActivated (Int32 id)
+
+Supported menu_hints:
+
+Required:
+ - label + String
+   OR
+ - uri + String
+Optional:
+ - container-title + String
+ - icon-file + String
+ - icon-name + String
+
+Implementor can choose whether icon setting will be honored when the menu item is specified using the "uri" key.
+
+Supported hints: All hints are optional.
+- attention + Boolean
+- badge + String
+- icon-file + String
+- message + String
+- progress + Int
+- tooltip + String
+- waiting + Boolean
+
diff --git a/interfaces/org.freedesktop.DockItem.xml b/interfaces/org.freedesktop.DockItem.xml
new file mode 100644 (file)
index 0000000..e18a5d6
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.freedesktop.DockItem">
+    <method name="AddMenuItem">
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
+      <arg name="menu_hints" type="a{sv}" direction="in"/>
+      <arg name="result" type="i" direction="out"/>
+    </method>
+    <method name="RemoveMenuItem">
+      <arg name="id" type="i" direction="in"/>
+    </method>
+    <method name="UpdateDockItem">
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
+      <arg name="hints" type="a{sv}" direction="in"/>
+    </method>
+    <property name="DesktopFile" type="s" access="read"/>
+    <property name="Uri" type="s" access="read"/>
+    <signal name="MenuItemActivated">
+      <arg name="id" type="i"/>
+    </signal>
+  </interface>
+</node>
diff --git a/interfaces/org.freedesktop.DockManager.xml b/interfaces/org.freedesktop.DockManager.xml
new file mode 100644 (file)
index 0000000..778888b
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.freedesktop.DockManager">
+    <method name="GetCapabilities">
+      <arg name="result" type="as" direction="out"/>
+    </method>
+    <method name="GetItems">
+      <arg name="result" type="ao" direction="out"/>
+    </method>
+    <method name="GetItemsByName">
+      <arg name="name" type="s" direction="in"/>
+      <arg name="result" type="ao" direction="out"/>
+    </method>
+    <method name="GetItemsByDesktopFile">
+      <arg name="desktop_file" type="s" direction="in"/>
+      <arg name="result" type="ao" direction="out"/>
+    </method>
+    <method name="GetItemsByPid">
+      <arg name="pid" type="i" direction="in"/>
+      <arg name="result" type="ao" direction="out"/>
+    </method>
+    <method name="GetItemByXid">
+      <arg name="xid" type="x" direction="in"/>
+      <arg name="result" type="o" direction="out"/>
+    </method>
+    <method name="AwnSetVisibility">
+      <arg name="win_name" type="s" direction="in"/>
+      <arg name="visible" type="b" direction="in"/>
+    </method>
+    <method name="AwnRegisterProxyItem">
+      <arg name="desktop_file" type="s" direction="in"/>
+      <arg name="uri" type="s" direction="in"/>
+      <arg name="result" type="o" direction="out"/>
+    </method>
+    <signal name="ItemAdded">
+      <arg name="path" type="o"/>
+    </signal>
+    <signal name="ItemRemoved">
+      <arg name="path" type="o"/>
+    </signal>
+  </interface>
+</node>
index 8d8dce0..7f67c9d 100644 (file)
@@ -155,8 +155,8 @@ else(HAVE_KDE)
 endif(HAVE_KDE)
 
 if(HAVE_DBUS)
-  set(SOURCES ${SOURCES} statusnotifieritem.cpp statusnotifieritemdbus.cpp)
-  set(MOC_HDRS ${MOC_HDRS} statusnotifieritem.h statusnotifieritemdbus.h)
+  set(SOURCES ${SOURCES} statusnotifieritem.cpp statusnotifieritemdbus.cpp dockmanagernotificationbackend.cpp)
+  set(MOC_HDRS ${MOC_HDRS} statusnotifieritem.h statusnotifieritemdbus.h dockmanagernotificationbackend.h)
   set(FORMS ${FORMS})
   if(WITH_QT5)
     qt5_add_dbus_interface(DBUS ../../interfaces/org.kde.StatusNotifierWatcher.xml statusnotifierwatcher)
diff --git a/src/qtui/dockmanagernotificationbackend.cpp b/src/qtui/dockmanagernotificationbackend.cpp
new file mode 100644 (file)
index 0000000..c9fdc0d
--- /dev/null
@@ -0,0 +1,227 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   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) version 3.                                           *
+ *                                                                         *
+ *   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.         *
+ ***************************************************************************/
+
+#include "dockmanagernotificationbackend.h"
+
+#include <QHBoxLayout>
+#include <QCheckBox>
+#include <QDBusReply>
+
+#include "client.h"
+#include "clientsettings.h"
+#include "coreconnection.h"
+#include "clientbacklogmanager.h"
+
+DockManagerNotificationBackend::DockManagerNotificationBackend(QObject *parent)
+    : AbstractNotificationBackend(parent), _bus(QDBusConnection::sessionBus()), _dock(0), _item(0), _count(0)
+{
+    NotificationSettings notificationSettings;
+    _enabled = notificationSettings.value("DockManager/Enabled", false).toBool();
+
+    notificationSettings.notify("DockManager/Enabled", this, SLOT(enabledChanged(const QVariant &)));
+
+    _dock = new QDBusInterface("net.launchpad.DockManager", "/net/launchpad/DockManager", "net.launchpad.DockManager", _bus, this);
+    if (_dock->isValid()) {
+        _bus.connect("net.launchpad.DockManager", "/net/launchpad/DockManager", "net.launchpad.DockManager", "ItemAdded", this, SLOT(itemAdded(QDBusObjectPath)));
+    } else {
+        // evil implementations (awn) use fd.o
+        _dock = new QDBusInterface("org.freedesktop.DockManager", "/org/freedesktop/DockManager", "org.freedesktop.DockManager", _bus, this);
+        if (_dock->isValid()) {
+            _bus.connect("org.freedesktop.DockManager", "/org/freedesktop/DockManager", "org.freedesktop.DockManager", "ItemAdded", this, SLOT(itemAdded(QDBusObjectPath)));
+        } else {
+            qDebug() << "No DockManager available";
+            _enabled = false;
+            return;
+        }
+    }
+
+    itemAdded(QDBusObjectPath());
+
+    connect(Client::coreConnection(), SIGNAL(progressValueChanged(int)), this, SLOT(updateProgress(int)));
+    connect(Client::coreConnection(), SIGNAL(synchronized()), this, SLOT(synchronized()));
+}
+
+
+void DockManagerNotificationBackend::itemAdded(QDBusObjectPath p)
+{
+    Q_UNUSED(p);
+
+    if (_item)
+        return;
+
+    // stupid implementations (awn; kde?) use wrong casing of PID, but proper type
+    QDBusReply<QList<QDBusObjectPath> > paths = _dock->call("GetItemsByPid", (int)QCoreApplication::applicationPid());
+    if (!paths.isValid()) {
+        // stupid implementations (i.e. docky) use uint, but proper casing
+        paths = _dock->call("GetItemsByPID", (unsigned int)QCoreApplication::applicationPid());
+        if (!paths.isValid()) {
+            qDebug() << "DBus error:" << paths.error().message();
+            return;
+        }
+    }
+    if (paths.value().count() == 0) { // no icon for this instance
+        return;
+    }
+
+    QString path = paths.value()[0].path(); // no sense in using multiple icons for one instance
+    _item = new QDBusInterface("org.freedesktop.DockManager", path, "org.freedesktop.DockItem", _bus, this);
+}
+
+
+void DockManagerNotificationBackend::updateProgress(int progress)
+{
+    if (!_enabled || !_item)
+        return;
+
+    CoreConnection *c = Client::instance()->coreConnection();
+    int perc = 0;
+    if (c->progressMaximum() == c->progressMinimum())
+        perc = 0;
+    else
+        perc = (progress - c->progressMinimum()) * 100 / (c->progressMaximum() - c->progressMinimum());
+
+    QHash<QString, QVariant> args;
+    args["progress"] = perc;
+    _item->call("UpdateDockItem", args);
+}
+
+
+void DockManagerNotificationBackend::updateProgress(int done, int total)
+{
+    if (!_enabled || !_item)
+        return;
+
+    int perc = 0;
+    if (done == total) {
+        disconnect(Client::backlogManager(), 0, this, 0);
+        perc = -1;
+    } else
+        perc = (done * 100) / total;
+
+    QHash<QString, QVariant> args;
+    args["progress"] = perc;
+    _item->call("UpdateDockItem", args);
+}
+
+
+void DockManagerNotificationBackend::synchronized()
+{
+    connect(Client::backlogManager(), SIGNAL(updateProgress(int, int)), this, SLOT(updateProgress(int, int)));
+}
+
+
+void DockManagerNotificationBackend::notify(const Notification &notification)
+{
+    if (!_enabled || !_item) {
+        return;
+    }
+    if (notification.type != Highlight && notification.type != PrivMsg) {
+        return;
+    }
+
+    QHash<QString, QVariant> args;
+    args["attention"] = true;
+    args["badge"] = QString::number(++_count);
+    _item->call("UpdateDockItem", args);
+}
+
+
+void DockManagerNotificationBackend::close(uint notificationId)
+{
+    Q_UNUSED(notificationId);
+    if (!_item)
+        return;
+
+    QHash<QString, QVariant> args;
+    args["attention"] = false;
+    args["badge"] = --_count == 0 ? QString() : QString::number(_count);
+    _item->call("UpdateDockItem", args);
+}
+
+
+void DockManagerNotificationBackend::enabledChanged(const QVariant &v)
+{
+    _enabled = v.toBool();
+
+    if (!_enabled && _item) {
+        QHash<QString, QVariant> args;
+        args["attention"] = false;
+        args["badge"] = QString();
+        _item->call("UpdateDockItem", args);
+    }
+}
+
+
+SettingsPage *DockManagerNotificationBackend::createConfigWidget() const
+{
+    return new ConfigWidget();
+}
+
+
+/***************************************************************************/
+
+DockManagerNotificationBackend::ConfigWidget::ConfigWidget(QWidget *parent)
+    : SettingsPage("Internal", "DockManagerNotification", parent)
+{
+    QHBoxLayout *layout = new QHBoxLayout(this);
+    layout->addWidget(enabledBox = new QCheckBox(tr("Mark dockmanager entry"), this));
+    enabledBox->setEnabled(true);
+
+    connect(enabledBox, SIGNAL(toggled(bool)), SLOT(widgetChanged()));
+}
+
+
+void DockManagerNotificationBackend::ConfigWidget::widgetChanged()
+{
+    bool changed = enabled != enabledBox->isChecked();
+
+    if (changed != hasChanged()) setChangedState(changed);
+}
+
+
+bool DockManagerNotificationBackend::ConfigWidget::hasDefaults() const
+{
+    return true;
+}
+
+
+void DockManagerNotificationBackend::ConfigWidget::defaults()
+{
+    enabledBox->setChecked(false);
+    widgetChanged();
+}
+
+
+void DockManagerNotificationBackend::ConfigWidget::load()
+{
+    NotificationSettings s;
+    enabled = s.value("DockManager/Enabled", false).toBool();
+
+    enabledBox->setChecked(enabled);
+    setChangedState(false);
+}
+
+
+void DockManagerNotificationBackend::ConfigWidget::save()
+{
+    NotificationSettings s;
+    s.setValue("DockManager/Enabled", enabledBox->isChecked());
+    load();
+}
diff --git a/src/qtui/dockmanagernotificationbackend.h b/src/qtui/dockmanagernotificationbackend.h
new file mode 100644 (file)
index 0000000..70efb00
--- /dev/null
@@ -0,0 +1,81 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   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) version 3.                                           *
+ *                                                                         *
+ *   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.         *
+ ***************************************************************************/
+
+#ifndef DOCKMANAGERNOTIFICATIONBACKEND_H_
+#define DOCKMANAGERNOTIFICATIONBACKEND_H_
+
+#include <QDBusConnection>
+#include <QDBusInterface>
+
+#include "abstractnotificationbackend.h"
+#include "settingspage.h"
+
+class QCheckBox;
+
+class DockManagerNotificationBackend : public AbstractNotificationBackend
+{
+    Q_OBJECT
+
+public:
+    DockManagerNotificationBackend(QObject *parent = 0);
+
+    void notify(const Notification &);
+    void close(uint notificationId);
+    virtual SettingsPage *createConfigWidget() const;
+
+private slots:
+    void enabledChanged(const QVariant &);
+    void updateProgress(int progress);
+    void updateProgress(int done, int total);
+    void itemAdded(QDBusObjectPath);
+    void synchronized();
+
+private:
+    class ConfigWidget;
+    bool _enabled;
+    QDBusConnection _bus;
+    QDBusInterface *_dock;
+    QDBusInterface *_item;
+    int _count;
+};
+
+
+class DockManagerNotificationBackend::ConfigWidget : public SettingsPage
+{
+    Q_OBJECT
+
+public:
+    ConfigWidget(QWidget *parent = 0);
+
+    void save();
+    void load();
+    bool hasDefaults() const;
+    void defaults();
+
+private slots:
+    void widgetChanged();
+
+private:
+    QCheckBox *enabledBox;
+    bool enabled;
+};
+
+
+#endif
index fc53283..a4be9fd 100644 (file)
   #include "osxnotificationbackend.h"
 #endif
 
+#ifdef HAVE_DBUS
+  #include "dockmanagernotificationbackend.h"
+#endif
+
 #include "settingspages/aliasessettingspage.h"
 #include "settingspages/appearancesettingspage.h"
 #include "settingspages/backlogsettingspage.h"
@@ -226,6 +230,10 @@ void MainWin::init()
     QtUi::registerNotificationBackend(new OSXNotificationBackend(this));
 #endif
 
+#ifdef HAVE_DBUS
+    QtUi::registerNotificationBackend(new DockManagerNotificationBackend(this));
+#endif
+
     // we assume that at this point, all configurable actions are defined!
     QtUi::loadShortcuts();