Search code examples
cocoansevent

Cocoa application: scroll programmatically?


I'm currently developing a cross plattform application for iOS and macOS that makes your iOS device function as a track pad.

Using CGDisplayMoveCursorToPoint, I can move the cursor on the screen. Works like a charm. With this piece of code in the "host" application (i.e. running on macOS), I can do mouse clicks:

let currentPosition = foo() // call to my helper function
                            // that translates NSEvent.mouseLocation()
let downEvent = CGEvent(
    mouseEventSource: nil,
    mouseType: .leftMouseDown, 
    mouseCursorPosition: currentPosition,
    mouseButton: .left
)
let upEvent = CGEvent(
    mouseEventSource: nil,
    mouseType: .leftMouseUp,
    mouseCursorPosition: currentPosition,
    mouseButton: .left
)
downEvent?.post(tap: CGEventTapLocation.cghidEventTap)
upEvent?.post(tap: CGEventTapLocation.cghidEventTap)

So far, so good.

Now I want to implement scrolling by using two fingers on the iOS device. The problem is not the communication between iOS and macOS (btw, PeerTalk is awesome), it's the fact that I can't seem to trigger a scroll event on the host.

At first, I tried something like this:

let eventSource = CGEventSource(stateID: .hidSystemState)
let event = CGEvent(
    mouseEventSource: eventSource,
    mouseType: .scrollWheel,
    mouseCursorPosition: currentPosition,
    mouseButton: .left
)

Unfortunately, event is always nil, and I can't figure out why. The other method I wanted to try is this (yes, I'm using hard-coded values I got from a global event listener, for testing purposes):

NSEvent.mouseEvent(
    with: NSEventType.scrollWheel,
    location: NSPoint(x: 671.0, y: 568.0),
    modifierFlags: .init(rawValue: 0),
    timestamp: 198223.19430632601,
    windowNumber: 14389,
    context: nil,
    eventNumber: 0,
    clickCount: 1,
    pressure: 1.0
)

But my application crashes when creating the event:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: _NSEventMask64FromType(type) & (MouseMask|NSEventMaskMouseMoved)'

Does anyone have an idea on how I could accomplish my scroll feature? Thanks in advance!


Solution

  • I solved it by

    a) using a bridging header:

    #ifndef Header_h
    #define Header_h
    void createScrollWheelEvent();
    #endif /* Header_h */
    

    b) writing a function to create the event in plain old C:

    #include <stdio.h>
    #include <ApplicationServices/ApplicationServices.h>
    
    void createScrollWheelEvent() {
        CGEventRef upEvent = CGEventCreateScrollWheelEvent(
            NULL, 
            kCGScrollEventUnitPixel, 
            2, 1, 1 );
        CGEventPost(kCGHIDEventTap, upEvent);
        CFRelease(upEvent);
    }
    

    c) and calling it directly from my Swift code:

    createScrollWheelEvent()
    

    Using CGEventCreateScrollWheelEvent in Swift is not possible because of the functions variadic nature. The code obviously needs tweaking, but it should give someone stumbling upon this post an idea of how to solve it.

    // edit

    I also noticed that CGDisplayMoveCursorToPoint is not "correct". Yes, it moves the cursor, but when I place the cursor on icons in the dock, there is no popup for the applications name. Therefore, I switched to CGEvent to move the cursor.