Help with addLocalMonitorForEventsMatchingMask

I am porting a carbon app to cocoa and have been unable to get a Declare to work. In the carbon app the user could double click on the title bar to collapse the content region leaving only the title bar visible and double click the bar again to expand it. In the “old days” this was a feature of the Mac OS; it was referred to as Window Shades. Since neither Xojo nor RealBasic provide mouse event in the title bar, I used the carbon function InstallEventHandler to get notified of the required events. This still works for getting application events but not for window events because the GetWindowEventTarget that is used in the process is now returning 0 when fed a cocoa window reference instead of a carbon window.

Cocoa provides a means to get window events with addLocalMonitorForEventsMatchingMask. Tell it which event(s) you want to be notified of (event masks) and it will call the handler/method you specify. It’s better than the carbon route because you are given the opportunity to return nil, which prevents the system from dispatching the event and allows you to assume responsibility for it (just love eating those tasty events).

Listed below are declarations in Objective C and Swift:
(Swift) class func addLocalMonitorForEventsMatchingMask(_ mask: NSEventMask, handler block: (NSEvent) -> NSEvent?) -> AnyObject?
(Objective C) (id)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent * _Nullable (^)(NSEvent *))block

I created a test app in Xcode using Swift and got it working but that didn’t help me with figuring out the declare in Xojo. I would appreciate it if somebody could show me a working declare for this.

if you use MBS Plugin, that’s already available thread safe via NSEventMonitorMBS class.

see
http://www.monkeybreadsoftware.net/class-nseventmonitormbs.shtml

Not exactly true. You can monitor the mouse with System.MouseX, MouseY and MouseDown in a timer, then trigger actions based on where the MouseDown occurred. Since System.Mouse works for the entire screen area, you can spot when something happens in the bar and take the appropriate action.

Michel, thanks for your reply.
Yes, in fact, out of desperation, I have already implemented that in a timer but it doesn’t seem nearly as elegant as addLocalMonitor, but better than nothing, of course.

You will need Joe Ranieri’s Blocks plugin to create the handler parameter.
https://forum.xojo.com/conversation/post/24713

Here’s another way to get clicks in the titlebar: put a Canvas there and use it’s mouse events.

But placing the Canvas in the IDE it’s always added to the windows contentView, which is clipped out from the titlebar. So the Canvas has to be moved from the contentView to the themeFrame to be visible/active in the titlebar. Moving a control like this is ‘mucking with the view hierarchy’ and not sanctioned by Xojo, though I haven’t had problems with it yet. You could work around this by not using a Xojo Canvas, instead create an NSView with mouse handlers de novo in code.

Anyways, here’s the simpler Canvas version. To your Window add a Canvas, name it clickTitler, and lock left, top, right. You can then try to position the Canvas over the titlebar in the IDE or leave it off the window and let the code below position it.

Sub Open()  //Window event

  //position the clickTitler Canvas over the titlebar
  dim b As REALbasic.Rect = self.Bounds
  clickTitler.Left   = 0
  clickTitler.Width  = b.Width
  clickTitler.Top    = self.Height - b.Height
  clickTitler.Height = b.Height - self.Height
  
  //move Canvas to title region
  moveControlsToTitleRegion(Array(clickTitler))

End Sub


Sub DoubleClick(X As Integer, Y As Integer)  //clickTitler event
  beep //collapse/expand window shade
End Sub


Function MouseDown(X As Integer, Y As Integer) As Boolean  //clickTitler event
  return true  //prevents minimizing on double-click
End Function


Private Sub moveControlsToTitleRegion(items() As RectControl)   //Window method
  
  declare function contentView lib "Cocoa" selector "contentView" (id As integer) As Ptr
  declare function superView   lib "Cocoa" selector "superview"   (id As Ptr) As Ptr
  declare function subviews    lib "Cocoa" selector "subviews"    (id As Ptr) As Ptr
  declare function objAtIdx    lib "Cocoa" selector "objectAtIndex:" _
      (id As Ptr, idx As UInt32) As Ptr
  declare sub      addSubView  lib "Cocoa" selector "addSubview:positioned:relativeTo:" _
      (id As Ptr, aView As integer, order As integer, rel As Ptr)
  
  dim cv As Ptr = contentView(self.Handle)
  
  dim themeFrame As Ptr = superView(cv)
  
  dim firstSubView As Ptr = objAtIdx(subViews(themeFrame), 0)
  
  for i As integer = 0 to items.Ubound
    addSubView(themeFrame, items(i).Handle, 0, firstSubView)
  next

End Sub

Adding subviews to the frame view isn’t supported by Apple and I think it might actually cause a warning to be logged to console on 10.10+.

OK, I see theme frame is private api (many on stackoverflow use it :slight_smile: ). I’m not getting console messages though.

Here’s how to declare local monitor.

[code]Sub testLocalMonitor()

declare function NSClassFromString lib “Cocoa” (aClassName as CFStringRef) as Ptr

declare function addLocal lib “Cocoa” selector “addLocalMonitorForEventsMatchingMask:handler:” _
(id As Ptr, mask As UInt64, handler As Ptr) As Ptr

dim NSEventCls As Ptr = NSClassFromString(“NSEvent”)

dim mask As UInt64 = 2 //1 << 1 left mouse down

dim handler As Ptr = ObjCBlocks.CreateBlock(AddressOf handler_impl)

dim r As Ptr = addLocal(NSEventCls, mask, handler)

End Sub

Function handler_impl(e As Ptr) As Ptr
//log “hit”
return e
End Function
[/code]

I’m not sure what the return value is for, the r. Maybe it needs to be released. And you’ll want to remove the monitor and release the block when done.

Using a monitor means you have to go through the shenanigans of testing xy for titlebar hit and timing the double click. Guessing you already have that part.

A simpler (and sanctioned) technique is put a Canvas at the very top of the contentView portion of your window and apply NSFullSizeContentViewWindowMask to the style mask. This moves the contentView up under the titleBar. Then apply titleAppearsTransparent so the Canvas ‘shows through’ and gets clicks. This modifies the look of your window though and maybe not acceptable.

Add Canvas clickTitler just under the title bar. Left and Top at 0, 22 high and full width of window. Locked left, top, right. Position all the other controls under the Canvas as if the Canvas is the titlebar.

[code]Sub Open() //Window event

declare sub setStyle lib “Cocoa” selector “setStyleMask:” (id As integer, value As UInt32)
declare function style lib “Cocoa” selector “styleMask” (id As integer) As UInt32

declare sub setTitlebarAppearsTransparent lib “Cocoa” selector “setTitlebarAppearsTransparent:” _
(windowHandle as integer, value as boolean)

setStyle(self.Handle, style(self.Handle) + 32768) //1<<15 NSFullSizeContentViewWindowMask

setTitlebarAppearsTransparent(self.Handle, true)

End Sub

Sub DoubleClick(X As Integer, Y As Integer) //clickTitler event
Msgbox “double clicked”
End Sub
[/code]

Thanks much for the help, Will.

I decided to implement the “canvas under title bar” approach and it is working fine. I have download the Blocks plugin also and will try that out just to see how it works. The canvas approach introduces the ability to change the color of the title bar, either permanently or temporarily, for particular situations. Since my app allows the user to double click the title bar to collapse the window to the title bar height, I have found it beneficial to change the fill color of the canvas so that the title bar is easier to spot when collapsed, then return the color to normal when expanded. Moving the contentView up to the top of the window required adjusting my code in many areas to get the controls and window locations working properly but it’s definitely a good solution for my problem. Thanks again.