Introduce DBus support
authorMathieu <mbaudier@argeo.org>
Wed, 21 Dec 2022 07:41:49 +0000 (08:41 +0100)
committerMathieu <mbaudier@argeo.org>
Wed, 21 Dec 2022 07:41:49 +0000 (08:41 +0100)
19 files changed:
Makefile
org.argeo.cms.lib.dbus/.classpath [new file with mode: 0644]
org.argeo.cms.lib.dbus/.project [new file with mode: 0644]
org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml [new file with mode: 0644]
org.argeo.cms.lib.dbus/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.dbus/build.properties [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd [new file with mode: 0644]
org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java
swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd
swt/rcp/org.argeo.cms.swt.rcp/build.properties
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java [new file with mode: 0644]

index 330bc4fca94bd79b5de123e25dd24079678ad9c7..8c15c13072eab1e3b7760e395d3e2335a28fae0e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ org.argeo.cms \
 org.argeo.cms.ux \
 org.argeo.cms.ee \
 org.argeo.cms.lib.jetty \
+org.argeo.cms.lib.dbus \
 org.argeo.cms.lib.sshd \
 org.argeo.cms.cli \
 osgi/equinox/org.argeo.cms.lib.equinox \
diff --git a/org.argeo.cms.lib.dbus/.classpath b/org.argeo.cms.lib.dbus/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.cms.lib.dbus/.project b/org.argeo.cms.lib.dbus/.project
new file mode 100644 (file)
index 0000000..bf0398c
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.dbus</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml b/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml
new file mode 100644 (file)
index 0000000..6797c3d
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsDBus">
+   <implementation class="org.argeo.cms.dbus.CmsDBusImpl"/>
+   <service>
+      <provide interface="org.argeo.cms.dbus.CmsDBus"/>
+   </service>
+   <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms.lib.dbus/bnd.bnd b/org.argeo.cms.lib.dbus/bnd.bnd
new file mode 100644 (file)
index 0000000..407c785
--- /dev/null
@@ -0,0 +1,5 @@
+Import-Package: \
+org.freedesktop.dbus.connections.transports,\
+*
+
+Service-Component: OSGI-INF/cmsDBus.xml
diff --git a/org.argeo.cms.lib.dbus/build.properties b/org.argeo.cms.lib.dbus/build.properties
new file mode 100644 (file)
index 0000000..76d7c98
--- /dev/null
@@ -0,0 +1,5 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/cmsDBus.xml
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java
new file mode 100644 (file)
index 0000000..df76822
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.dbus;
+
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+
+public interface CmsDBus {
+       final static String DBUS_SESSION_BUS_ADDRESS = "DBUS_SESSION_BUS_ADDRESS";
+       
+       DBusConnection openSessionConnection();
+}
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java
new file mode 100644 (file)
index 0000000..b9a202b
--- /dev/null
@@ -0,0 +1,86 @@
+package org.argeo.cms.dbus;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.freedesktop.dbus.bin.EmbeddedDBusDaemon;
+import org.freedesktop.dbus.connections.BusAddress;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+public class CmsDBusImpl implements CmsDBus {
+       private final static CmsLog log = CmsLog.getLog(CmsDBusImpl.class);
+
+       private BusAddress sessionBusAddress;
+
+       private EmbeddedDBusDaemon dBusDaemon;
+
+       private CmsEventBus cmsEventBus;
+
+       public void start() {
+               try {
+                       final String envSessionBusAddress = System.getenv(DBUS_SESSION_BUS_ADDRESS);
+                       if (envSessionBusAddress != null) {
+                               sessionBusAddress = BusAddress.of(envSessionBusAddress);
+                               
+                               // !! We must first initialise a connection, otherwise there are classloader issues later on 
+                               try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress)
+                                               .withShared(false).build()) {
+
+                               }
+                               log.debug(() -> "Found session DBus with address " + sessionBusAddress);
+                       } else {
+                               Path socketLocation = Paths.get(System.getProperty("user.home"), ".cache", "argeo", "bus");
+                               Files.createDirectories(socketLocation.getParent());
+                               // TODO escape : on Windows?
+                               String embeddedSessionBusAddress = "unix:path=" + socketLocation.toUri().getPath();
+                               dBusDaemon = new EmbeddedDBusDaemon(embeddedSessionBusAddress);
+                               dBusDaemon.startInBackgroundAndWait(30 * 1000);
+
+                               sessionBusAddress = BusAddress.of(embeddedSessionBusAddress);
+                               try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress)
+                                               .withShared(false).build()) {
+
+                               }
+                               log.debug(() -> "Started embedded session DBus with address " + sessionBusAddress);
+
+                               // TODO set environment variable?
+                       }
+               } catch (DBusException | IOException e) {
+                       throw new IllegalStateException("Cannot find a session bus", e);
+               }
+       }
+
+       public void stop() {
+               if (dBusDaemon != null) {
+                       try {
+                               dBusDaemon.close();
+                       } catch (IOException e) {
+                               log.error("Cannot close embedded DBus daemon", e);
+                       }
+               }
+       }
+
+       @Override
+       public DBusConnection openSessionConnection() {
+               try {
+                       DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress).withShared(false)
+                                       .build();
+                       // TODO track all connections?
+                       return dBusConnection;
+               } catch (DBusException e) {
+                       e.printStackTrace();
+                       throw new IllegalStateException("Cannot open connection to session DBus", e);
+               }
+       }
+
+       public void setCmsEventBus(CmsEventBus cmsEventBus) {
+               this.cmsEventBus = cmsEventBus;
+       }
+
+}
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java
new file mode 100644 (file)
index 0000000..708ee61
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.freedesktop;
+
+import java.util.List;
+import java.util.Map;
+
+import org.freedesktop.dbus.annotations.DBusInterfaceName;
+import org.freedesktop.dbus.annotations.DBusMemberName;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.types.Variant;
+
+/**
+ * The org.freedesktop.Application interface.
+ */
+@DBusInterfaceName("org.freedesktop.Application")
+public interface FreeDesktopApplication extends DBusInterface {
+
+       @DBusMemberName(value = "Activate")
+       void activate(Map<String, Variant<?>> platformData);
+
+       @DBusMemberName(value = "Open")
+       void open(List<String> uris, Map<String, Variant<?>> platformData);
+
+       @DBusMemberName(value = "ActivateAction")
+       void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData);
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd
new file mode 100644 (file)
index 0000000..ba263d3
--- /dev/null
@@ -0,0 +1,37 @@
+<!-- DTD for D-Bus Introspection data -->
+<!-- (C) 2005-02-02 David A. Wheeler; released under the D-Bus licenses,
+         GNU GPL version 2 (or greater) and AFL 1.1 (or greater) -->
+
+<!-- see D-Bus specification for documentation -->
+
+<!ELEMENT node (node|interface)*>
+<!ATTLIST node name CDATA #IMPLIED>
+
+<!ELEMENT interface (method|signal|property|annotation)*>
+<!ATTLIST interface name CDATA #REQUIRED>
+
+<!ELEMENT method (arg|annotation)*>
+<!ATTLIST method name CDATA #REQUIRED>
+
+<!ELEMENT signal (arg|annotation)*>
+<!ATTLIST signal name CDATA #REQUIRED>
+
+<!ELEMENT arg EMPTY>
+<!ATTLIST arg name CDATA #IMPLIED>
+<!ATTLIST arg type CDATA #REQUIRED>
+<!-- Method arguments SHOULD include "direction",
+     while signal and error arguments SHOULD not (since there's no point).
+     The DTD format can't express that subtlety. -->
+<!ATTLIST arg direction (in|out) "in">
+
+<!-- AKA "attribute" -->
+<!ELEMENT property (annotation)*>
+<!ATTLIST property name CDATA #REQUIRED>
+<!ATTLIST property type CDATA #REQUIRED> 
+<!ATTLIST property access (read|write|readwrite) #REQUIRED>
+
+<!ELEMENT annotation EMPTY>  <!-- Generic metadata -->
+<!ATTLIST annotation name CDATA #REQUIRED>
+<!ATTLIST annotation value CDATA #REQUIRED>
+
+
diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml
new file mode 100644 (file)
index 0000000..5af11f7
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE node SYSTEM "introspect-1.0.dtd">
+<!-- See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus -->
+<node>
+       <interface name="org.freedesktop.Application">
+               <method name="Activate">
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+               <method name="Open">
+                       <arg type="as" name="uris" direction="in" />
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+               <method name="ActivateAction">
+                       <arg type="s" name="action-name" direction="in" />
+                       <arg type="av" name="parameter" direction="in" />
+                       <arg type="a{sv}" name="platform-data" direction="in" />
+               </method>
+       </interface>
+</node>
\ No newline at end of file
index f71878060e3d0bf399594f5e065c5e8af7cfc545..6cf4ebf5e00dbf17229d5348c965f4f5e444f12a 100644 (file)
@@ -22,7 +22,7 @@ public class OsUserUtils {
        public static LoginContext loginAsSystemUser(Subject subject) {
                try {
                        URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
-                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
+                                       .getResource("org/argeo/cms/osgi/useradmin/jaas-os.cfg");
                        URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
                        Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
                        LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml
new file mode 100644 (file)
index 0000000..e8bfefb
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsRcpDBusLauncher">
+   <implementation class="org.argeo.cms.ui.rcp.dbus.CmsRcpDBusLauncher"/>
+   <reference bind="setCmsDBus" cardinality="1..1" interface="org.argeo.cms.dbus.CmsDBus" name="CmsDBus" policy="static"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+</scr:component>
index 5cf5164e1b69f8c09156c2ee14ec4e29367405df..e1735f03cd5e850df581fa66fbcdb345d858c205 100644 (file)
@@ -5,9 +5,11 @@ org.argeo.cms.auth,\
 org.eclipse.swt,\
 org.eclipse.swt.graphics,\
 org.w3c.css.sac,\
+org.freedesktop.dbus.connections,\
 *
 
 Service-Component:\
 OSGI-INF/cmsRcpDisplayFactory.xml,\
-OSGI-INF/cmsRcpHttpLauncher.xml
+OSGI-INF/cmsRcpDBusLauncher.xml
+#OSGI-INF/cmsRcpHttpLauncher.xml
 
index 4ed1d4748afc3e7dc121f8f0fab5a1c1a56d24e3..f85c0d8ca5f1d4168fd5a62a4f046447dce18088 100644 (file)
@@ -1,6 +1,8 @@
-output.. = bin/
 bin.includes = META-INF/,\
                .,\
                OSGI-INF/,\
-               OSGI-INF/cmsRcpHttpLauncher.xml
+               OSGI-INF/cmsRcpHttpLauncher.xml,\
+               OSGI-INF/cmsRcpDBusLauncher.xml
+additional.bundles = org.slf4j.api
 source.. = src/
+output.. = bin/
index a83a54db3926cfa2503079c22d3224dff28a8591..63a1fd84ebb76ce6dc7f9da3d9d924e807871143 100644 (file)
@@ -67,7 +67,8 @@ public class CmsRcpDisplayFactory {
                                display = null;
                        } catch (UnsatisfiedLinkError e) {
                                logger.log(Level.ERROR,
-                                               "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.",
+                                               "Cannot load SWT, either because the SWT DLLs are no in the java.library.path,"
+                                                               + " or because the OSGi framework has been refreshed." + " Restart the application.",
                                                e);
                        }
                }
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java
new file mode 100644 (file)
index 0000000..ea69057
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.dbus.CmsDBus;
+
+public class CmsRcpDBusLauncher {
+       private CompletableFuture<CmsDBus> cmsDBus = new CompletableFuture<>();
+
+       private Map<String, CmsRcpFreeDesktopApplication> apps = new HashMap<>();
+
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               cmsDBus.thenAcceptAsync((cmsDBus) -> {
+                       CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsDBus, contextName, cmsApp);
+                       apps.put(contextName, application);
+               });
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               CmsRcpFreeDesktopApplication application = apps.remove(contextName);
+               if (application != null) {
+                       try {
+                               application.close();
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot remove CMS RCP app " + contextName, e);
+                       }
+               }
+       }
+
+       public void setCmsDBus(CmsDBus cmsDBus) {
+               this.cmsDBus.complete(cmsDBus);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java
new file mode 100644 (file)
index 0000000..98c84fa
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.dbus.CmsDBus;
+import org.argeo.cms.freedesktop.FreeDesktopApplication;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.Variant;
+
+public class CmsRcpFreeDesktopApplication implements FreeDesktopApplication, Closeable {
+       private String path;
+
+       private CmsApp cmsApp;
+
+       private DBusConnection dBusConnection;
+
+       public CmsRcpFreeDesktopApplication(CmsDBus cmsDBus, String contextName, CmsApp cmsApp) {
+               // TODO find a better prefix and/or make it customisable
+               this.path = "/org/argeo/cms/" + contextName;
+               this.cmsApp = cmsApp;
+               try {
+                       String appName = path.replace('/', '.').substring(1);
+                       dBusConnection = cmsDBus.openSessionConnection();
+                       dBusConnection.requestBusName(appName);
+                       dBusConnection.exportObject(getObjectPath(), this);
+               } catch (DBusException e) {
+                       throw new IllegalStateException("Cannot add CMS app " + path, e);
+               }
+       }
+
+       @Override
+       public String getObjectPath() {
+               return path;
+       }
+
+       @Override
+       public void close() throws IOException {
+               if (dBusConnection != null)
+                       dBusConnection.close();
+       }
+
+       @Override
+       public void activate(Map<String, Variant<?>> platformData) {
+               // String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) :
+               // "";
+               String uiName = "app";
+               CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+       }
+
+       @Override
+       public void open(List<String> uris, Map<String, Variant<?>> platformData) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData) {
+               // TODO Auto-generated method stub
+
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java
new file mode 100644 (file)
index 0000000..8f2da98
--- /dev/null
@@ -0,0 +1,96 @@
+package org.argeo.cms.ui.rcp.dbus;
+
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.argeo.cms.freedesktop.FreeDesktopApplication;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.Variant;
+
+public class TestApplication implements FreeDesktopApplication {
+       private DBusConnection dBusConnection;
+
+       private final Display display;
+
+       private Shell shell = null;
+
+       private Text text;
+
+       public TestApplication() throws DBusException {
+               display = Display.getCurrent();
+
+               /* Get a connection to the session bus so we can request a bus name */
+               dBusConnection = DBusConnectionBuilder.forSessionBus().build();
+//             m_conn = DBusConnectionBuilder.forAddress("unix:path=/tmp/dbus-80908265778467677465").build();
+//             m_conn = DBusConnectionBuilder.forAddress("tcp:host=localhost,port=55556").build();
+               /* Request a unique bus name */
+               dBusConnection.requestBusName("org.argeo.TestApplication");
+               /* Export this object onto the bus using the path '/' */
+               dBusConnection.exportObject(getObjectPath(), this);
+       }
+
+       @Override
+       public String getObjectPath() {
+               return "/org/argeo/TestApplication";
+       }
+
+       @Override
+       public void activate(Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+               });
+
+       }
+
+       protected void shellVisible() {
+               if (shell == null || shell.isDisposed()) {
+                       shell = new Shell(display);
+                       shell.setLayout(new GridLayout());
+                       text = new Text(shell, SWT.MULTI | SWT.WRAP);
+                       text.setLayoutData(CmsSwtUtils.fillAll());
+                       text.setText("New shell\n");
+                       shell.open();
+               } else {
+               }
+               shell.forceActive();
+       }
+
+       @Override
+       public void open(List<String> uris, Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+                       for (String uri : uris) {
+                               text.append(uri);
+                               text.append("\n");
+                       }
+                       shell.forceActive();
+               });
+       }
+
+       @Override
+       public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData) {
+               display.syncExec(() -> {
+                       shellVisible();
+                       text.append("Execute action '" + actionName + "' with arguments " + parameter);
+                       text.append("\n");
+               });
+       }
+
+       public static void main(String[] args) throws DBusException {
+               Display display = new Display();
+               new TestApplication();
+               while (!display.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+               display.dispose();
+       }
+}