diff --git a/src/cmd/synmacph/Launchd.plist b/src/cmd/synmacph/Launchd.plist index d56cca85..9ac34f46 100644 --- a/src/cmd/synmacph/Launchd.plist +++ b/src/cmd/synmacph/Launchd.plist @@ -4,11 +4,9 @@ Label synmacph - RunAtLoad - - LaunchOnlyOnce - - KeepAlive - - + MachServices + + synmacph + + diff --git a/src/cmd/synmacph/synmacph.c b/src/cmd/synmacph/synmacph.c index e8d117a1..f4026585 100644 --- a/src/cmd/synmacph/synmacph.c +++ b/src/cmd/synmacph/synmacph.c @@ -19,16 +19,78 @@ #include #include #include +#include + +const char* const label = "synmacph"; + +static void xpcEventHandler(xpc_connection_t connection, xpc_object_t event) +{ + syslog(LOG_NOTICE, "received event in helper"); + + xpc_type_t type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + if (event == XPC_ERROR_CONNECTION_INVALID) { + // the client process on the other end of the connection has either + // crashed or cancelled the connection. After receiving this error, + // the connection is in an invalid state, and you do not need to + // call xpc_connection_cancel(). Just tear down any associated state + // here. + } + else if (event == XPC_ERROR_TERMINATION_IMMINENT) { + // handle per-connection termination cleanup. + } + } + else { + xpc_connection_t remote = xpc_dictionary_get_remote_connection(event); + + const char* command = xpc_dictionary_get_string(event, "request"); + syslog(LOG_NOTICE, "received command in helper: %s", command); + system(command); + + xpc_object_t reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_string(reply, "reply", "command has been executed"); + xpc_connection_send_message(remote, reply); + xpc_release(reply); + } +} + +static void xpcConnectionHandler(xpc_connection_t connection) +{ + syslog(LOG_NOTICE, "configuring message event handler for helper"); + + xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { + xpcEventHandler(connection, event); + }); + + xpc_connection_resume(connection); +} int main(int argc, const char * argv[]) { - #pragma unused(argc) - #pragma unused(argv) - - system("sudo sqlite3 /Library/Application\\ Support/com.apple.TCC/TCC.db 'delete from access where client like \"%Synergy.app%\"'"); +#pragma unused(argc) +#pragma unused(argv) - (void) sleep(10); + xpc_connection_t service = xpc_connection_create_mach_service( + label, + dispatch_get_main_queue(), + XPC_CONNECTION_MACH_SERVICE_LISTENER); + + if (!service) { + syslog(LOG_NOTICE, "failed to create service"); + exit(EXIT_FAILURE); + } + + syslog(LOG_NOTICE, "configuring connection event handler for helper"); + xpc_connection_set_event_handler(service, ^(xpc_object_t connection) { + xpcConnectionHandler(connection); + }); + + xpc_connection_resume(service); + + dispatch_main(); + + xpc_release(service); return EXIT_SUCCESS; } - diff --git a/src/gui/src/AXDatabaseCleaner.h b/src/gui/src/AXDatabaseCleaner.h index 0f8b544b..51b25dc9 100644 --- a/src/gui/src/AXDatabaseCleaner.h +++ b/src/gui/src/AXDatabaseCleaner.h @@ -22,9 +22,12 @@ public: AXDatabaseCleaner(); ~AXDatabaseCleaner(); - void loadPrivilegeHelper(); + bool loadPrivilegeHelper(); + bool xpcConnect(); + bool privilegeCommand(const char* command); private: class Private; - Private* d; + Private* m_private; + bool m_waitForResponse; }; diff --git a/src/gui/src/AXDatabaseCleaner.mm b/src/gui/src/AXDatabaseCleaner.mm index 94368c29..f870dd78 100644 --- a/src/gui/src/AXDatabaseCleaner.mm +++ b/src/gui/src/AXDatabaseCleaner.mm @@ -21,6 +21,8 @@ #import #endif #import +#import +#import const NSString* const label = @"synmacph"; @@ -28,29 +30,31 @@ class AXDatabaseCleaner::Private { public: NSAutoreleasePool* autoReleasePool; AuthorizationRef authRef; + xpc_connection_t xpcConnection; }; AXDatabaseCleaner::AXDatabaseCleaner() { - d = new Private; + m_private = new Private; + m_private->autoReleasePool = [[NSAutoreleasePool alloc] init]; - d->autoReleasePool = [[NSAutoreleasePool alloc] init]; + m_waitForResponse = false; } AXDatabaseCleaner::~AXDatabaseCleaner() { - [d->autoReleasePool release]; - delete d; + [m_private->autoReleasePool release]; + delete m_private; } -void AXDatabaseCleaner::loadPrivilegeHelper() +bool AXDatabaseCleaner::loadPrivilegeHelper() { #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks - OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &d->authRef); + OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &m_private->authRef); if (status != errAuthorizationSuccess) { assert(NO); - d->authRef = NULL; + m_private->authRef = NULL; } AuthorizationItem authItem = {kSMRightBlessPrivilegedHelper, 0, NULL, 0}; @@ -63,13 +67,13 @@ void AXDatabaseCleaner::loadPrivilegeHelper() BOOL result = NO; NSError* error = nil; - status = AuthorizationCopyRights(d->authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); + status = AuthorizationCopyRights(m_private->authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); if (status != errAuthorizationSuccess) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; } else { CFErrorRef cfError; - result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (CFStringRef)label, d->authRef, &cfError); + result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (CFStringRef)label, m_private->authRef, &cfError); if (!result) { error = CFBridgingRelease(cfError); @@ -79,7 +83,79 @@ void AXDatabaseCleaner::loadPrivilegeHelper() if (!result) { assert(error != nil); NSLog(@"bless error: domain= %@ / code= %d", [error domain], (int) [error code]); + return false; } -#endif + return true; +} + +bool AXDatabaseCleaner::xpcConnect() +{ + const char *cStr = [label cStringUsingEncoding:NSASCIIStringEncoding]; + m_private->xpcConnection = xpc_connection_create_mach_service( + cStr, + NULL, + XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); + + if (!m_private->xpcConnection) { + NSLog(@"failed to create xpc connection"); + return false; + } + + xpc_connection_set_event_handler(m_private->xpcConnection, ^(xpc_object_t event) { + xpc_type_t type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { + NSLog(@"xpc connection interupted"); + + } + else if (event == XPC_ERROR_CONNECTION_INVALID) { + NSLog(@"xpc connection invalid, releasing"); + xpc_release(m_private->xpcConnection); + } + else { + NSLog(@"unexpected xpc connection error"); + } + } + else { + NSLog(@"unexpected xpc connection event"); + } + }); + + xpc_connection_resume(m_private->xpcConnection); + + return true; +} + +bool AXDatabaseCleaner::privilegeCommand(const char* command) +{ + xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(message, "request", command); + m_waitForResponse = true; + + xpc_connection_send_message_with_reply( + m_private->xpcConnection, + message, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), + ^(xpc_object_t event) { + const char* response = xpc_dictionary_get_string(event, "reply"); + NSLog(@"reply from helper tool: %s", response); + m_waitForResponse = false; + }); + + QTime time = QTime::currentTime(); + time.start(); + + while (m_waitForResponse) { + sleep(1); + if (time.elapsed() > 10000) { + QMessageBox::critical(NULL, "Synergy", + QObject::tr("No response from helper tool.Restart Synergy may solve this problem.")); + return false; + } + } +#endif + + return true; } diff --git a/src/gui/src/main.cpp b/src/gui/src/main.cpp index 12bbc852..af42ee4e 100644 --- a/src/gui/src/main.cpp +++ b/src/gui/src/main.cpp @@ -126,10 +126,19 @@ int waitForTray() } #if defined(Q_OS_MAC) +bool settingsExist() +{ + QSettings settings; + + //FIXME: check settings existance properly + int port = settings.value("port", -1).toInt(); + + return port == -1 ? false : true; +} + bool checkMacAssistiveDevices() { #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks - // new in mavericks, applications are trusted individually // with use of the accessibility api. this call will show a // prompt which can show the security/privacy/accessibility @@ -137,20 +146,56 @@ bool checkMacAssistiveDevices() // show up there automatically, but will be unchecked. const void* keys[] = { kAXTrustedCheckOptionPrompt }; - const void* falseValue[] = { kCFBooleanFalse }; const void* trueValue[] = { kCFBooleanTrue }; - - CFDictionaryRef optionsWithoutPrompt = CFDictionaryCreate(NULL, keys, falseValue, 1, NULL, NULL); + const void* falseValue[] = { kCFBooleanFalse }; CFDictionaryRef optionsWithPrompt = CFDictionaryCreate(NULL, keys, trueValue, 1, NULL, NULL); + CFDictionaryRef optionsWithoutPrompt = CFDictionaryCreate(NULL, keys, falseValue, 1, NULL, NULL); bool result; result = AXIsProcessTrustedWithOptions(optionsWithoutPrompt); - if (!result) { - // call privilege help tool - AXDatabaseCleaner axdc; - axdc.loadPrivilegeHelper(); - result = AXIsProcessTrustedWithOptions(optionsWithPrompt); + // Synergy is not checked in accessibility + if (!result) { + // if setting doesn't exist, just skip helper tool + if (!settingsExist()) { + result = AXIsProcessTrustedWithOptions(optionsWithPrompt); + } + else { + int reply; + reply = QMessageBox::question( + NULL, + "Synergy", + "Synergy requires access to Assistive Devices, but was not allowed.\n\nShould Synergy attempt to fix this?", + QMessageBox::Yes|QMessageBox::Default, + QMessageBox::No|QMessageBox::Escape); + + if (reply == QMessageBox::Yes) { + // call privilege help tool + AXDatabaseCleaner axdc; + result = axdc.loadPrivilegeHelper(); + result = axdc.xpcConnect(); + + QMessageBox box; + box.setModal(false); + box.setStandardButtons(0); + box.setText("Please wait."); + box.resize(150, 10); + box.show(); + + const char* command = "sudo sqlite3 /Library/Application\\ Support/com.apple.TCC/TCC.db 'delete from access where client like \"%Synergy.app%\"'"; + result = axdc.privilegeCommand(command); + + box.hide(); + + if (result) { + QMessageBox::information( + NULL, "Synergy", + "Synergy helper tool is complete. Please tick the checkbox in Accessibility."); + } + } + + result = AXIsProcessTrustedWithOptions(optionsWithPrompt); + } } CFRelease(optionsWithoutPrompt);