fixed helper tool:

used xpc communicate between gui and mhp
made helper tool optional
gave more feedback
This commit is contained in:
jerry 2014-05-01 15:54:09 +00:00
parent ff42afc36c
commit 735fd08400
5 changed files with 218 additions and 34 deletions

View File

@ -4,11 +4,9 @@
<dict> <dict>
<key>Label</key> <key>Label</key>
<string>synmacph</string> <string>synmacph</string>
<key>RunAtLoad</key> <key>MachServices</key>
<dict>
<key>synmacph</key>
<true/> <true/>
<key>LaunchOnlyOnce</key> </dict></dict>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist> </plist>

View File

@ -19,16 +19,78 @@
#include <unistd.h> #include <unistd.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <xpc/xpc.h>
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[]) int main(int argc, const char * argv[])
{ {
#pragma unused(argc) #pragma unused(argc)
#pragma unused(argv) #pragma unused(argv)
system("sudo sqlite3 /Library/Application\\ Support/com.apple.TCC/TCC.db 'delete from access where client like \"%Synergy.app%\"'"); xpc_connection_t service = xpc_connection_create_mach_service(
label,
dispatch_get_main_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER);
(void) sleep(10); 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; return EXIT_SUCCESS;
} }

View File

@ -22,9 +22,12 @@ public:
AXDatabaseCleaner(); AXDatabaseCleaner();
~AXDatabaseCleaner(); ~AXDatabaseCleaner();
void loadPrivilegeHelper(); bool loadPrivilegeHelper();
bool xpcConnect();
bool privilegeCommand(const char* command);
private: private:
class Private; class Private;
Private* d; Private* m_private;
bool m_waitForResponse;
}; };

View File

@ -21,6 +21,8 @@
#import <ServiceManagement/ServiceManagement.h> #import <ServiceManagement/ServiceManagement.h>
#endif #endif
#import <Security/Authorization.h> #import <Security/Authorization.h>
#import <QMessageBox>
#import <QTime>
const NSString* const label = @"synmacph"; const NSString* const label = @"synmacph";
@ -28,29 +30,31 @@ class AXDatabaseCleaner::Private {
public: public:
NSAutoreleasePool* autoReleasePool; NSAutoreleasePool* autoReleasePool;
AuthorizationRef authRef; AuthorizationRef authRef;
xpc_connection_t xpcConnection;
}; };
AXDatabaseCleaner::AXDatabaseCleaner() 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() AXDatabaseCleaner::~AXDatabaseCleaner()
{ {
[d->autoReleasePool release]; [m_private->autoReleasePool release];
delete d; delete m_private;
} }
void AXDatabaseCleaner::loadPrivilegeHelper() bool AXDatabaseCleaner::loadPrivilegeHelper()
{ {
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks #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) { if (status != errAuthorizationSuccess) {
assert(NO); assert(NO);
d->authRef = NULL; m_private->authRef = NULL;
} }
AuthorizationItem authItem = {kSMRightBlessPrivilegedHelper, 0, NULL, 0}; AuthorizationItem authItem = {kSMRightBlessPrivilegedHelper, 0, NULL, 0};
@ -63,13 +67,13 @@ void AXDatabaseCleaner::loadPrivilegeHelper()
BOOL result = NO; BOOL result = NO;
NSError* error = nil; NSError* error = nil;
status = AuthorizationCopyRights(d->authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); status = AuthorizationCopyRights(m_private->authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
if (status != errAuthorizationSuccess) { if (status != errAuthorizationSuccess) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
} }
else { else {
CFErrorRef cfError; CFErrorRef cfError;
result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (CFStringRef)label, d->authRef, &cfError); result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (CFStringRef)label, m_private->authRef, &cfError);
if (!result) { if (!result) {
error = CFBridgingRelease(cfError); error = CFBridgingRelease(cfError);
@ -79,7 +83,79 @@ void AXDatabaseCleaner::loadPrivilegeHelper()
if (!result) { if (!result) {
assert(error != nil); assert(error != nil);
NSLog(@"bless error: domain= %@ / code= %d", [error domain], (int) [error code]); 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;
} }

View File

@ -126,10 +126,19 @@ int waitForTray()
} }
#if defined(Q_OS_MAC) #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() bool checkMacAssistiveDevices()
{ {
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 // mavericks
// new in mavericks, applications are trusted individually // new in mavericks, applications are trusted individually
// with use of the accessibility api. this call will show a // with use of the accessibility api. this call will show a
// prompt which can show the security/privacy/accessibility // prompt which can show the security/privacy/accessibility
@ -137,21 +146,57 @@ bool checkMacAssistiveDevices()
// show up there automatically, but will be unchecked. // show up there automatically, but will be unchecked.
const void* keys[] = { kAXTrustedCheckOptionPrompt }; const void* keys[] = { kAXTrustedCheckOptionPrompt };
const void* falseValue[] = { kCFBooleanFalse };
const void* trueValue[] = { kCFBooleanTrue }; const void* trueValue[] = { kCFBooleanTrue };
const void* falseValue[] = { kCFBooleanFalse };
CFDictionaryRef optionsWithoutPrompt = CFDictionaryCreate(NULL, keys, falseValue, 1, NULL, NULL);
CFDictionaryRef optionsWithPrompt = CFDictionaryCreate(NULL, keys, trueValue, 1, NULL, NULL); CFDictionaryRef optionsWithPrompt = CFDictionaryCreate(NULL, keys, trueValue, 1, NULL, NULL);
CFDictionaryRef optionsWithoutPrompt = CFDictionaryCreate(NULL, keys, falseValue, 1, NULL, NULL);
bool result; bool result;
result = AXIsProcessTrustedWithOptions(optionsWithoutPrompt); result = AXIsProcessTrustedWithOptions(optionsWithoutPrompt);
// Synergy is not checked in accessibility
if (!result) { 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 // call privilege help tool
AXDatabaseCleaner axdc; AXDatabaseCleaner axdc;
axdc.loadPrivilegeHelper(); 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); result = AXIsProcessTrustedWithOptions(optionsWithPrompt);
} }
}
CFRelease(optionsWithoutPrompt); CFRelease(optionsWithoutPrompt);
CFRelease(optionsWithPrompt); CFRelease(optionsWithPrompt);