Tapping events (makes live scroll possible)

Mac OSX specific forum
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Tapping events (makes live scroll possible)

Post by wilbert »

Code: Select all

#LeftMouseDownMask      = 1 << 1
#LeftMouseUpMask        = 1 << 2
#RightMouseDownMask     = 1 << 3
#RightMouseUpMask       = 1 << 4
#MouseMovedMask         = 1 << 5
#LeftMouseDraggedMask   = 1 << 6
#RightMouseDraggedMask  = 1 << 7
#KeyDownMask            = 1 << 10
#KeyUpMask              = 1 << 11
#FlagsChangedMask       = 1 << 12
#ScrollWheelMask        = 1 << 22
#OtherMouseDownMask     = 1 << 25
#OtherMouseUpMask       = 1 << 26
#OtherMouseDraggedMask  = 1 << 27

ImportC ""
  CFRunLoopAddCommonMode(rl, mode)
  CFRunLoopGetCurrent()
  CGEventTapCreateForPSN(*psn, place, options, eventsOfInterest.q, callback, refcon)
  GetCurrentProcess(*psn)
EndImport

DeclareC eventTapFunction(proxy, type, event, refcon)

CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), CocoaMessage(0, 0, "NSString stringWithString:$", @"NSEventTrackingRunLoopMode"))
GetCurrentProcess(@psn.q)

mask = #LeftMouseDownMask | #LeftMouseUpMask
mask | #RightMouseDownMask | #RightMouseUpMask
mask | #LeftMouseDraggedMask | #RightMouseDraggedMask
mask | #KeyDownMask

eventTap = CGEventTapCreateForPSN(@psn, 0, 1, mask, @eventTapFunction(), 0)
If eventTap
  CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopCommonModes")
EndIf

; callback function

ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected NSEvent, Window, View, Point.NSPoint
  Static dragObject
  If type > 0 And type < 29
    NSEvent = CocoaMessage(0, 0, "NSEvent eventWithCGEvent:", event)
    If NSEvent
      Window = CocoaMessage(0, NSEvent, "window")
      
      If Window
        CocoaMessage(@Point, NSEvent, "locationInWindow")
        View = CocoaMessage(0, CocoaMessage(0, Window, "contentView"), "hitTest:@", @Point)
        If type = 1
          dragObject = View
        ElseIf type = 2
          dragObject = 0
        EndIf
        Select View
            
          Case GadgetID(0)
            Debug "Gadget 0, event type :" + Str(type)         
            
          Case GadgetID(1)
            Debug "Gadget 1, event type :" + Str(type)         
            
          Case GadgetID(2)
            If dragObject = GadgetID(2) Or type = 2
              Debug "Gadget 2, value :" + Str(GetGadgetState(2))
            EndIf
            
        EndSelect
      Else
        If type = 10
          key.s = PeekS(CocoaMessage(0, CocoaMessage(0, NSEvent, "charactersIgnoringModifiers"), "UTF8String"), 1, #PB_UTF8)
          Debug "Key " + key + " pressed (key code : " + Str(CocoaMessage(0, NSEvent, "keyCode")) + ")"
        EndIf
      EndIf
      
    EndIf
  EndIf
EndProcedure



; *** test ***

If OpenWindow(0, 0, 0, 220, 120, "ButtonGadgets", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ButtonGadget(0, 10, 10, 200, 30, "Button 0")
  ButtonGadget(1, 10, 40, 200, 30, "Button 1")
  ScrollBarGadget(2, 10, 70, 200, 30, 0, 100, 1)
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Here's also a less complex implementation.
The callback is called while the mouse is dragging with the left mouse button pressed.
You still have to check for yourself if anything has changed.

Code: Select all

EnableExplicit

ImportC ""
  CGEventTapCreateForPSN(*psn, place, options, eventsOfInterest.q, callback, refcon)
  GetCurrentProcess(*psn)
EndImport

Define psn.q, eventTap
DeclareC eventTapFunction(proxy, type, event, refcon)

GetCurrentProcess(@psn)
eventTap = CGEventTapCreateForPSN(@psn, 0, 1, 64, @eventTapFunction(), 0)
If eventTap
  CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"NSEventTrackingRunLoopMode")
EndIf

; callback function

ProcedureC eventTapFunction(proxy, type, event, refcon)
  Static Gadget0Value
  
  If GetGadgetState(0) <> Gadget0Value
    Gadget0Value = GetGadgetState(0)
    Debug "Gadget value :" + Str(Gadget0Value)
  EndIf
              
EndProcedure


; *** test ***

If OpenWindow(0, 0, 0, 220, 120, "ButtonGadgets", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ScrollBarGadget(0, 10, 70, 200, 30, 0, 100, 1)
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Last edited by wilbert on Sun May 05, 2013 5:38 am, edited 7 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
J. Baker
Addict
Addict
Posts: 2178
Joined: Sun Apr 27, 2003 8:12 am
Location: USA
Contact:

Re: Tapping events (makes live scroll possible)

Post by J. Baker »

That's pretty cool, thanks! ;)
www.posemotion.com

PureBasic Tools for OS X: PureMonitor, plist Tool, Data Maker & App Chef

Mac: 10.13.6 / 1.4GHz Core 2 Duo / 2GB DDR3 / Nvidia 320M
PC: Win 7 / AMD 64 4000+ / 3GB DDR / Nvidia 720GT


Even the vine knows it surroundings but the man with eyes does not.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Tapping events (makes live scroll possible)

Post by wilbert »

J. Baker wrote:That's pretty cool, thanks! ;)
Thanks :)

I updated my example to also show how to detect a keyboard event.
What I like myself about this tapping using CGEvent is that it doesn't mess with PureBasic internals and works for Cocoa on both x32 and x64.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Tapping events (makes live scroll possible)

Post by luis »

About tapping events:
wilbert wrote:What I like myself about this tapping using CGEvent is that it doesn't mess with PureBasic internals and works for Cocoa on both x32 and x64.
Event taps receive key up and key down events if one of the following conditions is true:

The current process is running as the root user.

Access for assistive devices is enabled. In OS X v10.4, you can enable this feature using System Preferences, Universal Access panel, Keyboard view.
https://developer.apple.com/library/mac ... rence.html

Aren't those two requirements a problem ?

Maybe it's not a good idea to use this approach in your everyday program ?

Just trying to understand ...
"Have you tried turning it off and on again ?"
A little PureBasic review
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Tapping events (makes live scroll possible)

Post by wilbert »

You seem to be right Luis.
I overlooked that. I only have one user account so the root user issue never showed up I guess.
Assistive devices probably was enabled already. On Mavericks my example doesn't seem to work.
I found some information how to enable it on Mavericks
http://www.tekrevue.com/2013/06/25/how- ... mavericks/

When coding using XCode, the most logical approach might be to use the addGlobalMonitorForEventsMatchingMask:handler: class method of the NSEvent class.
The problem with this approach is that it uses a block instead of a callback. No problem when coding using XCode but I don't see how it could be used from within PureBasic.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
luis
Addict
Addict
Posts: 3876
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Tapping events (makes live scroll possible)

Post by luis »

Thank you for the reply.

I'm trying to look around to see what are the most "accepted" ways to intercept keyboard and mouse events in OSX for low level activities like gaming.

I'm writing a library (well, in source form) to help making games and/or demos based on OpenGL, and very well integrated with PB.

In Windows I'm a pretty decent coder and it's the OS I use everyday, so I'm writing the lib for that, but I thought to keep it open to a possible port in the future, since OpenGL is available on the other platforms.
I'm keeping that in mind while writing the code (wrapping platform specific code with more general interface procedures, that sort of thing).

In the meantime I'm SLOOOWLY looking around in OSX to see how some the things I done could be accomplished.

The first thing is low level keyboard/mouse access.

On OSX I found these codes mentioned as "virtual keycodes"

http://forums.macrumors.com/showthread.php?t=780577

They are positional and so they look very much as the scan codes. Then I saw the UCKeyTranslate() API, maybe this one can be used to translate these codes to the corresponding chars valid for the current keyboard layout starting from the key codes I can get from:

Code: Select all

-(void)keyUp:(NSEvent*)event;
-(void)keyDown:(NSEvent*)event;
It's all still a little unclear to me for now...

I tell you what I do under windows (simplifying it a lot because there are many subtleties)

I use the #WM_KEYDOWN / UP to track the keys pressed, using the virtual keys code, and with a joint effort from MapVirtualKey() and GetKeyNameText() I can visualize the name of that key on the current keyboard layout.
With the ones above I can also track control/modifiers keys.
With #WM_CHAR I track the "char" pressed, which can result from a combination of more then one virtual key (i.e.: shift + z = "Z").
With the various #WM_MOUSE*.* I track the mouse movements, enter leaving the OpenGL area, etc.
This work very well.

Basically I would like to see something similar in a PB program using Cocoa for that...in the more appropriate way.

If you have some thought or suggestions I would be very happy to listen !

EDIT: corrected some mistakes, added better explanation...
EDIT 1 year later: ... unfortunately for nothing :wink:
"Have you tried turning it off and on again ?"
A little PureBasic review
mestnyi
Addict
Addict
Posts: 995
Joined: Mon Nov 25, 2013 6:41 am

Re: Tapping events (makes live scroll possible)

Post by mestnyi »

When clicking the mouse button and moving the mouse, how to determine the window under the cursor?

Code: Select all

EnableExplicit

ImportC ""
  CGEventTapCreateForPSN(*psn, place, options, eventsOfInterest.q, callback, refcon)
  GetCurrentProcess(*psn)
EndImport

#LeftMouseDraggedMask   = 1 << 6
#RightMouseDraggedMask  = 1 << 7

Define psn.q, eventTap
DeclareC eventTapFunction(proxy, type, event, refcon)

GetCurrentProcess(@psn)
eventTap = CGEventTapCreateForPSN(@psn, 0, 1, #LeftMouseDraggedMask|#RightMouseDraggedMask, @eventTapFunction(), 0)
If eventTap
  CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopCommonModes")
EndIf

; callback function
ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected NSEvent = CocoaMessage(0, 0, "NSEvent eventWithCGEvent:", event)
  
  If NSEvent
    Protected  Window = CocoaMessage(0, NSEvent, "window")
    
    If Window
      Protected Point.NSPoint
      CocoaMessage(@Point, NSEvent, "locationInWindow")
      Protected contentView = CocoaMessage(0, Window, "contentView")
      
      Protected View = CocoaMessage(0, contentView, "hitTest:@", @Point)
      Debug ""+Window +" "+ contentView +" "+ View
      
    EndIf
  EndIf           
EndProcedure


; *** test ***

OpenWindow(1, 200, 100, 220, 430, "mouse button down", #PB_Window_SystemMenu)
CanvasGadget(1, 10, 10, 200, 200)
CanvasGadget(11, 10, 220, 200, 200)

OpenWindow(2, 400, 200, 220, 220, "get at point window", #PB_Window_SystemMenu)
CanvasGadget(2, 10, 10, 200, 200)

Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
I found a solution, maybe it will help someone.

Code: Select all

EnableExplicit

ImportC ""
  CGEventTapCreateForPSN(*psn, place, options, eventsOfInterest.q, callback, refcon)
  GetCurrentProcess(*psn)
EndImport

#LeftMouseDraggedMask   = 1 << 6
#RightMouseDraggedMask  = 1 << 7

Define psn.q, eventTap
DeclareC eventTapFunction(proxy, type, event, refcon)

GetCurrentProcess(@psn)
eventTap = CGEventTapCreateForPSN(@psn, 0, 1, #LeftMouseDraggedMask|#RightMouseDraggedMask, @eventTapFunction(), 0)
If eventTap
  CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopCommonModes")
EndIf
Global NSApp = CocoaMessage(0, 0, "NSApplication sharedApplication")
      
; callback function
ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected Point.NSPoint
  Protected NSEvent, WindowNumber, Window, contentView, View
  NSEvent = CocoaMessage(0, 0, "NSEvent eventWithCGEvent:", event)
  
  If NSEvent
    Window = CocoaMessage(0, NSEvent, "window")
    
    If Window
      CocoaMessage(@Point, 0, "NSEvent mouseLocation")
      
      ; 
      WindowNumber = CocoaMessage(0, 0, "NSWindow windowNumberAtPoint:@", @Point, "belowWindowWithWindowNumber:", 0)
      Window = CocoaMessage(0, NSApp, "windowWithWindowNumber:", WindowNumber)
      
      If Window
        CocoaMessage(@Point,  Window , "mouseLocationOutsideOfEventStream")
        contentView = CocoaMessage(0, Window, "contentView")
        View = CocoaMessage(0, contentView, "hitTest:@", @Point)
      EndIf
        
      Debug ""+Window +" "+ contentView +" "+ View
      
    EndIf
  EndIf           
EndProcedure


; *** test ***

OpenWindow(1, 200, 100, 220, 430, "mouse button down", #PB_Window_SystemMenu)
CanvasGadget(1, 10, 10, 200, 200)
CanvasGadget(11, 10, 220, 200, 200)

OpenWindow(2, 400, 200, 220, 220, "get at point window", #PB_Window_SystemMenu)
CanvasGadget(2, 10, 10, 200, 200)

Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
Last edited by mestnyi on Thu Sep 15, 2022 4:35 am, edited 1 time in total.
mestnyi
Addict
Addict
Posts: 995
Joined: Mon Nov 25, 2013 6:41 am

Re: Tapping events (makes live scroll possible)

Post by mestnyi »

You can also supply a mask to observe all events:
CGEventMask mask = kCGEventMaskForAllEvents;
does anyone know how to use this?
Post Reply