diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5f3c364e..935ffaa1 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1,15 +1,20 @@ cmake_minimum_required(VERSION 3.10) project(launcher) +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + enable_language(OBJC) + enable_language(OBJCXX) +endif () + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) if (CMAKE_BUILD_TYPE STREQUAL "Debug") -# if (NOT DEFINED SANITIZE) -# set(SANITIZE YES) -# endif () + # if (NOT DEFINED SANITIZE) + # set(SANITIZE YES) + # endif () endif () if (SANITIZE STREQUAL "YES") diff --git a/launcher/gui/CMakeLists.txt b/launcher/gui/CMakeLists.txt index 3bf05683..c700b880 100644 --- a/launcher/gui/CMakeLists.txt +++ b/launcher/gui/CMakeLists.txt @@ -1,15 +1,27 @@ -add_executable(launcher +add_executable(DhfsLauncher src/LauncherApp.cpp src/GLauncherApp.cpp src/LauncherAppMainFrame.cpp src/DhfsGuiInstance.cpp src/DhfsGuiInstance.hpp + src/DhfsWxServer.cpp + src/DhfsWxServer.hpp + src/DhfsWxConnection.cpp + src/DhfsWxConnection.hpp ) -target_link_libraries(launcher ${wxWidgets_LIBRARIES}) -target_link_libraries(launcher backend utils) +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_sources(DhfsLauncher PRIVATE + src/macos/utils.m + src/macos/utils.h + ) +endif () -set_target_properties(launcher PROPERTIES + +target_link_libraries(DhfsLauncher ${wxWidgets_LIBRARIES}) +target_link_libraries(DhfsLauncher backend utils) + +set_target_properties(DhfsLauncher PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_GUI_IDENTIFIER "com.usatiuk.dhfs.launcher" MACOSX_BUNDLE_BUNDLE_NAME "DHFS Launcher" diff --git a/launcher/gui/src/DhfsGuiInstance.cpp b/launcher/gui/src/DhfsGuiInstance.cpp index 995fa44f..6b7c060f 100644 --- a/launcher/gui/src/DhfsGuiInstance.cpp +++ b/launcher/gui/src/DhfsGuiInstance.cpp @@ -6,18 +6,19 @@ #include "LauncherAppMainFrame.h" -DhfsGuiInstance::DhfsGuiInstance(LauncherAppMainFrame& parent): _parent(parent) { +wxDEFINE_EVENT(NEW_LINE_OUTPUT_EVENT, wxCommandEvent); +wxDEFINE_EVENT(DHFS_STATE_CHANGE_EVENT, wxCommandEvent); + +DhfsGuiInstance::DhfsGuiInstance(wxEvtHandler& parent): _evtHandler(parent) { } void DhfsGuiInstance::OnStateChange() { - wxCommandEvent* event = new wxCommandEvent(DHFS_STATE_CHANGE_EVENT, _parent.GetId()); - event->SetEventObject(&_parent); - _parent.GetEventHandler()->QueueEvent(event); + wxCommandEvent* event = new wxCommandEvent(DHFS_STATE_CHANGE_EVENT); + _evtHandler.QueueEvent(event); } void DhfsGuiInstance::OnRead(std::string s) { - wxCommandEvent* event = new wxCommandEvent(NEW_LINE_OUTPUT_EVENT, _parent.GetId()); - event->SetEventObject(&_parent); + wxCommandEvent* event = new wxCommandEvent(NEW_LINE_OUTPUT_EVENT); event->SetString(std::move(s)); - _parent.GetEventHandler()->QueueEvent(event); + _evtHandler.QueueEvent(event); } diff --git a/launcher/gui/src/DhfsGuiInstance.hpp b/launcher/gui/src/DhfsGuiInstance.hpp index 2a809b8d..6de8b152 100644 --- a/launcher/gui/src/DhfsGuiInstance.hpp +++ b/launcher/gui/src/DhfsGuiInstance.hpp @@ -6,19 +6,19 @@ #define DHFSGUIINSTANCE_HPP #include "DhfsInstance.hpp" - -class LauncherAppMainFrame; +wxDECLARE_EVENT(NEW_LINE_OUTPUT_EVENT, wxCommandEvent); +wxDECLARE_EVENT(DHFS_STATE_CHANGE_EVENT, wxCommandEvent); class DhfsGuiInstance : public DhfsInstance { public: - DhfsGuiInstance(LauncherAppMainFrame& parent); + DhfsGuiInstance(wxEvtHandler& parent); void OnStateChange() override; void OnRead(std::string s) override; protected: - LauncherAppMainFrame& _parent; + wxEvtHandler& _evtHandler; }; diff --git a/launcher/gui/src/DhfsWxConnection.cpp b/launcher/gui/src/DhfsWxConnection.cpp new file mode 100644 index 00000000..e69cdb57 --- /dev/null +++ b/launcher/gui/src/DhfsWxConnection.cpp @@ -0,0 +1,21 @@ +// +// Created by Stepan Usatiuk on 29.06.2025. +// + +#include "DhfsWxConnection.hpp" + +#include + +#include "wx/app.h" +#include "LauncherApp.h" + +DhfsWxConnection::DhfsWxConnection() : wxConnection() { +} + +bool DhfsWxConnection::OnExec(const wxString& wx_uni_char_refs, const wxString& wx_uni_chars) { + std::cout << "DhfsWxConnection::OnExec called with topic: " << wx_uni_char_refs << " and item: " << wx_uni_chars << + std::endl; + + wxGetApp().Open(); + return true; +} diff --git a/launcher/gui/src/DhfsWxConnection.hpp b/launcher/gui/src/DhfsWxConnection.hpp new file mode 100644 index 00000000..8d5a2bda --- /dev/null +++ b/launcher/gui/src/DhfsWxConnection.hpp @@ -0,0 +1,18 @@ +// +// Created by Stepan Usatiuk on 29.06.2025. +// + +#ifndef DHFSWXCONNECTION_HPP +#define DHFSWXCONNECTION_HPP +#include "wx/ipc.h" + + +class DhfsWxConnection : public wxConnection { +public: + DhfsWxConnection(); + + bool OnExec(const wxString&, const wxString&) override; +}; + + +#endif //DHFSWXCONNECTION_HPP diff --git a/launcher/gui/src/DhfsWxServer.cpp b/launcher/gui/src/DhfsWxServer.cpp new file mode 100644 index 00000000..03787732 --- /dev/null +++ b/launcher/gui/src/DhfsWxServer.cpp @@ -0,0 +1,11 @@ +// +// Created by Stepan Usatiuk on 29.06.2025. +// + +#include "DhfsWxServer.hpp" + +#include "DhfsWxConnection.hpp" + +wxConnectionBase* DhfsWxServer::OnAcceptConnection(const wxString& topic) { + return new DhfsWxConnection(); +} diff --git a/launcher/gui/src/DhfsWxServer.hpp b/launcher/gui/src/DhfsWxServer.hpp new file mode 100644 index 00000000..e06cff96 --- /dev/null +++ b/launcher/gui/src/DhfsWxServer.hpp @@ -0,0 +1,16 @@ +// +// Created by Stepan Usatiuk on 29.06.2025. +// + +#ifndef DHFSWXSERVER_HPP +#define DHFSWXSERVER_HPP + +#include "wx/ipc.h" + +class DhfsWxServer : public wxServer { +public: + wxConnectionBase* OnAcceptConnection(const wxString& topic) override; +}; + + +#endif //DHFSWXSERVER_HPP diff --git a/launcher/gui/src/GLauncherApp.cpp b/launcher/gui/src/GLauncherApp.cpp index 3d54c810..e2e26c96 100644 --- a/launcher/gui/src/GLauncherApp.cpp +++ b/launcher/gui/src/GLauncherApp.cpp @@ -53,7 +53,7 @@ MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, co m_panel1->SetSizer( bSizer2 ); m_panel1->Layout(); bSizer2->Fit( m_panel1 ); - m_notebook1->AddPage( m_panel1, _("Info"), true ); + m_notebook1->AddPage( m_panel1, _("Info"), false ); m_panel3 = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxGridSizer* gSizer1; gSizer1 = new wxGridSizer( 1, 1, 0, 0 ); @@ -65,7 +65,7 @@ MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, co m_panel3->SetSizer( gSizer1 ); m_panel3->Layout(); gSizer1->Fit( m_panel3 ); - m_notebook1->AddPage( m_panel3, _("Logs"), false ); + m_notebook1->AddPage( m_panel3, _("Logs"), true ); m_panel2 = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* bSizer5; bSizer5 = new wxBoxSizer( wxVERTICAL ); @@ -139,7 +139,7 @@ MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, co m_panel5 = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_notebook1->AddPage( m_panel5, _("Web UI"), false ); - bSizer3->Add( m_notebook1, 1, wxEXPAND, 5 ); + bSizer3->Add( m_notebook1, 1, wxBOTTOM|wxEXPAND|wxTOP, 5 ); this->SetSizer( bSizer3 ); @@ -148,6 +148,9 @@ MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, co this->Centre( wxBOTH ); // Connect Events + this->Connect( wxEVT_ACTIVATE, wxActivateEventHandler( MainFrame::OnActivate ) ); + this->Connect( wxEVT_ACTIVATE_APP, wxActivateEventHandler( MainFrame::OnActivateApp ) ); + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MainFrame::OnClose ) ); m_notebook1->Connect( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, wxNotebookEventHandler( MainFrame::OnNotebookPageChanged ), NULL, this ); m_notebook1->Connect( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, wxNotebookEventHandler( MainFrame::OnNotebookPageChanging ), NULL, this ); m_startStopButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainFrame::OnStartStopButtonClick ), NULL, this ); diff --git a/launcher/gui/src/GLauncherApp.h b/launcher/gui/src/GLauncherApp.h index 4df6a8db..c1d65548 100644 --- a/launcher/gui/src/GLauncherApp.h +++ b/launcher/gui/src/GLauncherApp.h @@ -59,6 +59,9 @@ class MainFrame : public wxFrame wxPanel* m_panel5; // Virtual event handlers, override them in your derived class + virtual void OnActivate( wxActivateEvent& event ) { event.Skip(); } + virtual void OnActivateApp( wxActivateEvent& event ) { event.Skip(); } + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } virtual void OnNotebookPageChanged( wxNotebookEvent& event ) { event.Skip(); } virtual void OnNotebookPageChanging( wxNotebookEvent& event ) { event.Skip(); } virtual void OnStartStopButtonClick( wxCommandEvent& event ) { event.Skip(); } diff --git a/launcher/gui/src/LauncherApp.cpp b/launcher/gui/src/LauncherApp.cpp index c46e9ebe..f01cdedc 100644 --- a/launcher/gui/src/LauncherApp.cpp +++ b/launcher/gui/src/LauncherApp.cpp @@ -3,12 +3,6 @@ // // For compilers that don't support precompilation, include "wx/wx.h" -#include "wx/wxprec.h" - -#ifndef WX_PRECOMP -# include "wx/wx.h" -#endif - #include "wx/notebook.h" #include "LauncherApp.h" @@ -16,20 +10,34 @@ #include "LauncherAppMainFrame.h" #include "wx/taskbar.h" #include +#include "wx/snglinst.h" + IMPLEMENT_APP(LauncherApp) // This is executed upon startup, like 'main()' in non-wxWidgets programs. bool LauncherApp::OnInit() { - wxFileConfig::Get()->SetAppName("DHFS"); + m_checker = new wxSingleInstanceChecker; + if (m_checker->IsAnotherRunning()) { + // wxLogError(_("Another program instance is already running, aborting.")); + + delete m_checker; // OnExit() won't be called if we return false + m_checker = NULL; + + auto clinet = new wxClient(); + auto conn = clinet->MakeConnection("dhfs", "/Users/stepus53/dhfs-sock", "dhfs"); + conn->Execute("wakeup"); + + return false; + } + + m_server.Create("/Users/stepus53/dhfs-sock"); wxFrame* frame = new LauncherAppMainFrame(NULL); frame->Show(true); SetTopWindow(frame); - // wxTaskBarIcon* tb = new wxTaskBarIcon(); - // auto img = new wxImage(32, 32, false); - // img->Clear(128); - // tb->SetIcon(*(new wxBitmapBundle(*(new wxBitmap(*img)))), "e"); + Bind(NEW_LINE_OUTPUT_EVENT, &LauncherApp::forwardEventToTopWindow, this); + Bind(DHFS_STATE_CHANGE_EVENT, &LauncherApp::forwardEventToTopWindow, this); return true; } @@ -42,3 +50,41 @@ bool LauncherApp::OnExceptionInMainLoop() { } return true; } + +int LauncherApp::OnExit() { + delete m_checker; + + return wxApp::OnExit(); +} + +void LauncherApp::Open() { + if (m_topWindow) { + m_topWindow->SetFocus(); + m_topWindow->Raise(); + if (auto frame = dynamic_cast(m_topWindow)) { + frame->RequestUserAttention(); + } + } else { + wxFrame* frame = new LauncherAppMainFrame(NULL); + frame->Show(true); + SetTopWindow(frame); + } +} + +void LauncherApp::OnTopFrameClose(wxCloseEvent& event) { + SetTopWindow(nullptr); +} + +void LauncherApp::forwardEventToTopWindow(wxCommandEvent& event) { + if (m_topWindow) { + m_topWindow->GetEventHandler()->ProcessEvent(event); + } else { + event.Skip(); + } +} + +#ifdef __APPLE__ +void LauncherApp::MacReopenApp() { + this->Open(); +} +#endif diff --git a/launcher/gui/src/LauncherApp.h b/launcher/gui/src/LauncherApp.h index 830702a7..4b3cdb6c 100644 --- a/launcher/gui/src/LauncherApp.h +++ b/launcher/gui/src/LauncherApp.h @@ -5,13 +5,38 @@ #ifndef HELLOWORLDAPP_H #define HELLOWORLDAPP_H +#include "DhfsGuiInstance.hpp" +#include "wx/wx.h" + +#include "DhfsWxServer.hpp" + +class wxSingleInstanceChecker; + // The HelloWorldApp class. This class shows a window // containing a statusbar with the text "Hello World" class LauncherApp : public wxApp { public: virtual bool OnInit() override; + virtual int OnExit() override; + virtual bool OnExceptionInMainLoop() override; + +#ifdef __APPLE__ + void MacReopenApp() override; +#endif + + void Open(); + + void OnTopFrameClose(wxCloseEvent& event); + +private: + wxSingleInstanceChecker* m_checker = nullptr; + DhfsWxServer m_server; + void forwardEventToTopWindow(wxCommandEvent& event); + +public: + DhfsGuiInstance m_dhfsInstance{*this}; }; DECLARE_APP(LauncherApp) diff --git a/launcher/gui/src/LauncherAppMainFrame.cpp b/launcher/gui/src/LauncherAppMainFrame.cpp index 32a4b575..9731d9ef 100644 --- a/launcher/gui/src/LauncherAppMainFrame.cpp +++ b/launcher/gui/src/LauncherAppMainFrame.cpp @@ -5,10 +5,12 @@ #include #include -#include "Exception.h" +#ifdef __APPLE__ +#include "macos/utils.h" +#endif -wxDEFINE_EVENT(NEW_LINE_OUTPUT_EVENT, wxCommandEvent); -wxDEFINE_EVENT(DHFS_STATE_CHANGE_EVENT, wxCommandEvent); +#include "LauncherApp.h" +#include "Exception.h" std::string getBundlePath() { if (wxGetenv("DHFS_BUNDLE_PATH") == NULL) @@ -39,10 +41,17 @@ LauncherAppMainFrame::LauncherAppMainFrame(wxWindow* parent) wxFontWeight::wxFONTWEIGHT_NORMAL); m_logOutputTextCtrl->SetFont(font); updateState(); +#ifdef __APPLE__ + SetAppAsRegular(); +#endif +} + +static DhfsInstance& getDhfsInstance() { + return wxGetApp().m_dhfsInstance; } void LauncherAppMainFrame::updateState() { - switch (_dhfsInstance.state()) { + switch (getDhfsInstance().state()) { case DhfsInstanceState::RUNNING: { m_statusText->SetLabel("Running"); m_startStopButton->SetLabel("Stop"); @@ -53,24 +62,28 @@ void LauncherAppMainFrame::updateState() { m_webView->LoadURL("http://localhost:8080"); } + wxGetApp().SetExitOnFrameDelete(false); break; } case DhfsInstanceState::STARTING: { m_statusText->SetLabel("Starting"); m_startStopButton->SetLabel("Stop"); m_statusBar1->SetStatusText("Starting", 0); + wxGetApp().SetExitOnFrameDelete(false); break; } case DhfsInstanceState::STOPPED: { m_statusText->SetLabel("Stopped"); m_startStopButton->SetLabel("Start"); m_statusBar1->SetStatusText("Stopped", 0); + wxGetApp().SetExitOnFrameDelete(true); break; } case DhfsInstanceState::STOPPING: { m_statusText->SetLabel("Stopping"); m_startStopButton->SetLabel("Kill"); m_statusBar1->SetStatusText("Stopping", 0); + wxGetApp().SetExitOnFrameDelete(false); break; } default: @@ -79,10 +92,10 @@ void LauncherAppMainFrame::updateState() { } void LauncherAppMainFrame::OnStartStopButtonClick(wxCommandEvent& event) { - switch (_dhfsInstance.state()) { + switch (getDhfsInstance().state()) { case DhfsInstanceState::RUNNING: case DhfsInstanceState::STARTING: { - _dhfsInstance.stop(); + getDhfsInstance().stop(); break; } case DhfsInstanceState::STOPPED: { @@ -94,7 +107,7 @@ void LauncherAppMainFrame::OnStartStopButtonClick(wxCommandEvent& event) { options.jar_path = getBundlePath() + "/app/Server/quarkus-run.jar"; options.webui_path = getBundlePath() + "/app/Webui"; - _dhfsInstance.start(options); + getDhfsInstance().start(options); break; } case DhfsInstanceState::STOPPING: { @@ -149,3 +162,20 @@ void LauncherAppMainFrame::prepareWebview() { m_webView->LoadURL("http://localhost:8080"); m_panel5->Layout(); } + + +void LauncherAppMainFrame::OnActivate(wxActivateEvent& event) { + MainFrame::OnActivate(event); +} + +void LauncherAppMainFrame::OnActivateApp(wxActivateEvent& event) { + MainFrame::OnActivateApp(event); +} + +void LauncherAppMainFrame::OnClose(wxCloseEvent& event) { +#ifdef __APPLE__ + SetAppAsAccessory(); +#endif + wxGetApp().OnTopFrameClose(event); + MainFrame::OnClose(event); +} diff --git a/launcher/gui/src/LauncherAppMainFrame.h b/launcher/gui/src/LauncherAppMainFrame.h index 00272542..a8b81b50 100644 --- a/launcher/gui/src/LauncherAppMainFrame.h +++ b/launcher/gui/src/LauncherAppMainFrame.h @@ -18,9 +18,6 @@ static constexpr auto kJavaHomeSettingsKey = "DHFS/JavaHome"; static constexpr auto kMountPointSettingsKey = "DHFS/MountDir"; static constexpr auto kDataDirSettingsKey = "DHFS/DataDir"; -wxDECLARE_EVENT(NEW_LINE_OUTPUT_EVENT, wxCommandEvent); -wxDECLARE_EVENT(DHFS_STATE_CHANGE_EVENT, wxCommandEvent); - /** Implementing MainFrame */ class LauncherAppMainFrame : public MainFrame { protected: @@ -44,8 +41,15 @@ protected: void updateState(); void unloadWebview(); + void prepareWebview(); + void OnActivate(wxActivateEvent& event) override; + + void OnActivateApp(wxActivateEvent& event) override; + + void OnClose(wxCloseEvent& event) override; + public: /** Constructor */ LauncherAppMainFrame(wxWindow* parent); @@ -55,8 +59,6 @@ public: private: wxWebView* m_webView = nullptr; wxGridSizer* m_webViewSizer; - - DhfsGuiInstance _dhfsInstance{*this}; }; #endif // __LauncherAppMainFrame__ diff --git a/launcher/gui/src/launcher.fbp b/launcher/gui/src/launcher.fbp index 25a394a8..fd7dcb1d 100644 --- a/launcher/gui/src/launcher.fbp +++ b/launcher/gui/src/launcher.fbp @@ -60,6 +60,9 @@ DHFS wxTAB_TRAVERSAL 1 + OnActivate + OnActivateApp + OnClose @@ -91,7 +94,7 @@ none 5 - wxEXPAND + wxBOTTOM|wxEXPAND|wxTOP 1 1 diff --git a/launcher/gui/src/macos/utils.h b/launcher/gui/src/macos/utils.h new file mode 100644 index 00000000..c2cc98cb --- /dev/null +++ b/launcher/gui/src/macos/utils.h @@ -0,0 +1,20 @@ +// +// Created by Stepan Usatiuk on 29.06.2025. +// + +#ifndef UTILS_H +#define UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +void SetAppAsRegular(void); + +void SetAppAsAccessory(void); + +#ifdef __cplusplus +} +#endif + +#endif //UTILS_H diff --git a/launcher/gui/src/macos/utils.m b/launcher/gui/src/macos/utils.m new file mode 100644 index 00000000..9911a052 --- /dev/null +++ b/launcher/gui/src/macos/utils.m @@ -0,0 +1,11 @@ +#import +#import "utils.h" + +void SetAppAsRegular(void) { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; +} + +void SetAppAsAccessory(void) { + [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; +} +