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

Posted

技术标签:

【中文标题】在 OSX 中的另一个应用程序上触发 drop 事件【英文标题】:Trigger drop event on another application in OSX 【发布时间】:2012-12-08 15:52:31 【问题描述】:

我使用一些专有软件进行 DJ(Native Instruments Traktor)。如果你不熟悉这种东西,可以把它想象成一个美化的 iTunes:它可以浏览和播放音频文件。

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

我正在设计的文件浏览器的性质意味着我实际上不想拖放东西——我只想在我的应用程序中单击一个组件,并获得相同的效果。因此,我正在寻找从我的文件浏览器应用程序以编程方式触发另一个应用程序上的放置事件的方法。

我选择的平台是 Python 和 PyQt,但我开始觉得我可能不得不降低一点。虽然还没有完成大量的 C#/Java,所以这可能是一个学习曲线(我已经完成了很多 ANSI C 但这可能是低级......)

这是我已经走了多远:

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

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

我还深入研究了 OSX 开发人员的文档,特别是 this stuff,它描述了传递到目标应用程序(放置目标)的消息序列。

这一切都说得通,但我正处于下降到 C#/Java 来尝试模仿这些消息的边缘,这听起来像兔子洞,如果可以避免的话,我宁愿不冒险。

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

    这甚至可能吗?还是我会遇到某种跨应用程序安全障碍等? (删除目的地只接受直接来自操作系统或其他东西的消息) 如果是,有没有更简单的方法?理想情况下使用 PyQt/Tkinter/wxPython...?

我知道我可以通过点击自动化来做到这一点,但我可以想象它真的不可靠,会很大程度上依赖于窗口的位置等。

提前致谢!

【问题讨论】:

您是将音乐文件拖到 Traktor 图标上(在这种情况下,它会运行带有参数的 Traktor,您完全可以自己完成),还是将其拖到 Traktor 制作的窗口中? 进入窗口 - 这是将轨道加载到已经运行的实例中。据我所知,没有 CLI 方法可以做到这一点 - 通过 CLI 调用 Traktor 总是会创建一个新实例,这不是我想要的。 我目前最好的想法是create an event tap 监视应用程序的事件,并在您将文件拖到应用程序上时查看哪些用户数据是这些事件的一部分。 酷,感谢@icktoofay 的建议 - 我会调查一下。 设法设置一个事件点击并查看所有 mouseenter/mousemove/mousemouseup 事件,但看起来这些事件不包含任何拖放数据:所有事件由成功的拖放有 SourceUserData = 0。 【参考方案1】:

没有尝试过,但 CGEventCreateMouseEventCGEventPostToPSN 之类的内容可能会有所帮助。 CGEvent.h 和 CGRemoteOperation.h

我还想知道目标应用程序是否会响应苹果事件 - 如果可以,您可以创建苹果事件并将其发送给它,这样会更干净。我会尝试运行 AppleScript Editor 或 Automator 并在相关应用上打开字典,看看它是否有一个事件字典,可以满足你的需求。

运气。

【讨论】:

不幸的是,它看起来不是可编写脚本的。不过这个建议很好,我在这个过程中学到了一些关于 AppleScript / Apple Events 的知识。我将查看您突出显示的 CGEvent 类 - 可能通过 PyObjC 将所有内容保存在 Python 中(显然仍需要学习一些有关 Objective-C 的知识,但希望不会像在其中重写我的应用程序那样多! ) 因为那些事件不能保存拖放数据,我想我在这里有点死胡同。另一方面,看起来WM_DROPFILES message on Windows 会做我想做的事,所以作为最后的手段,我可​​以背叛...... 我想知道操作系统在这种情况下是否正在帮助应用程序,并在有人在其上放置文件时向其发送 OpenDocument (ODOC) 苹果事件。鉴于您在顶部对 SourceUserData 始终为 0 的评论,这似乎值得研究。尝试阅读本技术说明developer.apple.com/library/mac/#technotes/tn2124/_index.html 的 Apple 事件部分,以设置用于跟踪 Apple 事件的 Env 变量。【参考方案2】:

进步!虽然我仍然不知道将什么事件传递给应用程序,但我确实知道文件数据存储在哪里:它在拖动粘贴板上!尝试将文件拖到某处,然后运行:

#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;

【讨论】:

【参考方案3】:

我知道这是一篇旧帖子,但我正在开发我的 Traktor Suggestions 程序。我已经完成了 95%,现在我只需要一种方法来允许用户选择一个文件,然后单击“加载到甲板 A”或“加载到甲板 B”。如您所知,Traktor 将接受拖动的文件。但我想在你做 DJ 的时候把它自动化 触摸鼠标越少越好。

我也对你的工作非常感兴趣。

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

self 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 函数触发鼠标向下,启动 drake,切换到 Traktor,然后将鼠标移动到甲板 A 或甲板 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);

这是完整的自定义 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

【讨论】:

以上是关于在 OSX 中的另一个应用程序上触发 drop 事件的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个网页上没有触发“drop”事件?

将元素附加到子元素会触发父元素中的 drop 事件

从 Google 工作表过滤器添加的新行上的另一个邮件合并触发器

将文件拖入 cocoa OSX 中的其他应用程序

使用 OS X 中的另一个端口进行 SSH 连接? [关闭]

Drop 事件未在 chrome 中触发