diff --git a/ChangeLog b/ChangeLog index acb1de6bd..da93ae826 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,7 @@ Enhancements: - #7485 Use `.venv` dir for as Python venv and cache - #7488 Only wait for elevated process to end when arg is set - #7489 Add `--config-toml` arg for TOML config file +- #7492 Add warnings for `libei` and `libportal` # 1.15.1 diff --git a/config.yaml b/config.yaml index fa477043e..091f486cd 100644 --- a/config.yaml +++ b/config.yaml @@ -161,7 +161,8 @@ config: sudo apt-get install -y \ python3-attr \ python3-jinja2 \ - libsystemd-dev + libsystemd-dev && + pip install attrs jinja2 ubuntu: *debian_libei linuxmint: *debian_libei @@ -170,7 +171,8 @@ config: sudo dnf install -y \ python3-attrs \ python3-jinja2 \ - systemd-devel + systemd-devel && + pip install attrs jinja2 rhel: *fedora_libei rocky: *fedora_libei diff --git a/cspell.json b/cspell.json index 68bd9e767..38caa3183 100644 --- a/cspell.json +++ b/cspell.json @@ -25,6 +25,7 @@ "hotspots", "Hutterer", "ifdef", + "INPUTCAPTURE", "integtests", "keychain", "Keychains", @@ -69,7 +70,8 @@ "vmactions", "Volker", "whot", - "winget" + "winget", + "XWINDOWS" ], "ignoreWords": [], "import": [] diff --git a/src/gui/res/Synergy.qrc b/res/gui/Synergy.qrc similarity index 95% rename from src/gui/res/Synergy.qrc rename to res/gui/Synergy.qrc index e7bb40f31..d4c70383e 100644 --- a/src/gui/res/Synergy.qrc +++ b/res/gui/Synergy.qrc @@ -1,5 +1,5 @@ - + icons/16x16/synergy.png icons/64x64/video-display.png icons/64x64/user-trash.png diff --git a/src/gui/res/icons/16x16/padlock.png b/res/gui/icons/16x16/padlock.png similarity index 100% rename from src/gui/res/icons/16x16/padlock.png rename to res/gui/icons/16x16/padlock.png diff --git a/src/gui/res/icons/16x16/synergy.png b/res/gui/icons/16x16/synergy.png similarity index 100% rename from src/gui/res/icons/16x16/synergy.png rename to res/gui/icons/16x16/synergy.png diff --git a/src/gui/res/icons/16x16/warning.png b/res/gui/icons/16x16/warning.png similarity index 100% rename from src/gui/res/icons/16x16/warning.png rename to res/gui/icons/16x16/warning.png diff --git a/src/gui/res/icons/256x256/synergy.ico b/res/gui/icons/256x256/synergy.ico similarity index 100% rename from src/gui/res/icons/256x256/synergy.ico rename to res/gui/icons/256x256/synergy.ico diff --git a/src/gui/res/icons/64x64/folder.png b/res/gui/icons/64x64/folder.png similarity index 100% rename from src/gui/res/icons/64x64/folder.png rename to res/gui/icons/64x64/folder.png diff --git a/src/gui/res/icons/64x64/synergy-dark.png b/res/gui/icons/64x64/synergy-dark.png similarity index 100% rename from src/gui/res/icons/64x64/synergy-dark.png rename to res/gui/icons/64x64/synergy-dark.png diff --git a/src/gui/res/icons/64x64/synergy-light.png b/res/gui/icons/64x64/synergy-light.png similarity index 100% rename from src/gui/res/icons/64x64/synergy-light.png rename to res/gui/icons/64x64/synergy-light.png diff --git a/src/gui/res/icons/64x64/user-trash.png b/res/gui/icons/64x64/user-trash.png similarity index 100% rename from src/gui/res/icons/64x64/user-trash.png rename to res/gui/icons/64x64/user-trash.png diff --git a/src/gui/res/icons/64x64/video-display.png b/res/gui/icons/64x64/video-display.png similarity index 100% rename from src/gui/res/icons/64x64/video-display.png rename to res/gui/icons/64x64/video-display.png diff --git a/src/gui/res/image/about-dark.png b/res/gui/image/about-dark.png similarity index 100% rename from src/gui/res/image/about-dark.png rename to res/gui/image/about-dark.png diff --git a/src/gui/res/image/about-light.png b/res/gui/image/about-light.png similarity index 100% rename from src/gui/res/image/about-light.png rename to res/gui/image/about-light.png diff --git a/src/gui/res/image/setup-blocker.png b/res/gui/image/setup-blocker.png similarity index 100% rename from src/gui/res/image/setup-blocker.png rename to res/gui/image/setup-blocker.png diff --git a/src/gui/res/image/welcome.png b/res/gui/image/welcome.png similarity index 100% rename from src/gui/res/image/welcome.png rename to res/gui/image/welcome.png diff --git a/src/gui/res/mac/Info.plist b/res/gui/mac/Info.plist similarity index 100% rename from src/gui/res/mac/Info.plist rename to res/gui/mac/Info.plist diff --git a/src/gui/res/mac/QSynergy.icns b/res/gui/mac/QSynergy.icns similarity index 100% rename from src/gui/res/mac/QSynergy.icns rename to res/gui/mac/QSynergy.icns diff --git a/src/gui/res/mac/Synergy.icns b/res/gui/mac/Synergy.icns similarity index 100% rename from src/gui/res/mac/Synergy.icns rename to res/gui/mac/Synergy.icns diff --git a/src/gui/res/win/Synergy.rc b/res/gui/win/Synergy.rc similarity index 100% rename from src/gui/res/win/Synergy.rc rename to res/gui/win/Synergy.rc diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b59266661..862d85d04 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -20,26 +20,23 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(res_dir ${CMAKE_SOURCE_DIR}/res/gui) +set(qrc_file ${res_dir}/Synergy.qrc) + file( GLOB - GUI_SOURCE_FILES + sources src/*.cpp src/*.h src/validators/* src/widgets/*) -file(GLOB GUI_UI_FILES src/*.ui) +file(GLOB ui_files src/*.ui) if(WIN32) - set(GUI_RC_FILES res/win/Synergy.rc ${CMAKE_BINARY_DIR}/src/version.rc) + set(rc_files ${res_dir}/win/Synergy.rc ${CMAKE_BINARY_DIR}/src/version.rc) endif() -add_executable( - ${target} WIN32 - ${GUI_SOURCE_FILES} - ${GUI_UI_FILES} - ${GUI_RC_FILES} - res/Synergy.qrc - ${QM_FILES}) +add_executable(${target} WIN32 ${sources} ${ui_files} ${rc_files} ${qrc_file}) # regular exe headers include_directories(./src) diff --git a/src/gui/src/AboutDialog.cpp b/src/gui/src/AboutDialog.cpp index 735f3e4d7..0838257b4 100644 --- a/src/gui/src/AboutDialog.cpp +++ b/src/gui/src/AboutDialog.cpp @@ -55,7 +55,7 @@ int AboutDialog::exec() { void AboutDialog::updateLogo() const { if (isDarkMode()) { qDebug("dark mode detected, showing dark logo"); - QPixmap logo(":/res/image/about-dark.png"); + QPixmap logo(":/image/about-dark.png"); if (!logo.isNull()) { m_pLabel_Logo->setPixmap(logo); } diff --git a/src/gui/src/AboutDialogBase.ui b/src/gui/src/AboutDialogBase.ui index 28c7a1b8d..ca04453ba 100644 --- a/src/gui/src/AboutDialogBase.ui +++ b/src/gui/src/AboutDialogBase.ui @@ -121,7 +121,7 @@ - :/res/image/about-light.png + :/image/about-light.png true @@ -249,9 +249,6 @@ - - - buttonOk diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 76beba556..737c38227 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -38,10 +38,14 @@ #include "gui/styles.h" #include "gui/tls/TlsFingerprint.h" #include "license/License.h" +#include "platform/wayland.h" #if defined(Q_OS_MAC) #include "gui/OSXHelpers.h" #endif +#if defined(Q_OS_LINUX) +#include "config.h" +#endif #include #include @@ -69,11 +73,11 @@ using CoreMode = CoreProcess::Mode; using CoreConnectionState = CoreProcess::ConnectionState; using CoreProcessState = CoreProcess::ProcessState; -const auto kIconFile16 = ":/res/icons/16x16/synergy.png"; +const auto kIconFile16 = ":/icons/16x16/synergy.png"; #ifdef Q_OS_MAC -const auto kLightIconFile = ":/res/icons/64x64/synergy-light.png"; -const auto kDarkIconFile = ":/res/icons/64x64/synergy-dark.png"; +const auto kLightIconFile = ":/icons/64x64/synergy-light.png"; +const auto kDarkIconFile = ":/icons/64x64/synergy-dark.png"; #endif // Q_OS_MAC MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig) @@ -584,6 +588,12 @@ void MainWindow::onCoreProcessStarting() { } } +#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) + if (synergy::platform::isWayland()) { + m_WaylandWarnings.showOnce(this, m_CoreProcess.mode()); + } +#endif + saveSettings(); } @@ -844,6 +854,10 @@ void MainWindow::updateStatus() { setStatus("Synergy is starting..."); break; + case RetryPending: + setStatus("Synergy will retry in a moment..."); + break; + case Stopping: setStatus("Synergy is stopping..."); break; @@ -896,7 +910,8 @@ void MainWindow::onCoreProcessStateChanged(CoreProcessState state) { } if (state == CoreProcessState::Started || - state == CoreProcessState::Starting) { + state == CoreProcessState::Starting || + state == CoreProcessState::RetryPending) { disconnect( m_pButtonToggleStart, &QPushButton::clicked, m_pActionStartCore, &QAction::trigger); diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 56eebabc1..8df40a178 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -36,6 +36,7 @@ #include "gui/core/ClientConnection.h" #include "gui/core/CoreProcess.h" #include "gui/core/ServerConnection.h" +#include "gui/core/WaylandWarnings.h" #include "gui/tls/TlsUtility.h" #include "ui_MainWindowBase.h" @@ -198,6 +199,7 @@ private: bool m_Quitting = false; synergy::gui::config::ServerConfigDialogState m_ServerConfigDialogState; bool m_SaveOnExit = true; + synergy::gui::core::WaylandWarnings m_WaylandWarnings; synergy::gui::ConfigScopes &m_ConfigScopes; AppConfig &m_AppConfig; diff --git a/src/gui/src/MainWindowBase.ui b/src/gui/src/MainWindowBase.ui index d736b082e..22d9150e8 100644 --- a/src/gui/src/MainWindowBase.ui +++ b/src/gui/src/MainWindowBase.ui @@ -501,7 +501,7 @@ - :/res/icons/16x16/padlock.png + :/icons/16x16/padlock.png @@ -740,9 +740,6 @@ m_pButtonApply m_pButtonToggleStart - - - m_pButtonToggleStart diff --git a/src/gui/src/ServerConfigDialogBase.ui b/src/gui/src/ServerConfigDialogBase.ui index bc3d4cf57..c2d4bf88e 100644 --- a/src/gui/src/ServerConfigDialogBase.ui +++ b/src/gui/src/ServerConfigDialogBase.ui @@ -71,7 +71,7 @@ - :/res/icons/64x64/user-trash.png + :/icons/64x64/user-trash.png @@ -1072,8 +1072,8 @@ Enabling this setting will disable the server config GUI. - - :/res/icons/64x64/folder.png:/res/icons/64x64/folder.png + + :/icons/64x64/folder.png:/icons/64x64/folder.png @@ -1179,9 +1179,6 @@ Enabling this setting will disable the server config GUI. m_pEditConfigFile m_pTabWidget - - - m_pButtonBox diff --git a/src/gui/src/SetupWizardBase.ui b/src/gui/src/SetupWizardBase.ui index 2a209c80d..88a141f58 100644 --- a/src/gui/src/SetupWizardBase.ui +++ b/src/gui/src/SetupWizardBase.ui @@ -60,7 +60,7 @@ - :/res/image/welcome.png + :/image/welcome.png 1 @@ -186,8 +186,5 @@ - - - diff --git a/src/lib/gui/CMakeLists.txt b/src/lib/gui/CMakeLists.txt index 3cbbbde6c..fcac62fc1 100644 --- a/src/lib/gui/CMakeLists.txt +++ b/src/lib/gui/CMakeLists.txt @@ -15,6 +15,9 @@ set(target gui) +set(res_dir ${CMAKE_SOURCE_DIR}/res/gui) +set(qrc_file ${res_dir}/Synergy.qrc) + set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) @@ -33,7 +36,8 @@ if(ADD_HEADERS_TO_SOURCES) list(APPEND sources ${headers}) endif() -add_library(${target} STATIC ${sources} ${ui_files}) +add_library(${target} STATIC ${sources} ${ui_files} ${qrc_file}) + target_link_libraries( ${target} license diff --git a/src/lib/gui/core/CoreProcess.cpp b/src/lib/gui/core/CoreProcess.cpp index 8c97fa28d..a2a2d057c 100644 --- a/src/lib/gui/core/CoreProcess.cpp +++ b/src/lib/gui/core/CoreProcess.cpp @@ -76,6 +76,8 @@ QString processStateToString(CoreProcess::ProcessState state) { return "stopping"; case Stopped: return "stopped"; + case RetryPending: + return "retry pending"; default: qFatal("invalid process state"); abort(); @@ -184,6 +186,14 @@ CoreProcess::CoreProcess( connect( &m_pDeps->process(), &QProcessProxy::readyReadStandardError, this, &CoreProcess::onProcessReadyReadStandardError); + + connect(&m_retryTimer, &QTimer::timeout, [this] { + if (m_processState == ProcessState::RetryPending) { + start(); + } else { + qDebug("retry cancelled, process state is not retry pending"); + } + }); } void CoreProcess::onIpcClientServiceReady() { @@ -227,7 +237,6 @@ void CoreProcess::onProcessReadyReadStandardError() { void CoreProcess::onProcessFinished(int exitCode, QProcess::ExitStatus) { const auto wasStarted = m_processState == ProcessState::Started; - setProcessState(ProcessState::Stopped); setConnectionState(ConnectionState::Disconnected); if (exitCode == 0) { @@ -238,7 +247,16 @@ void CoreProcess::onProcessFinished(int exitCode, QProcess::ExitStatus) { if (wasStarted) { qDebug("desktop process was running, retrying in %d ms", kRetryDelay); - QTimer::singleShot(kRetryDelay, [this] { start(); }); + + if (m_retryTimer.isActive()) { + m_retryTimer.stop(); + } + + setProcessState(ProcessState::RetryPending); + m_retryTimer.setSingleShot(true); + m_retryTimer.start(kRetryDelay); + } else { + setProcessState(ProcessState::Stopped); } } diff --git a/src/lib/gui/core/CoreProcess.h b/src/lib/gui/core/CoreProcess.h index 46f7c4cc4..37695371a 100644 --- a/src/lib/gui/core/CoreProcess.h +++ b/src/lib/gui/core/CoreProcess.h @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace synergy::gui { @@ -55,7 +56,13 @@ public: enum class Mode { None, Client, Server }; enum class Error { AddressMissing, StartFailed }; - enum class ProcessState { Starting, Started, Stopping, Stopped }; + enum class ProcessState { + Starting, + Started, + Stopping, + Stopped, + RetryPending + }; enum class ConnectionState { Disconnected, Connecting, Connected, Listening }; explicit CoreProcess( @@ -130,6 +137,7 @@ private: QMutex m_processMutex; QString m_secureSocketVersion = ""; std::optional m_lastProcessMode = std::nullopt; + QTimer m_retryTimer; }; } // namespace synergy::gui diff --git a/src/lib/gui/core/WaylandWarnings.cpp b/src/lib/gui/core/WaylandWarnings.cpp new file mode 100644 index 000000000..dd313d49e --- /dev/null +++ b/src/lib/gui/core/WaylandWarnings.cpp @@ -0,0 +1,65 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2024 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "WaylandWarnings.h" + +#include "messages.h" + +using namespace synergy::platform; + +namespace synergy::gui::core { + +// +// WaylandWarnings::Deps +// + +void WaylandWarnings::Deps::showWaylandLibraryError(QWidget *parent) { + messages::showWaylandLibraryError(parent); +} + +void WaylandWarnings::Deps::showWaylandExperimental(QWidget *parent) { + messages::showWaylandExperimental(parent); +} + +// +// WaylandWarnings +// + +void WaylandWarnings::showOnce( + QWidget *parent, CoreProcess::Mode mode, bool hasEi, bool hasPortal, + bool hasPortalInputCapture) { + + const auto portalIcProblem = + !hasPortalInputCapture && mode == CoreProcess::Mode::Server; + + if (!hasEi || !hasPortal || portalIcProblem) { + if (!m_errorShown) { + m_errorShown = true; + m_pDeps->showWaylandLibraryError(parent); + } else { + qWarning("missing required wayland lib(s) or feature"); + } + return; + } + + if (!m_warningShown) { + m_warningShown = true; + m_pDeps->showWaylandExperimental(parent); + } +} + +} // namespace synergy::gui::core diff --git a/src/lib/gui/core/WaylandWarnings.h b/src/lib/gui/core/WaylandWarnings.h new file mode 100644 index 000000000..473b59410 --- /dev/null +++ b/src/lib/gui/core/WaylandWarnings.h @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2024 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "CoreProcess.h" +#include "platform/wayland.h" + +namespace synergy::gui::core { + +class WaylandWarnings { +public: + struct Deps { + virtual ~Deps() = default; + virtual void showWaylandExperimental(QWidget *parent); + virtual void showWaylandLibraryError(QWidget *parent); + }; + + explicit WaylandWarnings( + std::shared_ptr deps = std::make_shared()) + : m_pDeps(deps) {} + + void showOnce( + QWidget *parent, CoreProcess::Mode mode, bool hasEi = platform::kHasEi, + bool hasPortal = platform::kHasPortal, + bool hasPortalInputCapture = platform::kHasPortalInputCapture); + +private: + bool m_errorShown{false}; + bool m_warningShown{false}; + std::shared_ptr m_pDeps; +}; + +} // namespace synergy::gui::core diff --git a/src/lib/gui/dialogs/SettingsDialogBase.ui b/src/lib/gui/dialogs/SettingsDialogBase.ui index c4f2c9dcd..ecf5a4996 100644 --- a/src/lib/gui/dialogs/SettingsDialogBase.ui +++ b/src/lib/gui/dialogs/SettingsDialogBase.ui @@ -277,8 +277,8 @@ - - :/res/icons/64x64/folder.png:/res/icons/64x64/folder.png + + :/icons/64x64/folder.png:/icons/64x64/folder.png @@ -567,8 +567,8 @@ - - :/res/icons/64x64/folder.png:/res/icons/64x64/folder.png + + :/icons/64x64/folder.png:/icons/64x64/folder.png @@ -753,9 +753,6 @@ m_pRadioSystemScope m_pComboElevate - - - m_pButtonBox diff --git a/src/lib/gui/messages.cpp b/src/lib/gui/messages.cpp index be77b2e65..18938f430 100644 --- a/src/lib/gui/messages.cpp +++ b/src/lib/gui/messages.cpp @@ -288,4 +288,30 @@ void showReadOnlySettings(QWidget *parent, const QString &systemSettingsPath) { .arg(nativePath)); } +void showWaylandExperimental(QWidget *parent) { + QMessageBox::information( + parent, "Experimental feature", + QString( + "

Thanks for trying the beta version!

" + "

Wayland support is experimental and contains bugs.

" + R"(

Please report bugs to us if you find any.

)" + "

Happy testing!

") + .arg(kUrlBugReport, kColorSecondary)); +} + +void showWaylandLibraryError(QWidget *parent) { + QMessageBox::critical( + parent, "Library problem", + QString( + "

Sorry, while this version of Synergy does support Wayland, " + "this build was not linked with one or more of the required " + "libraries.

" + "

Please either switch to X from your login screen or use a build " + "that uses the correct libraries.

" + "

If you think this is incorrect, please " + R"(report a bug.

)" + "

Please check the logs for more information.

") + .arg(kUrlBugReport, kColorSecondary)); +} + } // namespace synergy::gui::messages diff --git a/src/lib/gui/messages.h b/src/lib/gui/messages.h index 054ebb405..46aa8b0f5 100644 --- a/src/lib/gui/messages.h +++ b/src/lib/gui/messages.h @@ -52,4 +52,8 @@ bool showClearSettings(QWidget *parent); void showReadOnlySettings(QWidget *parent, const QString &systemSettingsPath); +void showWaylandExperimental(QWidget *parent); + +void showWaylandLibraryError(QWidget *parent); + } // namespace synergy::gui::messages diff --git a/src/lib/platform/EiScreen.cpp b/src/lib/platform/EiScreen.cpp index df58ec27d..2bd00aafc 100644 --- a/src/lib/platform/EiScreen.cpp +++ b/src/lib/platform/EiScreen.cpp @@ -71,8 +71,7 @@ EiScreen::EiScreen(bool is_primary, IEventQueue *events, bool use_portal) #if HAVE_LIBPORTAL_INPUTCAPTURE portal_input_capture_ = new PortalInputCapture(this, events_); #else - throw std::invalid_argument( - "Missing libportal InputCapture portal support"); + throw std::invalid_argument("missing libportal input capture support"); #endif // HAVE_LIBPORTAL_INPUTCAPTURE } else { #if WINAPI_LIBPORTAL @@ -82,8 +81,7 @@ EiScreen::EiScreen(bool is_primary, IEventQueue *events, bool use_portal) this, &EiScreen::handle_portal_session_closed)); portal_remote_desktop_ = new PortalRemoteDesktop(this, events_); #else - throw std::invalid_argument( - "Missing libportal RemoteDesktop portal support"); + throw std::invalid_argument("missing libportal remote desktop support"); #endif // WINAPI_LIBPORTAL } } else { @@ -91,7 +89,7 @@ EiScreen::EiScreen(bool is_primary, IEventQueue *events, bool use_portal) auto rc = ei_setup_backend_socket(ei_, nullptr); if (rc != 0) { LOG_DEBUG("ei init error: %s", strerror(-rc)); - throw std::runtime_error("Failed to init ei context"); + throw std::runtime_error("failed to init ei context"); } } } @@ -819,7 +817,7 @@ void EiScreen::updateButtons() { IKeyState *EiScreen::getKeyState() const { return key_state_; } String EiScreen::getSecureInputApp() const { - throw std::runtime_error("Not implemented"); + throw std::runtime_error("get security input app not implemented"); } EiScreen::HotKeyItem::HotKeyItem(std::uint32_t mask, std::uint32_t id) diff --git a/src/lib/platform/PortalInputCapture.cpp b/src/lib/platform/PortalInputCapture.cpp index 86abe77b0..494fcb306 100644 --- a/src/lib/platform/PortalInputCapture.cpp +++ b/src/lib/platform/PortalInputCapture.cpp @@ -130,7 +130,7 @@ void PortalInputCapture::cb_session_closed(XdpSession *session) { void PortalInputCapture::cb_init_input_capture_session( GObject *object, GAsyncResult *res) { - LOG_DEBUG("portal session ready"); + LOG_DEBUG("portal input capture session initialized"); g_autoptr(GError) error = nullptr; auto session = xdp_portal_create_input_capture_session_finish( diff --git a/src/lib/platform/PortalRemoteDesktop.cpp b/src/lib/platform/PortalRemoteDesktop.cpp old mode 100644 new mode 100755 index 14167bcbd..17ce32456 --- a/src/lib/platform/PortalRemoteDesktop.cpp +++ b/src/lib/platform/PortalRemoteDesktop.cpp @@ -94,7 +94,9 @@ void PortalRemoteDesktop::cb_session_started( auto session = XDP_SESSION(object); auto success = xdp_session_start_finish(session, res, &error); if (!success) { - LOG_ERR("failed to start portal remote desktop session"); + LOG_ERR( + "failed to start portal remote desktop session, quitting: %s", + error->message); g_main_loop_quit(glib_main_loop_); events_->addEvent(Event::kQuit); return; @@ -123,7 +125,7 @@ void PortalRemoteDesktop::cb_session_started( void PortalRemoteDesktop::cb_init_remote_desktop_session( GObject *object, GAsyncResult *res) { - LOG_DEBUG("remote desktop session ready"); + LOG_DEBUG("portal remote desktop session initialized"); g_autoptr(GError) error = nullptr; auto session = xdp_portal_create_remote_desktop_session_finish( @@ -149,7 +151,7 @@ void PortalRemoteDesktop::cb_init_remote_desktop_session( session_signal_id_ = g_signal_connect( G_OBJECT(session), "closed", G_CALLBACK(cb_session_closed_cb), this); - LOG_DEBUG("Session ready, starting"); + LOG_DEBUG("portal remote desktop session starting"); xdp_session_start( session, nullptr, // parent diff --git a/src/lib/platform/wayland.h b/src/lib/platform/wayland.h index a572037b0..1344754de 100644 --- a/src/lib/platform/wayland.h +++ b/src/lib/platform/wayland.h @@ -21,9 +21,27 @@ namespace synergy::platform { +#if WINAPI_LIBEI +const auto kHasEi = true; +#else +const auto kHasEi = false; +#endif + +#if WINAPI_LIBPORTAL +const auto kHasPortal = true; +#else +const auto kHasPortal = false; +#endif + +#if HAVE_LIBPORTAL_INPUTCAPTURE +const auto kHasPortalInputCapture = true; +#else +const auto kHasPortalInputCapture = false; +#endif + inline bool isWayland() { - const std::string session = getenv("XDG_SESSION_TYPE"); - return session == "wayland"; + const auto session = getenv("XDG_SESSION_TYPE"); + return session != nullptr && std::string(session) == "wayland"; } } // namespace synergy::platform diff --git a/src/lib/synergy/App.h b/src/lib/synergy/App.h index 9693bed43..fad88e3f0 100644 --- a/src/lib/synergy/App.h +++ b/src/lib/synergy/App.h @@ -31,6 +31,8 @@ #include "synergy/unix/AppUtilUnix.h" #endif +#include + class IArchTaskBarReceiver; class BufferedLogOutputter; class ILogOutputter; @@ -46,6 +48,11 @@ typedef IArchTaskBarReceiver *(*CreateTaskBarReceiverFunc)( class App : public IApp { public: + class XNoEiSupport : public std::runtime_error { + public: + XNoEiSupport() : std::runtime_error("libei is not supported") {} + }; + App(IEventQueue *events, CreateTaskBarReceiverFunc createTaskBarReceiver, synergy::ArgsBase *args); App(App const &) = delete; diff --git a/src/lib/synergy/ClientApp.cpp b/src/lib/synergy/ClientApp.cpp index 5072a122a..42212ab6d 100644 --- a/src/lib/synergy/ClientApp.cpp +++ b/src/lib/synergy/ClientApp.cpp @@ -186,13 +186,19 @@ synergy::Screen *ClientApp::createScreen() { args().m_enableLangSync, args().m_clientScrollDirection), m_events); #endif -#if WINAPI_LIBEI + +#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) if (synergy::platform::isWayland()) { +#if WINAPI_LIBEI LOG((CLOG_INFO "using ei screen for wayland")); return new synergy::Screen( new synergy::EiScreen(false, m_events, true), m_events); +#else + throw XNoEiSupport(); +#endif } #endif + #if WINAPI_XWINDOWS LOG((CLOG_INFO "using legacy x windows screen")); return new synergy::Screen( @@ -202,6 +208,7 @@ synergy::Screen *ClientApp::createScreen() { m_events); #endif + #if WINAPI_CARBON return new synergy::Screen( new OSXScreen( diff --git a/src/lib/synergy/ServerApp.cpp b/src/lib/synergy/ServerApp.cpp index fca7fbee0..512c964c9 100644 --- a/src/lib/synergy/ServerApp.cpp +++ b/src/lib/synergy/ServerApp.cpp @@ -560,11 +560,15 @@ synergy::Screen *ServerApp::createScreen() { m_events); #endif -#if WINAPI_LIBEI +#if defined(WINAPI_XWINDOWS) or defined(WINAPI_LIBEI) if (synergy::platform::isWayland()) { +#if WINAPI_LIBEI LOG((CLOG_INFO "using ei screen for wayland")); return new synergy::Screen( new synergy::EiScreen(true, m_events, true), m_events); +#else + throw XNoEiSupport(); +#endif } #endif diff --git a/src/test/unittests/gui/core/WaylandWarningsTests.cpp b/src/test/unittests/gui/core/WaylandWarningsTests.cpp new file mode 100644 index 000000000..6c4a147ad --- /dev/null +++ b/src/test/unittests/gui/core/WaylandWarningsTests.cpp @@ -0,0 +1,129 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2024 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gui/core/WaylandWarnings.h" + +#include "gui/core/CoreProcess.h" + +#include +#include + +using namespace synergy::gui; +using namespace synergy::gui::core; + +namespace { +struct MockDeps : public WaylandWarnings::Deps { + MOCK_METHOD(void, showWaylandExperimental, (QWidget *), (override)); + MOCK_METHOD(void, showWaylandLibraryError, (QWidget *), (override)); +}; + +} // namespace + +TEST(WaylandWarningsTests, showOnce_serverNoEi_showLibraryError) { + const auto deps = std::make_shared(); + const bool hasEi = false; + const bool hasPortal = false; + const bool hasPortalIC = false; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandLibraryError(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningsTests, showOnce_serverNoPortal_showLibraryError) { + const auto deps = std::make_shared(); + const bool hasEi = true; + const bool hasPortal = false; + const bool hasPortalIC = false; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandLibraryError(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningsTests, showOnce_serverNoPortalIc_showLibraryError) { + const auto deps = std::make_shared(); + const bool hasEi = true; + const bool hasPortal = true; + const bool hasPortalIC = false; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandLibraryError(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningTests, showOnce_clientNoPortalIc_showExperimental) { + const auto deps = std::make_shared(); + const bool hasEi = true; + const bool hasPortal = true; + const bool hasPortalIC = false; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandExperimental(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Client, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningsTests, showOnce_serverHasPortalIc_showExperimentalOnly) { + const auto deps = std::make_shared(); + const bool hasEi = true; + const bool hasPortal = true; + const bool hasPortalIC = true; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandExperimental(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningsTests, showOnce_failureCalledTwice_messageOnlyShownOnce) { + const auto deps = std::make_shared(); + const bool hasEi = false; + const bool hasPortal = false; + const bool hasPortalIC = false; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandLibraryError(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +} + +TEST(WaylandWarningsTests, showOnce_successCalledTwice_messageOnlyShownOnce) { + const auto deps = std::make_shared(); + const bool hasEi = true; + const bool hasPortal = true; + const bool hasPortalIC = true; + WaylandWarnings waylandWarnings(deps); + + EXPECT_CALL(*deps, showWaylandExperimental(nullptr)).Times(1); + + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); + waylandWarnings.showOnce( + nullptr, CoreProcess::Mode::Server, hasEi, hasPortal, hasPortalIC); +}