diff --git a/include/IApplication.hpp b/include/IApplication.hpp index 5c1690b..882afe5 100644 --- a/include/IApplication.hpp +++ b/include/IApplication.hpp @@ -15,7 +15,7 @@ struct IApplicationCallback { virtual void appLaunched(IApplication* app) {(void)app;} virtual void appQuitting(IApplication* app) {(void)app;} - virtual bool appFileOpen(IApplication* app, const std::string& path) {(void)app;(void)path;return true;} + virtual void appFilesOpen(IApplication* app, const std::vector& paths) {(void)app;(void)paths;} }; class IApplication @@ -45,6 +45,8 @@ public: virtual void run()=0; virtual void quit()=0; + virtual const std::string& getUniqueName() const=0; + virtual const std::string& getFriendlyName() const=0; virtual const std::string& getProcessName() const=0; virtual const std::vector& getArgs() const=0; @@ -55,23 +57,27 @@ public: IApplication* IApplicationBootstrap(IApplication::EPlatformType platform, IApplicationCallback& cb, + const std::string& uniqueName, const std::string& friendlyName, const std::string& pname, - const std::vector& args); + const std::vector& args, + bool singleInstance=true); extern IApplication* APP; #define IApplicationInstance() APP static inline IApplication* IApplicationBootstrap(IApplication::EPlatformType platform, IApplicationCallback& cb, + const std::string& uniqueName, const std::string& friendlyName, - int argc, char** argv) + int argc, char** argv, + bool singleInstance=true) { if (APP) return APP; std::vector args; for (int i=1 ; i +#include + +DBusConnection* registerDBus(const char* appName, bool& isFirst) +{ + isFirst = true; + DBusError err = {}; + dbus_error_init(&err); + + /* connect to the bus and check for errors */ + DBusConnection* conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) + { + fprintf(stderr, "DBus Connection Error (%s)\n", err.message); + dbus_error_free(&err); + } + if (NULL == conn) + return NULL; + + /* request our name on the bus and check for errors */ + char busName[256]; + snprintf(busName, 256, "boo.%s.unique", appName); + int ret = dbus_bus_request_name(conn, busName, DBUS_NAME_FLAG_DO_NOT_QUEUE , &err); + if (dbus_error_is_set(&err)) + { + fprintf(stderr, "DBus Name Error (%s)\n", err.message); + dbus_error_free(&err); + dbus_connection_close(conn); + return NULL; + } + if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) + isFirst = false; + + return conn; + +} + namespace boo { IApplication* APP = NULL; IApplication* IApplicationBootstrap(IApplication::EPlatformType platform, IApplicationCallback& cb, + const std::string& uniqueName, const std::string& friendlyName, const std::string& pname, - const std::vector& args) + const std::vector& args, + bool singleInstance) { if (!APP) { if (platform == IApplication::PLAT_WAYLAND) - APP = new CApplicationWayland(cb, friendlyName, pname, args); + APP = new CApplicationWayland(cb, uniqueName, friendlyName, pname, args, singleInstance); else if (platform == IApplication::PLAT_XCB || platform == IApplication::PLAT_AUTO) - APP = new CApplicationXCB(cb, friendlyName, pname, args); + APP = new CApplicationXCB(cb, uniqueName, friendlyName, pname, args, singleInstance); else return NULL; } diff --git a/src/CApplicationWayland.hpp b/src/CApplicationWayland.hpp index f174349..54dd06a 100644 --- a/src/CApplicationWayland.hpp +++ b/src/CApplicationWayland.hpp @@ -4,6 +4,9 @@ #include "IApplication.hpp" +#include +DBusConnection* registerDBus(const char* appName, bool& isFirst); + namespace boo { @@ -12,10 +15,12 @@ IWindow* _CWindowWaylandNew(const std::string& title); class CApplicationWayland final : public IApplication { IApplicationCallback& m_callback; + const std::string m_uniqueName; const std::string m_friendlyName; const std::string m_pname; const std::vector m_args; - + bool m_singleInstance; + void _deletedWindow(IWindow* window) { (void)window; @@ -23,13 +28,17 @@ class CApplicationWayland final : public IApplication public: CApplicationWayland(IApplicationCallback& callback, + const std::string& uniqueName, const std::string& friendlyName, const std::string& pname, - const std::vector& args) + const std::vector& args, + bool singleInstance) : m_callback(callback), + m_uniqueName(uniqueName), m_friendlyName(friendlyName), m_pname(pname), - m_args(args) + m_args(args), + m_singleInstance(singleInstance) {} EPlatformType getPlatformType() const @@ -46,6 +55,16 @@ public: { } + + const std::string& getUniqueName() const + { + return m_uniqueName; + } + + const std::string& getFriendlyName() const + { + return m_friendlyName; + } const std::string& getProcessName() const { diff --git a/src/CApplicationWin32.cpp b/src/CApplicationWin32.cpp index 67f1842..ba53335 100644 --- a/src/CApplicationWin32.cpp +++ b/src/CApplicationWin32.cpp @@ -21,6 +21,7 @@ class CApplicationWin32 final : public IApplication const std::string m_pname; const std::vector m_args; std::unordered_map m_allWindows; + bool m_singleInstance; void _deletedWindow(IWindow* window) { @@ -32,11 +33,13 @@ public: CApplicationWin32(const IApplicationCallback& callback, const std::string& friendlyName, const std::string& pname, - const std::vector& args) + const std::vector& args, + bool singleInstance) : m_callback(callback), m_friendlyName(friendlyName), m_pname(pname), - m_args(args) + m_args(args), + m_singleInstance(singleInstance) {} EPlatformType getPlatformType() const @@ -95,14 +98,15 @@ IApplication* IApplicationBootstrap(IApplication::EPlatformType platform, IApplicationCallback& cb, const std::string& friendlyName, const std::string& pname, - const std::vector& args) + const std::vector& args, + bool singleInstance) { if (!APP) { if (platform != IApplication::PLAT_WIN32 && platform != IApplication::PLAT_AUTO) return NULL; - APP = new CApplicationWin32(cb, friendlyName, pname, args); + APP = new CApplicationWin32(cb, friendlyName, pname, args, singleInstance); } return APP; } diff --git a/src/CApplicationXCB.hpp b/src/CApplicationXCB.hpp index 26b1e6b..72c4534 100644 --- a/src/CApplicationXCB.hpp +++ b/src/CApplicationXCB.hpp @@ -12,6 +12,11 @@ #include #undef explicit +#include +DBusConnection* registerDBus(const char* appName, bool& isFirst); + +#include + namespace boo { @@ -113,14 +118,19 @@ IWindow* _CWindowXCBNew(const std::string& title, xcb_connection_t* conn); class CApplicationXCB final : public IApplication { IApplicationCallback& m_callback; + const std::string m_uniqueName; const std::string m_friendlyName; const std::string m_pname; const std::vector m_args; + /* DBus single-instance */ + bool m_singleInstance; + DBusConnection* m_dbus = NULL; + /* All windows */ std::unordered_map m_windows; - xcb_connection_t* m_xcbConn; + xcb_connection_t* m_xcbConn = NULL; bool m_running; void _deletedWindow(IWindow* window) @@ -130,14 +140,62 @@ class CApplicationXCB final : public IApplication public: CApplicationXCB(IApplicationCallback& callback, + const std::string& uniqueName, const std::string& friendlyName, const std::string& pname, - const std::vector& args) + const std::vector& args, + bool singleInstance) : m_callback(callback), + m_uniqueName(uniqueName), m_friendlyName(friendlyName), m_pname(pname), - m_args(args) + m_args(args), + m_singleInstance(singleInstance) { + /* DBus single instance registration */ + bool isFirst; + m_dbus = registerDBus(uniqueName.c_str(), isFirst); + if (m_singleInstance) + { + if (!isFirst) + { + /* This is a duplicate instance, send signal and return */ + if (args.size()) + { + /* create a signal & check for errors */ + DBusMessage* + msg = dbus_message_new_signal("/boo/signal/FileHandler", + "boo.signal.FileHandling", + "Open"); + + /* append arguments onto signal */ + DBusMessageIter argsIter; + dbus_message_iter_init_append(msg, &argsIter); + for (const std::string& arg : args) + { + const char* sigvalue = arg.c_str(); + dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &sigvalue); + } + + /* send the message and flush the connection */ + dbus_uint32_t serial; + dbus_connection_send(m_dbus, msg, &serial); + dbus_connection_flush(m_dbus); + dbus_message_unref(msg); + } + return; + } + else + { + /* This is the first instance, register for signal */ + // add a rule for which messages we want to see + DBusError err = {}; + dbus_bus_add_match(m_dbus, "type='signal',interface='boo.signal.FileHandling'", &err); + dbus_connection_flush(m_dbus); + } + } + + /* Open X connection */ m_xcbConn = xcb_connect(NULL, NULL); /* The xkb extension requests that the X server does not @@ -157,9 +215,6 @@ public: if (xiReply) XINPUT_OPCODE = xiReply->major_opcode; - - - } ~CApplicationXCB() @@ -174,24 +229,73 @@ public: void run() { + if (!m_xcbConn) + return; + xcb_generic_event_t* event; m_running = true; m_callback.appLaunched(this); xcb_flush(m_xcbConn); - while (m_running && (event = xcb_wait_for_event(m_xcbConn))) + int xcbFd = xcb_get_file_descriptor(m_xcbConn); + int dbusFd; + dbus_connection_get_unix_fd(m_dbus, &dbusFd); + int maxFd = MAX(xcbFd, dbusFd); + + while (m_running) { - bool windowEvent; - xcb_window_t evWindow = getWindowOfEvent(event, windowEvent); - //fprintf(stderr, "EVENT %d\n", XCB_EVENT_RESPONSE_TYPE(event)); - if (windowEvent) + fd_set fds; + FD_ZERO(&fds); + FD_SET(xcbFd, &fds); + FD_SET(dbusFd, &fds); + select(maxFd+1, &fds, NULL, NULL, NULL); + + if (FD_ISSET(xcbFd, &fds)) { - auto window = m_windows.find(evWindow); - if (window != m_windows.end()) - window->second->_incomingEvent(event); + event = xcb_poll_for_event(m_xcbConn); + if (!event) + break; + + bool windowEvent; + xcb_window_t evWindow = getWindowOfEvent(event, windowEvent); + //fprintf(stderr, "EVENT %d\n", XCB_EVENT_RESPONSE_TYPE(event)); + if (windowEvent) + { + auto window = m_windows.find(evWindow); + if (window != m_windows.end()) + window->second->_incomingEvent(event); + } + free(event); + } + + if (FD_ISSET(dbusFd, &fds)) + { + DBusMessage* msg; + dbus_connection_read_write(m_dbus, 0); + while ((msg = dbus_connection_pop_message(m_dbus))) + { + + /* check if the message is a signal from the correct interface and with the correct name */ + if (dbus_message_is_signal(msg, "boo.signal.FileHandling", "Open")) + { + /* read the parameters */ + std::vector paths; + DBusMessageIter iter; + dbus_message_iter_init(msg, &iter); + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) + { + const char* argVal; + dbus_message_iter_get_basic(&iter, &argVal); + paths.push_back(argVal); + dbus_message_iter_next(&iter); + } + m_callback.appFilesOpen(this, paths); + } + dbus_message_unref(msg); + } } - free(event); } + m_callback.appQuitting(this); } @@ -199,6 +303,16 @@ public: { m_running = false; } + + const std::string& getUniqueName() const + { + return m_uniqueName; + } + + const std::string& getFriendlyName() const + { + return m_friendlyName; + } const std::string& getProcessName() const { diff --git a/test/main.cpp b/test/main.cpp index f81e620..f00112c 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -123,10 +123,12 @@ struct CTestApplicationCallback : public IApplicationCallback { delete mainWindow; } - bool appFileOpen(IApplication*, const std::string& path) + void appFilesOpen(IApplication*, const std::vector& paths) { - printf("OPENING: %s\n", path.c_str()); - return true; + fprintf(stderr, "OPENING: "); + for (const std::string& path : paths) + fprintf(stderr, "%s ", path.c_str()); + fprintf(stderr, "\n"); } }; @@ -135,7 +137,8 @@ struct CTestApplicationCallback : public IApplicationCallback int main(int argc, char** argv) { boo::CTestApplicationCallback appCb; - boo::IApplication* app = IApplicationBootstrap(boo::IApplication::PLAT_AUTO, appCb, "RWK", argc, argv); + boo::IApplication* app = IApplicationBootstrap(boo::IApplication::PLAT_AUTO, + appCb, "rwk", "RWK", argc, argv); app->run(); delete app; printf("IM DYING!!\n");