From 584323016947aa72042ae62bb3c26905f3488ae0 Mon Sep 17 00:00:00 2001 From: Igor Sikachyna Date: Tue, 29 Jun 2021 13:20:56 +0300 Subject: [PATCH] SYNERGY-1032 - Use native notifications for secure input on MacOS (#7041) * SYNERGY-1017 - Try usnig NSUserNotification * SYNERGY-1032 - Use UNUserNotificationCenter instead of deprecated notifications * SYNERGY-1032 - Update app delegate and add log after notification access * SYNERGY-1032 - Try using deprecated notification * SYNERGY-1032 - Add notifications to the platform dependant code * SYNERGY-1032 - Check for development builds before asking permission for notifications * SYNERGY-1032 - Remove old notification and secure input detection * SYNERGY-1032 - Add permission request to the main app * SYNERGY-1032 - Only show notifications on server * SYNERGY-1032 - Request and show notification from main app * SYNERGY-1032 - Remove logging from OSX helpers * SYNERGY-1032 - Add UN notification for testing * SYNERGY-1032 - Show notification once * SYNERGY-1032 - Try replicating old flow * SYNERGY-1032 - Reuse notification center * SYNERGY-1032 - Send notification after granting permission * SYNERGY-1032 - Update app delegate * SYNERGY-1032 - Remove deprecated property * SYNERGY-1032 - Add output for debugging * SYNERGY-1032 - Try updating delegate * SYNERGY-1032 - Print current bundle name * SYNERGY-1032 - Add AppDelegate to the server * SYNERGY-1032 - Move notification creation to synergy app * SYNERGY-1032 - Update notification text * SYNERGY-1032 - Fix QString conversion for notification * SYNERGY-1032 - Move notification creation outside the handler * SYNERGY-1032 - Remove debug notification, show notification with no delay * SYNERGY-1032 - Show notification inside the completion handler * SYNERGY-1032 - Remove timed notification trigger * SYNERGY-1032 - Request permissions on synergy start * SYNERGY-1032 - Remove unused includes * SYNERGY-1032 - Update changelog * SYNERGY-1032 - Add empty notification implementations for Windows and Linux * SYNERGY-1032 - Removed temporary debug messages * SYNERGY-1032 - Resolve code smell --- CMakeLists.txt | 2 + ChangeLog | 2 +- src/gui/src/AppDelegate.h | 17 ++++ src/gui/src/AppDelegate.mm | 33 ++++++++ src/gui/src/MainWindow.cpp | 70 +++++++--------- src/gui/src/MainWindow.h | 9 +- src/gui/src/OSXHelpers.h | 8 +- src/gui/src/OSXHelpers.mm | 121 ++++++++++++++------------- src/lib/platform/MSWindowsScreen.cpp | 6 ++ src/lib/platform/MSWindowsScreen.h | 1 + src/lib/platform/OSXScreen.h | 3 + src/lib/platform/OSXScreen.mm | 86 +++++++++++++++++++ src/lib/platform/XWindowsScreen.cpp | 6 ++ src/lib/platform/XWindowsScreen.h | 1 + src/lib/synergy/IPlatformScreen.h | 6 ++ src/lib/synergy/Screen.cpp | 6 ++ src/lib/synergy/Screen.h | 7 ++ 17 files changed, 277 insertions(+), 107 deletions(-) create mode 100644 src/gui/src/AppDelegate.h create mode 100644 src/gui/src/AppDelegate.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 320bd1229..3dda58905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,7 @@ if (UNIX) find_library (lib_ApplicationServices ApplicationServices) find_library (lib_Foundation Foundation) find_library (lib_Carbon Carbon) + find_library (lib_UserNotifications UserNotifications) list (APPEND libs ${lib_ScreenSaver} @@ -174,6 +175,7 @@ if (UNIX) ${lib_ApplicationServices} ${lib_Foundation} ${lib_Carbon} + ${lib_UserNotifications} ) else() # not-apple diff --git a/ChangeLog b/ChangeLog index 4f7450918..7855debe6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,7 +15,7 @@ Enhancements: - #7026 Update the way we package OpenSSL for Windows build - #7035 Add Fedora 34 build - #7037 Add Ubuntu 21.04 build -- #7030 Add user nofication for secure input on Mac +- #7030 | #7041 Add user nofication for secure input on Mac =========== v1.14.0-stable diff --git a/src/gui/src/AppDelegate.h b/src/gui/src/AppDelegate.h new file mode 100644 index 000000000..fa1e09c7b --- /dev/null +++ b/src/gui/src/AppDelegate.h @@ -0,0 +1,17 @@ +#ifndef APPDELEGATE_H +#define APPDELEGATE_H +#ifdef __cplusplus +extern "C" { +#endif + +#import +#import + +@interface AppDelegate : NSObject + +@end + +#ifdef __cplusplus +} +#endif +#endif // APPDELEGATE_H diff --git a/src/gui/src/AppDelegate.mm b/src/gui/src/AppDelegate.mm new file mode 100644 index 000000000..7c994dba8 --- /dev/null +++ b/src/gui/src/AppDelegate.mm @@ -0,0 +1,33 @@ +#import "AppDelegate.h" + +@interface AppDelegate () + +@property (strong) IBOutlet NSWindow *window; +@end + +@implementation AppDelegate +{ +} + +-(void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; +} + +-(BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification{ + return YES; +} + +-(void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{ + UNNotificationPresentationOptions presentationOptions = + UNNotificationPresentationOptionSound + | UNNotificationPresentationOptionAlert + | UNNotificationPresentationOptionBadge; + + completionHandler(presentationOptions); +} + +@end diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index e89aad348..b073298d5 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -328,42 +328,6 @@ 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(); @@ -498,6 +462,12 @@ void MainWindow::updateFromLogLine(const QString &line) checkFingerprint(line); checkSecureSocket(line); + // subprocess (synergys, synergyc) is not allowed to show notifications + // process the log from it and show notificatino from synergy instead +#ifdef Q_OS_MAC + checkOSXNotification(line); +#endif + #ifndef SYNERGY_ENTERPRISE checkLicense(line); #endif @@ -609,6 +579,27 @@ void MainWindow::checkSecureSocket(const QString& line) m_SecureSocketVersion = line.mid(index + strlen(tlsCheckString)); // Compliant: we made sure that tlsCheckString variable ended with null(static const char* declaration) } } + +#ifdef Q_OS_MAC +void MainWindow::checkOSXNotification(const QString& line) +{ + static const QString OSXNotificationSubstring = "OSX Notification: "; + if (line.contains(OSXNotificationSubstring) && line.contains('|')) { + int delimterPosition = line.indexOf('|'); + int notificationStartPosition = line.indexOf(OSXNotificationSubstring); + QString title = + line.mid(notificationStartPosition + OSXNotificationSubstring.length(), + delimterPosition - notificationStartPosition - OSXNotificationSubstring.length()); + QString body = + line.mid(delimterPosition + 1, + line.length() - delimterPosition); + if (!showOSXNotification(title, body)) { + appendLogInfo("OSX notification was not shown"); + } + } +} +#endif + QString MainWindow::getTimeStamp() { QDateTime current = QDateTime::currentDateTime(); @@ -646,6 +637,10 @@ void MainWindow::startSynergy() { saveSettings(); +#ifdef Q_OS_MAC + requestOSXNotificationPermission(); +#endif + #ifndef SYNERGY_ENTERPRISE SerialKey serialKey = m_LicenseManager->serialKey(); if (!serialKey.isValid()) { @@ -743,7 +738,6 @@ 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; @@ -1061,7 +1055,6 @@ 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) { @@ -1069,7 +1062,6 @@ 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 e88803cf3..748a311de 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -164,7 +164,6 @@ 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(); @@ -207,6 +206,9 @@ public slots: void checkConnected(const QString& line); void checkFingerprint(const QString& line); void checkSecureSocket(const QString& line); +#ifdef Q_OS_MAC + void checkOSXNotification(const QString& line); +#endif #ifndef SYNERGY_ENTERPRISE void checkLicense(const QString& line); #endif @@ -247,11 +249,6 @@ 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 246bead75..0b98bee71 100644 --- a/src/gui/src/OSXHelpers.h +++ b/src/gui/src/OSXHelpers.h @@ -19,7 +19,7 @@ #define OSXHELPERS__H -#include +#include enum class IconsTheme { ICONS_DARK, @@ -27,9 +27,9 @@ enum class IconsTheme { ICONS_TEMPLATE }; -bool isOSXSecureInputEnabled(); -int getOSXSecureInputEventPID(); -std::string getOSXProcessName(int pid); +void requestOSXNotificationPermission(); +bool isOSXDevelopmentBuild(); +bool showOSXNotification(const QString& title, const QString& body); bool isOSXInterfaceStyleDark(); IconsTheme getOSXIconsTheme(); diff --git a/src/gui/src/OSXHelpers.mm b/src/gui/src/OSXHelpers.mm index 7cd319daf..a5fde23f6 100644 --- a/src/gui/src/OSXHelpers.mm +++ b/src/gui/src/OSXHelpers.mm @@ -20,76 +20,83 @@ #import #import #import -#import +#import +#import +#import +#import -#import -#import -#import -#import -#import -#import -#import +#import +void requestOSXNotificationPermission() +{ + if (@available(macOS 10.14, *)) + { + if (isOSXDevelopmentBuild()) + { + qWarning("Not requesting notification permission in dev build"); + return; + } + + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) + completionHandler:^(BOOL granted, NSError * _Nullable error) { + if(error != nil) + { + qWarning("Notification permission request error: %s", [[NSString stringWithFormat:@"%@", error] UTF8String]); + } + }]; + } +} bool -isOSXSecureInputEnabled() +isOSXDevelopmentBuild() { - return IsSecureEventInputEnabled(); + std::string bundleURL = [[[NSBundle mainBundle] bundleURL].absoluteString UTF8String]; + return (bundleURL.find("Applications/Synergy.app") == std::string::npos); } -int -getOSXSecureInputEventPID() +bool +showOSXNotification(const QString& title, const QString& body) { - io_service_t service = MACH_PORT_NULL, service_root = MACH_PORT_NULL; - mach_port_t masterPort; + if (@available(macOS 10.14, *)) + { + // accessing notification center on unsigned build causes an immidiate + // application shutodown (in this case synergys) and cannot be caught + // to avoid issues with it need to first check if this is a dev build + if (isOSXDevelopmentBuild()) + { + qWarning("Not showing notification in dev build"); + return false; + } - kern_return_t kr = IOMasterPort( MACH_PORT_NULL, &masterPort ); - if(kr != KERN_SUCCESS) return 0; + requestOSXNotificationPermission(); - // 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); + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; - std::unique_ptr::type, decltype(&CFRelease)> consoleUsers( - IORegistryEntrySearchCFProperty(service_root, kIOServicePlane, CFSTR("IOConsoleUsers"), NULL, kIORegistryIterateParents | kIORegistryIterateRecursively), - CFRelease - ); - if(!consoleUsers) return 0; + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.title = title.toNSString(); + content.body = body.toNSString(); - CFTypeID type = CFGetTypeID(consoleUsers.get()); - if(type != CFArrayGetTypeID()) return 0; + // Create the request object. + UNNotificationRequest* request = [UNNotificationRequest + requestWithIdentifier:@"SecureInput" content:content trigger:nil]; - 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; + [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if (error != nil) { + qWarning("Notification display request error: %s", [[NSString stringWithFormat:@"%@", error] UTF8String]); + } + }]; + } + else + { + NSUserNotification* notification = [[NSUserNotification alloc] init]; + notification.title = title.toNSString(); + notification.informativeText = body.toNSString(); + notification.soundName = NSUserNotificationDefaultSoundName; //Will play a default sound + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: notification]; + [notification autorelease]; + } + return true; } bool diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index 65b47d0e7..acfc53494 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -1988,6 +1988,12 @@ MSWindowsScreen::getDropTarget() const return m_desktopPath; } +void +MSWindowsScreen::createNotification(const String& title, const String& content) const +{ + // TODO: implement notification +} + bool MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask state, WPARAM wParam) const { diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h index e7fab317b..8f2447c90 100644 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -139,6 +139,7 @@ public: virtual String& getDraggingFilename(); virtual const String& getDropTarget() const; + void createNotification(const String& title, const String& content) const override; protected: // IPlatformScreen overrides diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h index fcc0e20f8..1360b96a1 100644 --- a/src/lib/platform/OSXScreen.h +++ b/src/lib/platform/OSXScreen.h @@ -101,6 +101,7 @@ public: const String& getDropTarget() const { return m_dropTarget; } void waitForCarbonLoop() const; + void createNotification(const String& title, const String& content) const override; protected: // IPlatformScreen overrides @@ -202,6 +203,8 @@ private: static char* CFStringRefToUTF8String(CFStringRef aString); void getDropTargetThread(void*); + + void createSecureInputNotification(); private: struct HotKeyItem { diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index bd3a69b06..f82ede88c 100755 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -46,6 +46,7 @@ #include #include #include +#include // This isn't in any Apple SDK that I know of as of yet. enum { @@ -58,6 +59,9 @@ enum { kCarbonLoopWaitTimeout = 10 }; +int getSecureInputEventPID(); +String getProcessName(int pid); + // TODO: upgrade deprecated function usage in these functions. void setZeroSuppressionInterval(); void avoidSupression(); @@ -881,6 +885,10 @@ OSXScreen::enter() bool OSXScreen::leave() { + if(m_isPrimary && IsSecureEventInputEnabled()) { + createSecureInputNotification(); + } + hideCursor(); if (isDraggingStarted()) { @@ -2150,6 +2158,84 @@ OSXScreen::waitForCarbonLoop() const } +void +OSXScreen::createNotification(const String& title, const String& content) const +{ + LOG((CLOG_INFO "OSX Notification: %s|%s", title.c_str(), content.c_str())); +} + +void +OSXScreen::createSecureInputNotification() +{ + std::string secureInputNotificationBody = + "Secure input was enabled in your system. " \ + "Synergy may not be able to send keyboard strokes. "; + + int secureInputProcessPID = getSecureInputEventPID(); + std::string infringingProcessName = getProcessName(secureInputProcessPID); + if(secureInputProcessPID == 0) infringingProcessName = "unknown"; + secureInputNotificationBody += "Infringing process is " + infringingProcessName; + + createNotification( + "Keyboard may not work correctly", + secureInputNotificationBody); +} + +int +getSecureInputEventPID() +{ + 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; +} + +String +getProcessName(int pid) +{ + if(!pid) return ""; + char buf[128]; + proc_name(pid, buf, sizeof(buf)); + return buf; +} + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" void diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index 7bd0ba8c7..8bbde462e 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -475,6 +475,12 @@ XWindowsScreen::isPrimary() const return m_isPrimary; } +void +XWindowsScreen::createNotification(const String& title, const String& content) const +{ + // TODO: implement notification +} + void* XWindowsScreen::getEventTarget() const { diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h index e0be16ec8..a0d401a0a 100644 --- a/src/lib/platform/XWindowsScreen.h +++ b/src/lib/platform/XWindowsScreen.h @@ -84,6 +84,7 @@ public: virtual void setOptions(const OptionsList& options); virtual void setSequenceNumber(UInt32); virtual bool isPrimary() const; + void createNotification(const String& title, const String& content) const override; protected: // IPlatformScreen overrides diff --git a/src/lib/synergy/IPlatformScreen.h b/src/lib/synergy/IPlatformScreen.h index 71eed1d02..ccb325f75 100644 --- a/src/lib/synergy/IPlatformScreen.h +++ b/src/lib/synergy/IPlatformScreen.h @@ -133,6 +133,12 @@ public: //! Change dragging status virtual void setDraggingStarted(bool started) = 0; + //! Send desktop user notification + /*! + Creates a platform dependant user notificaiton using native APIs + */ + virtual void createNotification(const String& title, const String& content) const = 0; + //@} //! @name accessors //@{ diff --git a/src/lib/synergy/Screen.cpp b/src/lib/synergy/Screen.cpp index b3c7868fb..15ab824f4 100644 --- a/src/lib/synergy/Screen.cpp +++ b/src/lib/synergy/Screen.cpp @@ -522,4 +522,10 @@ Screen::leaveSecondary() m_screen->fakeAllKeysUp(); } +void +Screen::createNotification(const String& title, const String& content) const +{ + m_screen->createNotification(title, content); +} + } diff --git a/src/lib/synergy/Screen.h b/src/lib/synergy/Screen.h index 267195965..15bf2f096 100644 --- a/src/lib/synergy/Screen.h +++ b/src/lib/synergy/Screen.h @@ -233,6 +233,13 @@ public: void startDraggingFiles(DragFileList& fileList); void setEnableDragDrop(bool enabled); + + //! Send desktop user notification + /*! + Creates a platform dependant user notificaiton using native APIs + */ + virtual void createNotification(const String& title, const String& content) const; + //@} //! @name accessors //@{