在OSX中的另一个应用程序上触发掉落事件

时间:2012-12-21 03:18:40

标签: python xcode macos drag-and-drop pyqt

我使用一些专有软件进行DJ(Native Instruments Traktor)。如果您不熟悉这种事情,只需将其视为一个美化的iTunes:它浏览并播放音频文件。

我想为此构建一个自定义文件浏览器应用程序,但没有任何类型的API。然而, 允许从文件系统中拖放音频文件,这是一个很好的开始。

我正在设计的文件浏览器的性质意味着我不想实际拖放东西 - 我只想在我的应用程序中单击一个组件,并具有相同的效果。所以我正在寻找从我的文件浏览器应用程序以编程方式触发其他应用程序上的drop事件的方法。

我选择的平台是Python和PyQt,但我开始觉得我可能需要更低级别。虽然没有做过大量的C#/ Java,但这可能是一个学习曲线(我已经做了很多ANSI C,但那可能低级......)

这是我有多远:

  • 我做了一个非常简单的PyQt应用程序
  • 我可以在拖动应用程序中的QLabel时创建QDrag对象
  • 我可以附加所有正确的MIME数据来表示音频文件到此
  • 因此,如果我将QLabel从我的应用程序拖放到Traktor中,它会识别音频文件并播放它 - 好时光

所以现在我需要删除中间人,然后点击,打包我的MIME数据,让Traktor认为我已经将它拖放到它上面。

我还在OSX开发人员的文档中进行了一些深入研究,特别是this stuff,它描述了传递给目标应用程序(丢弃目标)的消息序列。

这一切都是有道理的,但是我已经濒临下降到C#/ Java试图模仿这些消息,这听起来像兔子洞,如果我可以避免它,我宁愿不冒险。

所以,在我做之前......

  1. 这甚至可能吗?或者我是否会遇到某种跨应用安全障碍等? (删除目的地只直接从操作系统或其他东西接受消息)
  2. 如果是,有没有更简单的方法呢?理想情况下使用PyQt / Tkinter / wxPython ......?
  3. 我知道我可以通过点击自动化做到这一点,但我可以想象,真的不可靠,会大量依赖窗户的位置等。

    提前致谢!

3 个答案:

答案 0 :(得分:2)

未尝试此操作,但CGEventCreateMouseEventCGEventPostToPSN等内容可能会有所帮助。 CGEvent.h和CGRemoteOperation.h

我也想知道目标应用程序是否可以响应苹果事件 - 如果是,你可以创建苹果事件并将它们发送给它更干净。我尝试运行AppleScript编辑器或Automator,并在相关应用程序上打开字典,看看它是否有可能符合您要求的事件字典。

运气。

答案 1 :(得分:0)

进展!虽然我仍然不知道向应用程序传递了什么事件,但我确实知道文件数据的存储位置:它位于拖动粘贴板上!尝试在某处拖动文件然后运行它:

#include <stdlib.h>
#import <Foundation/Foundation.h>
#import <AppKit/NSPasteboard.h>

int main(int argc, char **argv) {
    (void)argc, (void)argv;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSPasteboard *dragPasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
    NSLog(@"%@", [dragPasteboard stringForType:(NSString *)kUTTypeFileURL]);
    [pool drain];
    return EXIT_SUCCESS;
}

答案 2 :(得分:0)

我知道这是一篇旧文章,但是我正在开发我的Traktor建议程序。我在那里有95%的数据,现在我只需要一种允许用户选择文件,然后单击“加载到Deck A”或“加载到Deck B”的方法。如您所知,Traktor将接受拖动的文件。但我想像当您的DJ时自动执行此操作 触摸鼠标越少越好。

我也非常感兴趣您所做的事情。

我花了点时间才知道,但是我意识到我需要创建一个粘贴板。由于我没有处理图像,只需要提供文件路径(作为NSString ...我也可以使用NSURL,但是直路径似乎最简单)。它们是创建粘贴板,拖动会话以及设置“拖动图像”等的多种方法。然后,我遇到了最简单的形式,即使用简单的NSView函数(需要放在Mouse Down函数中)。并且dragFilePath变量已经设置。 因此,在我的自定义NSView中,我有此代码。您还需要将NSImageView作为Custom NSView的子视图。为了使此“快速”功能正常工作。

自身的dragFile ....创建了一个即时粘贴板项目,所有拖动会话都将产生多行代码。

- (void)mouseDown:(NSEvent*)theEvent {
 NSLog(@"DRAGnDROP VIEW mouseDown happened");
NSLog(@"DRAGnDROP VIEW mouseDown dragFilePath is: %@", dragFilePath);
[self dragFile:dragFilePath fromRect:(self.bounds) slideBack:YES event:theEvent];
}

我有两个按钮,然后触发CGEvents。我在Applescript中有按钮运行功能。 Applescript函数触发鼠标向下,启动小龙,切换到Traktor,然后将鼠标移至Deck A或Deck B,然后释放。

AppleScript函数:

on loadTraktorDeckA:sender
    deckDetailControllerDelegate's loadForDrag:me
    delay 0.5
    tell application "Traktor"
        activate
    end tell
    deckDetailControllerDelegate's loadForReleaseA:me
end loadTraktorDeckA:

on loadTraktorDeckB:sender
    deckDetailControllerDelegate's loadForDrag:me
    delay 0.5
    tell application "Traktor"
        activate
    end tell
    deckDetailControllerDelegate's loadForReleaseB:me
end loadTraktorDeckB:

在自定义NSView中,这些是被称为的CG鼠标事件:

-(void)loadForDrag:(id)sender {
NSLog(@"mouse left drag called");
/* create a new Quartz mouse event.
 * @source : CGEventSourceRef
 * @mouseType : CGEventType
 * @mouseCursorPosition : CGPoint
 * @mouseButton : CGMouseButton
 */
CGEventSourceStateID kCGEventSourceStatePrivate = -1;
CGEventSourceRef loadDragEventRef = CGEventSourceCreate(kCGEventSourceStatePrivate);

CGPoint startPoint = CGPointMake(880.0, 770.0);
CGPoint movePoint1 = CGPointMake(610.0, 320.0);
CGEventRef leftDownEvent = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDown, startPoint, 1);
CGEventRef leftDragEvent1 = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDragged, startPoint, 0);
CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDragged, movePoint1, 0);
/* post a Quartz event into the event stream at a specified location.
 * @tap : CGEventTapLocation
 * @event : CGEventRef
 */
CGEventPost(kCGHIDEventTap, leftDragEvent2);

CGEventSourceSetLocalEventsSuppressionInterval(loadDragEventRef, 2);
CGEventPost(kCGHIDEventTap, leftDownEvent);
CGEventPost(kCGHIDEventTap, leftDragEvent1);
CGEventPost(kCGHIDEventTap, leftDragEvent2);

/**
 * release a Quartz event
 */
    // CFRelease(leftDragEvent);}

-(void)loadForReleaseA:(id)sender {
NSLog(@"mouse left Up called DECK A");
 CGPoint movePoint1 = CGPointMake(610.0, 320.0);
CGPoint movePointRelease = CGPointMake(220.0, 320.0);

CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, movePoint1, 0);
CGEventPost(kCGHIDEventTap, leftDragEvent2);

CGEventRef leftClickUpEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp,movePointRelease, 0);
CGEventPost(kCGHIDEventTap, leftClickUpEvent);

/** release a Quartz event
 */
CFRelease(leftClickUpEvent);}


-(void)loadForReleaseB:(id)sender {
NSLog(@"mouse left Up called DECK B");
CGPoint movePoint1 = CGPointMake(610.0, 320.0);
CGPoint movePointRelease = CGPointMake(1000.0, 320.0);

CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, movePoint1, 0);
CGEventPost(kCGHIDEventTap, leftDragEvent2);

CGEventRef leftClickUpEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp,movePointRelease, 0);
CGEventPost(kCGHIDEventTap, leftClickUpEvent);

CFRelease(leftClickUpEvent);}

这是完整的Custom DragNDropView类

DragNDropView.h

    //
//  DragNDropView.h
//  DJK-Tel Traktor Suggestions
//
//
//

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@interface DragNDropView : NSView <NSDraggingSource, NSDraggingDestination, NSImageDelegate, NSApplicationDelegate>

    //NSPasteboardItemDataProvider

@property (nonatomic, strong) NSString* dragFilePath;
@property (nonatomic, strong) NSURL* dragFilePathURL;

- (id)initWithCoder:(NSCoder *)coder;
- (id)initWithFrame:(NSRect)frameRect;
- (void)mouseDown:(NSEvent*)theEvent;
-(IBAction)loadForDrag:(id)sender;
-(IBAction)loadForReleaseA:(id)sender;
-(IBAction)loadForReleaseB:(id)sender;
- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event;

@end

DragNDropView.m

    //  DragNDropView.m
    //  DJK-Tel Traktor Suggestions
    //
    //
    //

#import "DragNDropView.h"

@implementation DragNDropView


@synthesize dragFilePath;
@synthesize dragFilePathURL;

- (id)initWithCoder:(NSCoder *)coder
{
    /*------------------------------------------------------
     Init method called for Interface Builder objects
     --------------------------------------------------------*/
    self=[super initWithCoder:coder];
    if ( self ) {

        NSLog(@"DRAGnDROP VIEW INIT WITH CODER happened");
            //[self registerForDraggedTypes:[NSArray arrayWithObjects:@"NSFilenamesPboardType",@"NSURLPboardType",nil]];
        [self initView];
    }
    return self;
}
- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        NSLog(@"DRAGnDROP VIEW INIT WITH FRAME happened");
        [self initView];
    }
    return self;
}


- (void)setFrame:(NSRect)frameRect
{
    [super setFrame:frameRect];
}

- (void) initView
{
    NSLog(@"DRAGnDROP VIEW Init View");

    dragFilePath = @"";
    dragFilePathURL = nil;
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
        // Drawing code here.
}


#pragma mark - Destination Operations

- (void)mouseDown:(NSEvent*)theEvent {
    NSLog(@"DRAGnDROP VIEW mouseDown happened");
    NSLog(@"DRAGnDROP VIEW mouseDown dragFilePath is: %@", dragFilePath);
    [self dragFile:dragFilePath fromRect:(self.bounds) slideBack:YES event:theEvent];
}

- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event {
    return YES;
}

- (void)mouseDragged:(NSEvent *)event {
}


- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    NSPasteboard *pboard = [sender draggingPasteboard];
        //NSLog(@"DRAGnDROP VIEW performDragOperation pboard is: %@", pboard);

    if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
        NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];

        if ([files count] == 1) {
            dragFilePath = files[1];
            return YES;
        }
    }
    else if ( [[pboard types] containsObject:NSURLPboardType] ) {
        dragFilePathURL = [NSURL URLFromPasteboard:pboard];
        NSLog(@"DRAGnDROP VIEW performDragOperation dragFilePathURL is: %@", dragFilePathURL);
        return YES;
    }
    return NO;
}

-(void)loadForDrag:(id)sender {
    NSLog(@"mouse left drag called");
    /* create a new Quartz mouse event.
     * @source : CGEventSourceRef
     * @mouseType : CGEventType
     * @mouseCursorPosition : CGPoint
     * @mouseButton : CGMouseButton
     */
    CGEventSourceStateID kCGEventSourceStatePrivate = -1;
    CGEventSourceRef loadDragEventRef = CGEventSourceCreate(kCGEventSourceStatePrivate);

    CGPoint startPoint = CGPointMake(880.0, 770.0);
    CGPoint movePoint1 = CGPointMake(610.0, 320.0);
    CGEventRef leftDownEvent = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDown, startPoint, 1);
    CGEventRef leftDragEvent1 = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDragged, startPoint, 0);
    CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(loadDragEventRef, kCGEventLeftMouseDragged, movePoint1, 0);
    /* post a Quartz event into the event stream at a specified location.
     * @tap : CGEventTapLocation
     * @event : CGEventRef
     */
    CGEventPost(kCGHIDEventTap, leftDragEvent2);

    CGEventSourceSetLocalEventsSuppressionInterval(loadDragEventRef, 2);
    CGEventPost(kCGHIDEventTap, leftDownEvent);
    CGEventPost(kCGHIDEventTap, leftDragEvent1);
    CGEventPost(kCGHIDEventTap, leftDragEvent2);

    /**
     * release a Quartz event
     */
        // CFRelease(leftDragEvent);
}

-(void)loadForReleaseA:(id)sender {
    NSLog(@"mouse left Up called DECK A");
    CGPoint movePoint1 = CGPointMake(610.0, 320.0);
    CGPoint movePointRelease = CGPointMake(220.0, 320.0);

    CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, movePoint1, 0);
    CGEventPost(kCGHIDEventTap, leftDragEvent2);

    CGEventRef leftClickUpEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp,movePointRelease, 0);
    CGEventPost(kCGHIDEventTap, leftClickUpEvent);

    CFRelease(leftClickUpEvent);
}

-(void)loadForReleaseB:(id)sender {
    NSLog(@"mouse left Up called DECK B");
    CGPoint movePoint1 = CGPointMake(610.0, 320.0);
    CGPoint movePointRelease = CGPointMake(1000.0, 320.0);

    CGEventRef leftDragEvent2 = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, movePoint1, 0);
    CGEventPost(kCGHIDEventTap, leftDragEvent2);

    CGEventRef leftClickUpEvent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp,movePointRelease, 0);
    CGEventPost(kCGHIDEventTap, leftClickUpEvent);

    CFRelease(leftClickUpEvent);
}

#pragma mark - Source Operations


- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
    /*------------------------------------------------------
     NSDraggingSource protocol method.  Returns the types of operations allowed in a certain context.
     --------------------------------------------------------*/
    switch (context) {
        case NSDraggingContextOutsideApplication:
            return NSDragOperationCopy;

                //by using this fall through pattern, we will remain compatible if the contexts get more precise in the future.
        case NSDraggingContextWithinApplication:
        default:
            return NSDragOperationCopy;
                //return NSDragOperationNone;
            break;
    }
}

- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
    /*------------------------------------------------------
     accept activation click as click in window
     --------------------------------------------------------*/
        //so source doesn't have to be the active window
    return NO;
}


@end