Skip to content

Commit

Permalink
Trying alternative approach, copy and paste from source app instead o…
Browse files Browse the repository at this point in the history
…f directly access text. Good news... should work with many more apps including Mail.app. Bad news... still not quite working.
  • Loading branch information
jessegrosjean committed Jan 5, 2010
1 parent 6a656cd commit c5392a6
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 12 deletions.
21 changes: 19 additions & 2 deletions QCAppDelegate.m
Expand Up @@ -287,6 +287,23 @@ - (IBAction)beginQuickCursorEdit:(id)sender {

QCUIElement *focusedElement = [QCUIElement focusedElement];

if ([focusedElement readString]) {
NSString *string = [[NSPasteboard generalPasteboard] stringForType:NSStringPboardType];
NSDictionary *context = [NSDictionary dictionaryWithObject:focusedElement forKey:@"uiElement"];
NSString *processName = [focusedElement processName];
NSString *windowTitle = focusedElement.window.title;
NSString *editorCustomPath = [NSString stringWithFormat:@"%@ - %@", processName, windowTitle];
[[ODBEditor sharedODBEditor] setEditorBundleIdentifier:bundleID];
[[ODBEditor sharedODBEditor] editString:string options:[NSDictionary dictionaryWithObject:editorCustomPath forKey:ODBEditorCustomPathKey] forClient:self context:context];
} else {
[[NSAlert alertWithMessageText:NSLocalizedString(@"Could not edit text", nil)
defaultButton:NSLocalizedString(@"OK", nil)
alternateButton:nil
otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"QuickCursor could not find any text to edit. Make sure that a text view has keyboard focus, and then try again.", nil)] runModal];
}

/*
id value = focusedElement.value;
if ([value isKindOfClass:[NSString class]]) {
Expand All @@ -303,7 +320,7 @@ - (IBAction)beginQuickCursorEdit:(id)sender {
alternateButton:nil
otherButton:nil
informativeTextWithFormat:NSLocalizedString(@"QuickCursor could not find any text to edit. Make sure that a text view has keyboard focus, and then try again.", nil)] runModal];
}
}*/
}

#pragma mark ODBEditor Callbacks
Expand All @@ -318,7 +335,7 @@ - (void)odbEditor:(ODBEditor *)editor didClosefile:(NSString *)path context:(NSD

- (void)odbEditor:(ODBEditor *)editor didModifyFileForString:(NSString *)newString context:(NSDictionary *)context; {
QCUIElement *uiElement = [context valueForKey:@"uiElement"];
if (![uiElement setValue:newString]) {
if (![uiElement writeString:newString]) {
[NSApp activateIgnoringOtherApps:YES];
[[NSAlert alertWithMessageText:NSLocalizedString(@"Could not save text", nil)
defaultButton:NSLocalizedString(@"OK", nil)
Expand Down
6 changes: 5 additions & 1 deletion QCUIElement.h
Expand Up @@ -27,9 +27,11 @@
@property(readonly) pid_t processID;
@property(readonly) NSString *processName;
@property(readonly) QCUIElement *application;
@property(readonly) QCUIElement *menuBar;
@property(readonly) QCUIElement *topLevelUIElement;
@property(readonly) QCUIElement *window;
@property(readonly) QCUIElement *parent;
@property(readonly) NSArray *children;
@property(readonly) NSString *title;
@property(readonly) NSString *role;

Expand All @@ -39,8 +41,10 @@
- (id)valueForAttribute:(NSString *)attributeName;
- (BOOL)setValue:(id)newValue forAttribute:(NSString *)attributeName;

#pragma mark Process
#pragma mark Actions

- (BOOL)activateProcess;
- (NSString *)readString;
- (BOOL)writeString:(NSString *)pasteString;

@end
94 changes: 86 additions & 8 deletions QCUIElement.m
Expand Up @@ -25,7 +25,7 @@ + (QCUIElement *)systemWideElement {
}

+ (QCUIElement *)focusedElement {
return [[self systemWideElement] valueForAttribute:@"AXFocusedUIElement"];
return [[self systemWideElement] valueForAttribute:(NSString *)kAXFocusedUIElementAttribute];
}

#pragma mark Init
Expand Down Expand Up @@ -67,13 +67,17 @@ - (NSString *)processName {
}

- (QCUIElement *)application {
QCUIElement *uiElement = self.topLevelUIElement;
QCUIElement *uiElement = self;
while (uiElement && ![[uiElement role] isEqualToString:(NSString *)kAXApplicationRole]) {
uiElement = uiElement.parent;
}
return uiElement;
}

- (QCUIElement *)menuBar {
return [[self application] valueForAttribute:(NSString *)kAXMenuBarAttribute];
}

- (QCUIElement *)topLevelUIElement {
return [self valueForAttribute:(NSString *)kAXTopLevelUIElementAttribute];
}
Expand All @@ -86,6 +90,10 @@ - (QCUIElement *)parent {
return [self valueForAttribute:(NSString *)kAXParentAttribute];
}

- (NSArray *)children {
return [self valueForAttribute:(NSString *)kAXChildrenAttribute];
}

- (NSString *)title {
return [self valueForAttribute:(NSString *)kAXTitleAttribute];
}
Expand All @@ -105,20 +113,19 @@ - (BOOL)setValue:(id)value {
- (NSArray *)attributeNames {
NSArray* attributeNames;
AXUIElementCopyAttributeNames(uiElementRef, (CFArrayRef *)&attributeNames);
return attributeNames;
return [(id)attributeNames autorelease];
}

- (id)valueForAttribute:(NSString *)attributeName {
id result = nil;
CFTypeRef theValue;

AXError error = AXUIElementCopyAttributeValue(uiElementRef, (CFStringRef)attributeName, &theValue);

if (error != kAXErrorSuccess) {
NSLog(@"error in AXUIElementCopyAttributeValue for attribute %@", attributeName);
return nil;
}
}

if (AXValueGetType(theValue) == kAXValueCGPointType) {
NSLog(@"unimplemented, should not be used by QuickCursor");
} else if (AXValueGetType(theValue) == kAXValueCGSizeType) {
Expand All @@ -130,7 +137,12 @@ - (id)valueForAttribute:(NSString *)attributeName {
} else if (CFGetTypeID(theValue) == AXUIElementGetTypeID()) {
result = [[[QCUIElement alloc] initWithAXUIElementRef:theValue] autorelease];
} else if (CFGetTypeID(theValue) == CFArrayGetTypeID()) {
NSLog(@"unimplemented, should not be used by QuickCursor");
CFIndex count = CFArrayGetCount(theValue);
NSMutableArray *children = [NSMutableArray arrayWithCapacity:count];
for (CFIndex i = 0; i < count; i++) {
[children addObject:[[[QCUIElement alloc] initWithAXUIElementRef:CFArrayGetValueAtIndex(theValue, i)] autorelease]];
}
result = children;
} else {
result = [[[(id)theValue description] copy] autorelease];
}
Expand Down Expand Up @@ -228,4 +240,70 @@ - (BOOL)activateProcess {
return NO;
}

- (NSString *)readString {
NSArray *menuBarItems = [[self menuBar] children];
QCUIElement *editMenu = [[[menuBarItems objectAtIndex:3] children] lastObject];

for (QCUIElement *eachMenuItem in editMenu.children) {
NSString *shortcut = [eachMenuItem valueForAttribute:(NSString *)kAXMenuItemCmdCharAttribute];

if ([shortcut isEqualToString:@"C"]) {
if ([[eachMenuItem valueForAttribute:(NSString *)kAXMenuItemCmdModifiersAttribute] isEqual:@"0"]) {
NSPasteboard *pboard = [NSPasteboard generalPasteboard];
NSString *savedString = [pboard stringForType:NSStringPboardType];
NSString *copiedString = nil;

if (AXUIElementPerformAction(eachMenuItem->uiElementRef, kAXPressAction) == kAXErrorSuccess) {
if (AXUIElementPerformAction(eachMenuItem->uiElementRef, kAXPressAction) == kAXErrorSuccess) { // twice seems neccessary to give pastboard time to record value.
copiedString = [pboard stringForType:NSStringPboardType];
}
}

[pboard setString:savedString forType:NSStringPboardType];

return copiedString;
}
}
}

return nil;
}

- (BOOL)writeString:(NSString *)pasteString {
NSArray *menuBarItems = [[self menuBar] children];
QCUIElement *editMenu = [[[menuBarItems objectAtIndex:3] children] lastObject];

for (QCUIElement *eachMenuItem in editMenu.children) {
NSString *shortcut = [eachMenuItem valueForAttribute:(NSString *)kAXMenuItemCmdCharAttribute];

if ([shortcut isEqualToString:@"V"]) {
NSString *modifiers = [eachMenuItem valueForAttribute:(NSString *)kAXMenuItemCmdModifiersAttribute];

if ([modifiers isEqualToString:@"0"]) {
NSPasteboard *pboard = [NSPasteboard generalPasteboard];
NSString *savedString = [pboard stringForType:NSStringPboardType];
BOOL result = NO;

[pboard setString:pasteString forType:NSStringPboardType];

if ([self activateProcess]) {
sleep(2);
NSLog(pasteString, nil);
if (AXUIElementPerformAction(eachMenuItem->uiElementRef, kAXPressAction) == kAXErrorSuccess) {
if (AXUIElementPerformAction(eachMenuItem->uiElementRef, kAXPressAction) == kAXErrorSuccess) {
result = YES;
}
}
}

[pboard setString:savedString forType:NSStringPboardType];

return result;
}
}
}

return NO;
}

@end
2 changes: 1 addition & 1 deletion QuickCursor.xcodeproj/project.pbxproj
Expand Up @@ -172,6 +172,7 @@
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
88AAF90B105014A500DFD8E5 /* StatusItemIcon.pdf */,
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
Expand Down Expand Up @@ -222,7 +223,6 @@
isa = PBXGroup;
children = (
88864E7B107A3D27001CFB7E /* CrashReporterWindow.xib */,
88AAF90B105014A500DFD8E5 /* StatusItemIcon.pdf */,
88AF3356107FCD190045633D /* StatusItemIcon.png */,
88B27E46104D95A000806BAE /* QuickCursor.icns */,
8D1107310486CEB800E47090 /* QuickCursor-Info.plist */,
Expand Down

0 comments on commit c5392a6

Please sign in to comment.