From 93832291a83666434bd589a0b3fd11f956224bfc Mon Sep 17 00:00:00 2001 From: Mathieu Date: Wed, 21 Dec 2022 08:41:49 +0100 Subject: [PATCH] Introduce DBus support --- Makefile | 1 + org.argeo.cms.lib.dbus/.classpath | 7 ++ org.argeo.cms.lib.dbus/.project | 33 +++++++ org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml | 8 ++ org.argeo.cms.lib.dbus/bnd.bnd | 5 + org.argeo.cms.lib.dbus/build.properties | 5 + .../src/org/argeo/cms/dbus/CmsDBus.java | 9 ++ .../src/org/argeo/cms/dbus/CmsDBusImpl.java | 86 +++++++++++++++++ .../freedesktop/FreeDesktopApplication.java | 26 +++++ .../argeo/cms/freedesktop/introspect-1.0.dtd | 37 +++++++ .../org.freedesktop.Application.xml | 18 ++++ .../argeo/cms/osgi/useradmin/OsUserUtils.java | 2 +- .../OSGI-INF/cmsRcpDBusLauncher.xml | 6 ++ swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd | 4 +- .../org.argeo.cms.swt.rcp/build.properties | 6 +- .../cms/ui/rcp/CmsRcpDisplayFactory.java | 3 +- .../cms/ui/rcp/dbus/CmsRcpDBusLauncher.java | 48 ++++++++++ .../dbus/CmsRcpFreeDesktopApplication.java | 68 +++++++++++++ .../cms/ui/rcp/dbus/TestApplication.java | 96 +++++++++++++++++++ 19 files changed, 463 insertions(+), 5 deletions(-) create mode 100644 org.argeo.cms.lib.dbus/.classpath create mode 100644 org.argeo.cms.lib.dbus/.project create mode 100644 org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml create mode 100644 org.argeo.cms.lib.dbus/bnd.bnd create mode 100644 org.argeo.cms.lib.dbus/build.properties create mode 100644 org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java create mode 100644 org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java create mode 100644 org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java create mode 100644 org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd create mode 100644 org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml create mode 100644 swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml create mode 100644 swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java create mode 100644 swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java create mode 100644 swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java diff --git a/Makefile b/Makefile index 330bc4fca..8c15c1307 100644 --- 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 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.lib.dbus/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.lib.dbus/.project b/org.argeo.cms.lib.dbus/.project new file mode 100644 index 000000000..bf0398c12 --- /dev/null +++ b/org.argeo.cms.lib.dbus/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.lib.dbus + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..6797c3d9b --- /dev/null +++ b/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms.lib.dbus/bnd.bnd b/org.argeo.cms.lib.dbus/bnd.bnd new file mode 100644 index 000000000..407c78525 --- /dev/null +++ b/org.argeo.cms.lib.dbus/bnd.bnd @@ -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 index 000000000..76d7c98d4 --- /dev/null +++ b/org.argeo.cms.lib.dbus/build.properties @@ -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 index 000000000..df768226d --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java @@ -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 index 000000000..b9a202be9 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java @@ -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 index 000000000..708ee6163 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java @@ -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> platformData); + + @DBusMemberName(value = "Open") + void open(List uris, Map> platformData); + + @DBusMemberName(value = "ActivateAction") + void activateAction(String actionName, List> parameter, Map> 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 index 000000000..ba263d326 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 000000000..5af11f722 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java index f71878060..6cf4ebf5e 100644 --- a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java @@ -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 index 000000000..e8bfefb3f --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd index 5cf5164e1..e1735f03c 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd +++ b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd @@ -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 diff --git a/swt/rcp/org.argeo.cms.swt.rcp/build.properties b/swt/rcp/org.argeo.cms.swt.rcp/build.properties index 4ed1d4748..f85c0d8ca 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/build.properties +++ b/swt/rcp/org.argeo.cms.swt.rcp/build.properties @@ -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/ diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java index a83a54db3..63a1fd84e 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java @@ -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 index 000000000..ea6905757 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java @@ -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 = new CompletableFuture<>(); + + private Map apps = new HashMap<>(); + + public void start() { + + } + + public void stop() { + + } + + public void addCmsApp(CmsApp cmsApp, Map 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 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 index 000000000..98c84fa41 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java @@ -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> platformData) { + // String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : + // ""; + String uiName = "app"; + CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null); + } + + @Override + public void open(List uris, Map> platformData) { + // TODO Auto-generated method stub + + } + + @Override + public void activateAction(String actionName, List> parameter, Map> 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 index 000000000..8f2da9861 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java @@ -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> 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 uris, Map> platformData) { + display.syncExec(() -> { + shellVisible(); + for (String uri : uris) { + text.append(uri); + text.append("\n"); + } + shell.forceActive(); + }); + } + + @Override + public void activateAction(String actionName, List> parameter, Map> 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(); + } +} -- 2.30.2