CFRunLoopAddSource: Run Mode to execute immediately - macos

I'm trying to add an observer to the active window (could be any program, not just mine) so that it tells me if it's moving. Here's how I added the observer to the run loop:
AXObserverAddNotification(m_observer, (AXUIElementRef)activeWindow, kAXMovedNotification, (__bridge void*)self);
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(m_observer),kCFRunLoopDefaultMode );
The callback that is associated to the observer currently just prints something to the output window. What I observe is that nothing is output when I'm dragging around the active window (again, this could be any app's window) but as soon as I stop, multiples of the printf results are output. Seems like the calls to the callback are queued until there's some idle time for it to execute all at once.
Is there a way to get the callback executed without waiting for idle time? Maybe a CFRunLoopMode that would do that?

Related

How check for Command-Period in a tight loop?

I'm implementing a scripting language where the user might be causing an endless loop by accident. I want to give the user the opportunity to cancel such a runaway loop by holding down the command key while typing the period (".") key.
Currently, once for every line, I check for cancellation with this code:
NSEvent * evt = [[NSApplication sharedApplication] nextEventMatchingMask: NSKeyDownMask untilDate: [NSDate date] inMode: WILDScriptExecutionEventLoopMode dequeue: YES];
if( evt )
{
NSString * theKeys = [evt charactersIgnoringModifiers];
if( (evt.modifierFlags & NSCommandKeyMask) && theKeys.length > 0 && [theKeys characterAtIndex: 0] == '.' )
{
// +++ cancel script execution here.
}
}
The problem with this is that it eats any keyboard events that the user might be typing while the script is running, even though scripts should be able to check for keypresses. Also, it doesn't dequeue the corresponding NSKeyUp events. But if I tell it to dequeue key up events as well, it might dequeue the keyUp for a keypress that was being held before my script started and my application might never find out the key was released.
Also, I would like to not dequeue any events until I know it is actually a cancel event, but there is no separate dequeue call, and it feels unreliable to just assume the frontmost event on a second call will be the same one. And even if it is guaranteed to be the first, that would mean that the user typing an 'a' and then Cmd-. would mean I only ever see the 'a' and never the Cmd-. behind it if I don't dequeue events.
Is there a better option than going to the old Carbon stand-by GetKeys()? Fortunately, that seems to be available in 64 bit.
Also, I'm thinking about adding an NSStatusItem that adds a button to cancel the script to the menu bar or so. But how would I process events in a way that doesn't let the user e.g. select a menu while a script expects to be ruler of the main thread?
Any suggestions? Recommendations?
Using -addLocalMonitorForEventsMatchingMask: as Dave suggests is probably the easiest way to go about this, yes.
I just wanted to add that despite your unreliable feeling, the event queue is really a queue, and events don't change order. It is perfectly safe (and standard practice in event loops) to call -nextEventMatchingMask:inMode:dequeue:NO, examine the event, determine that it is one you want to deal with, and then call -nextEventMatchingMask:inMode:dequeue:YES in order to consume it. Just make sure that your mask and mode are identical between the two calls.
I would suggest using an event monitor. Since you're asking NSApp for events, it would seem that you're running the script in the current process, so you only have to monitor events in your own process (and not globally).
There are several ways to do this (subclassing NSApplication and overriding -sendEvent:, putting in an event tap, etc), but the easiest way to do this would be with a local event monitor:
id eventHandler = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDown handler:^(NSEvent *event) {
// check the runloop mode
// check for cmd-.
// abort the script if necessary
return event;
}];
When you're all done monitoring for events, don't forget to unregister your monitor:
[NSEvent removeMonitor:eventHandler];
So there's +[NSEvent modifierFlags] which is intended as a replacement for GetKeys(). It doesn't cover your use case of the period key though, sadly.
The core problem here with the event queue is you want to be able to search it, which isn't something the API exposes. The only workaround to that I can think of is to dequeue all events into an array, checking for a Command-. event, and then re-queue them all using postEvent:atStart:. Not pretty.
Perhaps as an optimisation you could use +[NSEvent modifierFlags] to only check the event queue when the command key is held down, but that sounds open to race conditions to me.
So final suggestion, override -postEvent:atStart: (on either NSApplication or NSWindow) and see if you can fish out the desired info there. I think at worst it could be interesting for debugging.

"beginModalSessionForWindow" results in a modeless session?

I'm building an application with a main window and a sub-window that I'd like to present as a modal session. So I'm using this code within the window controller:
self.session = [[NSApplication sharedApplication] beginModalSessionForWindow: self.window];
[[NSApplication sharedApplication] runModalSession: self.session];
However, regardless of where I put this code - in windowDidLoad, in a post-windowDidLoad call from the main window, or even in the window controller's init function - what I get is a modeless sub-window. The sub-window appears over the main window, but the main window continues to respond to input events.
What am I doing wrong?
nvm - found my answer by reading some examples. (Once again, hours of research before posting on Stack failed to yield answers, and I stumble across insight right after posting.)
For posterity, here's the answer: beginModalSessionForWindow doesn't work like runModalForWindow.
With runModal, you can execute that code anywhere, and Cocoa will immediately halt all processing except for the modal window. (Unfortunately, that includes timers tied to non-UI events for background processing.) The code that executes runModal is blocked, and resumes only after the modal window is closed. The system enforces the modality of the window.
beginModalSessionForWindow works very differently. Wherever you execute it, the code starts up the modal window and then keeps executing. In the general use case (as demonstrated in Apple's examples), if you call beginModal before a loop and then condition the loop on polling the status of the session, the loop can perform whatever other processing you want; and during the loop, it's also blocking normal UI events - just like any IBAction would when executing a long-running loop.
The point is that beginModalSession actually doesn't enforce any modality of the window. Rather, your code enforces the modality of the window by executing a long-running loop. If you don't use a loop, but instead just let the "modal" session run and resume the ordinary event loop... then your other windows get all event processing, including UI events. The "modal" window becomes modeless.
I will argue that "beginModalSessionForWindow" should really be called beginModelessSessionForWindow, because that's what it does: it creates a modeless window and returns.

ShowWindowAsync doesn't activate a hidden+minimized window?

A given external (not owned by the current process) window (hWnd) is first minimized, then hidden:
ShowWindowAsync(hWnd, SW_MINIMIZE);
// wait loop inserted here
ShowWindowAsync(hWnd, SW_HIDE);
The following call properly restores it to the un-minimized (restored) state:
ShowWindow(hWnd, SW_RESTORE);
However, this call does not:
ShowWindowAsync(hWnd, SW_RESTORE);
In the second instance with ShowWindowAsync(), the window is un-minimized and no longer hidden, but it is not activated (remains behind other existing windows). Conversely, the first ShowWindow() call correctly activates the window.
Is this expected behavior? How can I restore the window (to the foreground) without relying on ShowWindow(), which is synchronous (blocking)? (The wait loop in the example can have a timeout, while ShowWindow() does not allow specification of a timeout.)
(WinXP SP3)
ShowWindowAsync posts a show-window event to the message queue of the given window. In particular, the window is shown by its thread, rather than your thread. And the difference is that your thread is the foreground thread, and can therefore activate another window, which it can't do itself.
Here's the solution as used:
ShowWindowAsync(hWnd, SW_SHOW);
// wait loop inserted here
ShowWindowAsync(hWnd, SW_RESTORE);
This is essentially an inversion of the snippet used to hide the window:
ShowWindowAsync(hWnd, SW_MINIMIZE);
// wait loop inserted here
ShowWindowAsync(hWnd, SW_HIDE);

cocoa - executing stuff, need to open window, get input, then continue w/o reentering runloop

Is there any way to do this?
Right now, I get called, I'm doing things, I have to open a window to ask for input, then I have to FINISH doing things before I return from the original call.
If I enter the runloop for the window, it never ends or returns control to me.
What am I missing here?
I got it -- [NSApp runModalForWindow: window] -- that runs JUST the loop for the window, and when you stop it, control returns to you, leaving the main run loop undisturbed.
What I was doing was [NSApp run], which runs everything, and so when the window issued the stop, everything did.

Updating the progress indicator while downloading firmware to the device

I am developing a cocoa application which downloads firmware to the device. The progress of downloading is showed using NSProgressIndicator. I call the -incrementBy: method of NSProgressIndicator after DeviceRequestTO method in a while loop. But the progress indicator gets updated only after the entire firmware is written to the device. It shows 100% completion at one go itself. So I added the -displayIfNeeded method of NSView class. Now it shows progress smoothly but this too occurs after the firmware download is complete. How can I achieve the progress indication and write operation simultaneously?
Following is the code:
while(1)
{
int result = (*dev)->DeviceRequestTO(dev, &request);
printf("\nBlocks Written Successfully: %d",DfuBlockCnt);
[refToSelf performSelectorOnMainThread:#selector(notifyContent)
withObject:nil
waitUntilDone:NO];
}
//In main thread
- (void)notifyContent{
[dnldIndicator incrementBy:1];
[self displayIfNeeded];
}
The method you need to call is setNeedsDisplay:, not displayIfNeeded. The latter means “send yourself display if somebody has sent you setNeedsDisplay:YES”. If you don't do that last part, the view doesn't know it should display, and displayIfNeeded will do nothing.
And once you add the setNeedsDisplay: message, you may be able to cut out the displayIfNeeded message, as the framework sends that message to the window (and, hence, to all its views) periodically anyway.
Your code looks exactly like some that I use for updating UIProgressIndicators and NSProgressIndicators on the Mac and iPhone, code that works perfectly for me. I'm assuming, like menumachine, that your while loop exists on a background thread (created using performSelectorInBackground:withObject: or NSThread's detachNewThreadSelector:toTarget:withObject:).
Are the minValue and maxValue of the progress indicator set correctly (0 and 100 or whatever your scale is)?
How frequently do updates occur? Maybe you're sending too many events too quickly and the UI is not having a chance to update properly.
This code should work, as far as I can tell.

Resources