diff --git a/ChangeLog b/ChangeLog index 355024bbb..ca6037579 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,7 @@ Enhancements: - #6998 Remove functionality related to the screen saver synchronisation - #7026 Update the way we package OpenSSL for Windows build - #7037 Add Ubuntu 21.04 build +- #7030 Add user nofication for secure input on Mac =========== v1.14.0-stable diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 58e08cc9c..e89aad348 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -328,6 +328,42 @@ void MainWindow::saveSettings() GUI::Config::ConfigWriter::make()->globalSave(); } +void MainWindow::checkSystemInterruptions() +{ + if(synergyType() == synergyServer) + { +#if defined(Q_OS_MAC) + if(!isOSXSecureInputEnabled()) + { + m_isSecureInputNotificationShown = false; + } + else if(!m_isSecureInputNotificationShown) + { + // avoid showing multiple duplicate messages + m_isSecureInputNotificationShown = true; + + QMessageBox message(this); + message.addButton(QObject::tr("Accept"), QMessageBox::AcceptRole); + std::string messageText = + "Secure input was enabled in your system by another application. " \ + "Synergy will not be able to send keyboard strokes while the secure input is enabled\n\n"; + int secureInputProcessPID = getOSXSecureInputEventPID(); + std::string infringingProcessName = getOSXProcessName(secureInputProcessPID); + + // IO registry may not contain the secure input process PID + // in this case don't add an option to quit the infringing app + if(secureInputProcessPID == 0) infringingProcessName = "unknown"; + else message.addButton(QString("Quit %1").arg(infringingProcessName.c_str()), QMessageBox::ApplyRole); + messageText += "Infringing process is " + infringingProcessName; + message.setText(QObject::tr(messageText.c_str())); + + // if user decides to stop the app send the SIGTERM signal + if (message.exec() == QMessageBox::Accepted && secureInputProcessPID) kill(secureInputProcessPID, SIGTERM); + } +#endif + } +} + void MainWindow::zeroConfToggled() { #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG) updateZeroconfService(); @@ -707,6 +743,7 @@ void MainWindow::startSynergy() connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus))); connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput())); connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError())); + connect(&m_systemInterruptionCheckTimer, SIGNAL(timeout()), this, SLOT(checkSystemInterruptions())); } qDebug() << args; @@ -1024,6 +1061,7 @@ void MainWindow::setSynergyState(qSynergyState state) connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); m_pButtonToggleStart->setText(tr("&Stop")); m_pButtonApply->setEnabled(true); + m_systemInterruptionCheckTimer.start(5000); // check every 5 seconds } else if (state == synergyDisconnected) { @@ -1031,6 +1069,7 @@ void MainWindow::setSynergyState(qSynergyState state) connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); m_pButtonToggleStart->setText(tr("&Start")); m_pButtonApply->setEnabled(false); + m_systemInterruptionCheckTimer.stop(); } bool running = false; diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index f21775da4..e88803cf3 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -164,6 +164,7 @@ public slots: void logError(); void updateFound(const QString& version); void saveSettings(); + void checkSystemInterruptions(); /// @brief Receives the signal that the auto config option has changed void zeroConfToggled(); @@ -246,6 +247,11 @@ public slots: QString m_SecureSocketVersion; // brief Contains the version of the Secure Socket currently active ServerConnection m_serverConnection; ClientConnection m_clientConnection; + QTimer m_systemInterruptionCheckTimer; // Timer used for sceduling a task for detecting the system + // operations preventing the Synergy from working properly +#if defined(Q_OS_MAC) + bool m_isSecureInputNotificationShown = false; +#endif void updateAutoConfigWidgets(); diff --git a/src/gui/src/OSXHelpers.h b/src/gui/src/OSXHelpers.h index 6f79c5162..246bead75 100644 --- a/src/gui/src/OSXHelpers.h +++ b/src/gui/src/OSXHelpers.h @@ -19,12 +19,17 @@ #define OSXHELPERS__H +#include + enum class IconsTheme { ICONS_DARK, ICONS_LIGHT, ICONS_TEMPLATE }; +bool isOSXSecureInputEnabled(); +int getOSXSecureInputEventPID(); +std::string getOSXProcessName(int pid); bool isOSXInterfaceStyleDark(); IconsTheme getOSXIconsTheme(); diff --git a/src/gui/src/OSXHelpers.mm b/src/gui/src/OSXHelpers.mm index a58d620a0..7cd319daf 100644 --- a/src/gui/src/OSXHelpers.mm +++ b/src/gui/src/OSXHelpers.mm @@ -20,6 +20,77 @@ #import #import #import +#import + +#import +#import +#import +#import +#import +#import +#import + + +bool +isOSXSecureInputEnabled() +{ + return IsSecureEventInputEnabled(); +} + +int +getOSXSecureInputEventPID() +{ + io_service_t service = MACH_PORT_NULL, service_root = MACH_PORT_NULL; + mach_port_t masterPort; + + kern_return_t kr = IOMasterPort( MACH_PORT_NULL, &masterPort ); + if(kr != KERN_SUCCESS) return 0; + + // IO registry refuses to tap into the root level directly + // as a workaround access the parent of the top user level + service = IORegistryEntryFromPath( masterPort, kIOServicePlane ":/" ); + IORegistryEntryGetParentEntry(service, kIOServicePlane, &service_root); + + std::unique_ptr::type, decltype(&CFRelease)> consoleUsers( + IORegistryEntrySearchCFProperty(service_root, kIOServicePlane, CFSTR("IOConsoleUsers"), NULL, kIORegistryIterateParents | kIORegistryIterateRecursively), + CFRelease + ); + if(!consoleUsers) return 0; + + CFTypeID type = CFGetTypeID(consoleUsers.get()); + if(type != CFArrayGetTypeID()) return 0; + + CFTypeRef dict = CFArrayGetValueAtIndex((CFArrayRef)consoleUsers.get(), 0); + if(!dict) return 0; + + type = CFGetTypeID(dict); + if(type != CFDictionaryGetTypeID()) return 0; + + CFTypeRef secureInputPID = nullptr; + CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR("kCGSSessionSecureInputPID"), &secureInputPID); + + if(secureInputPID == nullptr) return 0; + + type = CFGetTypeID(secureInputPID); + if(type != CFNumberGetTypeID()) return 0; + + auto pidRef = (CFNumberRef)secureInputPID; + CFNumberType numberType = CFNumberGetType(pidRef); + if(numberType != kCFNumberSInt32Type) return 0; + + int pid; + CFNumberGetValue(pidRef, kCFNumberSInt32Type, &pid); + return pid; +} + +std::string +getOSXProcessName(int pid) +{ + if(!pid) return ""; + char buf[128]; + proc_name(pid, buf, sizeof(buf)); + return buf; +} bool isOSXInterfaceStyleDark()