package org.asamk.signal.dbus; import org.asamk.signal.DbusConfig; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManager; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; import org.freedesktop.dbus.exceptions.DBusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; public class DbusHandler implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(DbusHandler.class); private final boolean isDbusSystem; private DBusConnection dBusConnection; private final List closeables = new ArrayList<>(); private final DbusRunner dbusRunner; private final boolean noReceiveOnStart; public DbusHandler(final boolean isDbusSystem, final Manager m, final boolean noReceiveOnStart) { this.isDbusSystem = isDbusSystem; this.dbusRunner = (connection) -> { try { exportDbusObject(connection, DbusConfig.getObjectPath(), m).join(); } catch (InterruptedException ignored) { } }; this.noReceiveOnStart = noReceiveOnStart; } public DbusHandler(final boolean isDbusSystem, final MultiAccountManager c, final boolean noReceiveOnStart) { this.isDbusSystem = isDbusSystem; this.dbusRunner = (connection) -> { final var signalControl = new DbusSignalControlImpl(c, DbusConfig.getObjectPath()); connection.exportObject(signalControl); c.addOnManagerAddedHandler(m -> { final var thread = exportManager(connection, m); try { thread.join(); } catch (InterruptedException ignored) { } }); c.addOnManagerRemovedHandler(m -> { final var path = DbusConfig.getObjectPath(m.getSelfNumber()); try { final var object = connection.getExportedObject(null, path); if (object instanceof DbusSignalImpl dbusSignal) { dbusSignal.close(); closeables.remove(dbusSignal); } } catch (DBusException ignored) { } }); final var initThreads = c.getManagers().stream().map(m -> exportManager(connection, m)).toList(); for (var t : initThreads) { try { t.join(); } catch (InterruptedException ignored) { } } }; this.noReceiveOnStart = noReceiveOnStart; } public void init() throws CommandException { if (dBusConnection != null) { throw new AssertionError("DbusHandler already initialized"); } final var busType = isDbusSystem ? DBusConnection.DBusBusType.SYSTEM : DBusConnection.DBusBusType.SESSION; logger.debug("Starting DBus server on {} bus: {}", busType, DbusConfig.getBusname()); try { dBusConnection = DBusConnectionBuilder.forType(busType).build(); dbusRunner.run(dBusConnection); } catch (DBusException e) { throw new UnexpectedErrorException("Dbus command failed: " + e.getMessage(), e); } catch (UnsupportedOperationException e) { throw new UserErrorException("Failed to connect to Dbus: " + e.getMessage(), e); } try { dBusConnection.requestBusName(DbusConfig.getBusname()); } catch (DBusException e) { throw new UnexpectedErrorException("Dbus command failed, maybe signal-cli dbus daemon is already running: " + e.getMessage(), e); } logger.info("Started DBus server on {} bus: {}", busType, DbusConfig.getBusname()); } @Override public void close() throws Exception { if (dBusConnection == null) { return; } dBusConnection.close(); for (final var c : new ArrayList<>(closeables)) { c.close(); } closeables.clear(); dBusConnection = null; } private Thread exportDbusObject(final DBusConnection conn, final String objectPath, final Manager m) { final var signal = new DbusSignalImpl(m, conn, objectPath, noReceiveOnStart); closeables.add(signal); return Thread.ofPlatform().name("dbus-init-" + m.getSelfNumber()).start(signal::initObjects); } private Thread exportManager(final DBusConnection conn, final Manager m) { final var objectPath = DbusConfig.getObjectPath(m.getSelfNumber()); return exportDbusObject(conn, objectPath, m); } private interface DbusRunner { void run(DBusConnection connection) throws DBusException; } }