I am having a problem where [NSRunningApplication activateWithOptions:] returns NO when there is no obvious reason that it should fail to activate an application.
This is not a duplicate of -[NSRunningApplication activateWithOptions:] not working
The accepted answer to that question is to use NSApplicationActivateIgnoringOtherApps instead of NSApplicationActivateAllWindows but that is not an acceptable answer as the two options are intended to activate an application in a different manner as the option values imply.
There are places within my code where [NSRunningApplication activateWithOptions:] does work, however when calling it via an EventTap, it almost always returns NO regardless of using NSApplicationActivateAllWindows or NSApplicationActivateIgnoringOtherApps as the option argument. The reason I say "almost" always returns NO is because the very first time it is called, it does work. All 2nd and subsequent calls fail.
I am basically handling a single-click event on an Application icon in the Dock. I am using an EventTap installed on the Dock process that was created with CGEventTapCreateForPSN(). Within the event tap callback, I have successfully determined the process identifier of the icon that was clicked on in the Dock. The process id was obtained using the Accessibility API.
Now I need to activate the application, so I call:
BOOL result = NO;
NSRunningApplication *currentApp = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
if (currentApp) {
result = [currentApp activateWithOptions:NSApplicationActivateAllWindows];
}
In the above example, I have verified that currentApp is valid, and that bundleIdentifier, processIdentifier and a few other instance vars in the RunningApplication object are correct.
As mentioned above, regardless of the NSApplicationActivationOptions used, the application does not activate, and the method returns NO. Again, it does occasionally work on the very first click, but fails on all subsequent clicks on any application's dock icon.
Can anyone explain why such a simple method is failing? and possibly why it would work only the first time it is called? and why it always works when not executed from an EventTap?
I have spent several hours trying to resolve the issue, and the only thing that always works is to use the deprecated method SetFrontProcessWithOptions(&psn, 0); instead of [NSRunningApplication activateWithOptions:NSApplicationActivateAllWindows].
I have tried wrapping the call to [NSRunningApplication activateWithOptions:] within dispatch_async, I have also tried using an operation queue from within the EventTap, neither of which solves the problem. Finally, I have tried sending a notification and calling [NSRunningApplication activateWithOptions:] from within the notification callback, which also fails. This is the code in my event tap:
case kCGEventLeftMouseDown:
if (cgClickCount == 1) {
NSNumber *pidNumber = [NSNumber numberWithLong:actualPid];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:pidNumber, #"pidNumber", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DockApplicationClickedNotification" object:self userInfo:userInfo];
return NULL; /* swallows event */
}
break;
Any assistance is greatly appreciated! A debug screen grab is attached that shows how the 1st attempt to activate an app by clicking its dock icon is successfully, and how subsequent clicks fail to activate the app.
UPDATE and half-solved:
I have determined why [NSRunningApplication activateWithOptions:] works on the 1st attempt and fails on 2nd and subsequent attempts. The first time I click on an application icon in the dock, my application is the active app. On subsequent clicks on an app icon, some other application is active. So the problem here seems to be that [NSRunningApplication activateWithOptions:] will only work if my application is the frontmost process.
If I add [NSApp activateIgnoringOtherApps:YES]; before the call to [NSRunningApplication activateWithOptions:], it consistently works as intended, however, there is a menubar flicker due to activating my app only to then activate a different app. So the question becomes:
Is there any way to get [NSRunningApplication activateWithOptions:] to work when my application is not the frontmost app? and is activateWithOptions: supposed to work this way?
Related
For a Mac application, I want to detect user activity in the app, so I can periodically let a web service know that the user is still active on the endpoint.
In Cocoa Touch, I would override sendEvent of UIApplication, but the sendEvent in NSApplication equivalent in Cocoa, doesn't do the same.
Which APIs should I use instead for a Mac application, to detect user activity? Can I perhaps somehow have a global responder hookup from where I can send the pings to my service?
Preferably, I want to listen for actions the user can be expected to perform every 15-30 second, ie. clicks, tabs, typing, switching windows or applications.
You most likely want to create a global event monitor using +[NSEvent addGlobalMonitorForEventsMatchingMask:handler:]. This calls your handler whenever an event whose type matches the passed mask (you should use NSAnyEventMask) is sent to another application. You can observe, but not change, the event here, which suits your usage perfectly. There is one thing to watch out for: the documentation says that you won't receive key events unless your app is trusted for Accessibility.
You can do similarly for events that are routed to your own application with +[NSEvent addLocalMonitorForEventsMatchingMask:handler:].
It's not a notification, but you can query the time since user activity using CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType).
This worked for me:
-(void) addMyApplicationEventsMonitor {
self.localEventsMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskAny handler:^NSEvent * (NSEvent * event) {
// do your stuff here
return event;
}];
}
-(void)dealloc{
//remember add this to remove your monitor
[NSEvent removeMonitor:self.localEventsMonitor];
}
reference here
I'm using Cocoa bindings to control the enabling and disabling of buttons and menu items in my app, and in general it is working very nicely. However, there is one issue: the enabling does not update during menu tracking.
My app runs a long task, started by the user. That task runs in the background, and so control is returned to the run loop as soon as the task starts. The user can do other things while the task is going; however, there are some things that the user cannot do while a task is running, and some things that the user can do only when a task is running. I want menu items and buttons related to such things to disable/enable while the task is running, and enable/disable, reciprocally, when the task finishes.
In general this works great; I've got it all working. The only problem is that if the user is tracking through the menu bar at the point in time when the task finishes, the menu items do not change their enabling until the user stops tracking. This means that the user can actually select a menu item that ought to have been disabled, because the enable state of the menu item has not yet been updated.
So I just need a call that I can make at the moment my task finishes that triggers an immediate re-evaluation of enable states based on bindings. Is there a way?
(Note this is not related to multithreading. The task is actually performed on the main thread (for various reasons) using delayed perform requests. So the task finishes during such a run loop perform on the main thread, sets the "finished" property immediately, and then returns, allowing menu tracking to continue. The problem is that AppKit apparently does not do anything with enabling during the menu tracking.)
UPDATE:
As explained in the comments below, I am an idiot. I had switched over to using validateMenuItem: for my menu item enabling, and then forgot that I had done so. So the problem was real, but it was about validateMenuItem:, not about bindings. Anyway, for anybody who finds this post and actually wants to know how to force the menubar to re-update, the quick answer is -[NSMenu update], the long answer that I arrived at is:
- (void)forceImmediateMenuUpdate
{
// So, the situation is that the simulation has stopped playing because the end of the simulation was reached. If that happens while the user
// is tracking a menu in the menu bar, the menus do not get updating (enabling, titles) until the user ends menu tracking. This is sort of a
// bug in Cocoa, I guess; it assumes that state that influences menu bar items does not change while the user is tracking menus, which is false
// in our case, but is true for most applications, I suppose, maybe. Anyway, we need to force an immediate update in this situation.
NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
NSInteger numberOfSubmenus = [mainMenu numberOfItems];
for (int itemIndex = 0; itemIndex < numberOfSubmenus; ++itemIndex)
{
NSMenuItem *menuItem = [mainMenu itemAtIndex:itemIndex];
NSMenu *submenu = [menuItem submenu];
[submenu update];
}
}
I call that method at the point where my task finishes, and the menus all update themselves even as the user is tracking them. (Which is obviously a questionable thing to do in general, but here it is just enabling/disabling items, not shuffling them around under the user's mouse :->)
I hope this helps somebody. Sorry for the noise.
I have a status application, it's long to post it all so I'll describe it and post only part of the code:
In the xib file there are two objects: AboutController and PreferencesController;
The app delegate is able to launch AboutController's and PreferencesController's panels;
The panels are in the xib file too;
The user by selecting a status menu item is able to launch these two panels;
There is a timer that constantly downloads a HTML page and reads it;
When the page is downloaded, the stringValue of a label is changed.But the stringValue may also be changed from the PreferencesController.The page is downloaded from a background thread, but it's changed through the main queue.
Now I've got some questions:
Do I have to invalidate the timer when the application starts sleeping (computer goes in standby),and create another one when it returns on?
The label is updated in the main queue, so I still have to protect the label access with a mutex?
Sometimes the panels are missing: at the start of the application I am able to launch panels by clicking a menu item, but sometimes they are not launching.I don't know how to reproduce this bug always, it just happens randomly when the application is active for 2/3 hours usually, I have to relaunch the application to go around this.
The code is too long, that's only a piece of code:
- (void) checkPosts: (id ) sender
{
NSOperationQueue* queue=[NSOperationQueue new];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^
{
NSNumber* newPosts= [self updatePosts];
NSNumber* posts= [controller posts];
if([posts integerValue]!=[newPosts integerValue])
{
NSOperationQueue* queue=[NSOperationQueue mainQueue];
posts= newPosts;
[queue addOperationWithBlock:^
{
// This is where I may have a race condition
item.attributedTitle=[[NSAttributedString alloc]initWithString: [formatter stringFromNumber: posts] attributes: #{NSForegroundColorAttributeName : [controller color], NSFontAttributeName : [NSFont userFontOfSize: 12.5]}];
}];
// That's not so relevant:
NSUserNotification* notification=[NSUserNotification new];
notification.title= [NSString stringWithFormat: #"posts Changed to %#",posts];
notification.deliveryDate=[NSDate date];
notification.soundName= NSUserNotificationDefaultSoundName;
NSUserNotificationCenter* center=[NSUserNotificationCenter defaultUserNotificationCenter];
[center deliverNotification: notification];
center.delegate= self;
[controller setPosts: posts];
}
}];
}
A little background information:
This method works in a background thread;
[self updatePosts] downloads the HTML page and returns the number of posts;
[controller posts] reads the previous number of posts using NSUserDefaults;
item is a status menu's menu item.
More Details
This is how I get the timer:
// In the applicationDidFinishLaunching method
timer=[NSTimer scheduledTimerWithTimeInterval: [interval integerValue ] target: self selector: #selector(checkReputation:) userInfo: nil repeats: YES];
timer is a property:
#property (nonatomic, strong) NSTimer* timer;
interval is a NSNumber, for sure it's integer value is greater or equal than 1.
It's not entirely clear what's happening here. You've provided plenty of information but not everything needed to give a definitive answer. I'll try to address your questions first:
Do I have to invalidate the timer when the application starts sleeping
(computer goes in standby),and create another one when it returns on?
Have to? No. Should for cleanliness and certainty of the state? Yes, probably. You should probably specify exactly how you set up the timer since you can run into problems with it interacting with the run loop ... but I don't think this is your problem.
The label is updated in the main queue, so I still have to protect the
label access with a mutex?
As long as you update the UI from the main thread/queue, you should be fine. This is a standard design approach with blocks.
Sometimes the panels are missing: at the start of the application I am
able to launch panels by clicking a menu item, but sometimes they are
not launching.I don't know how to reproduce this bug always, it just
happens randomly when the application is active for 2/3 hours usually,
I have to relaunch the application to go around this.
If you don't know how to reproduce it, I'm not sure we can help you beyond "places to look." The first thought I've got is that you may be recreating multiple copies of your primary controllers when the app becomes active (since you asked about this earlier, I assume you've tried doing something with it). Make sure the same controllers are being reused.
Now on to the code.
NSOperationQueue* queue=[NSOperationQueue new];
The queue variable is local to the scope of the method. I see no retain/release, so I assume you're using ARC. In that case, you're not retaining the new queue you're creating and its life span is not guaranteed to survive as long as you need it to once the method completes and you've left its scope. You should make queue an instance variable so it sticks around. This way the queue can be reused every time the method is fired and it'll stay around long enough for other queues/threads to use.
I think it's likely this is your biggest culprit. Adjust it and update your question to reflect how it affects the condition of your app.
I want to make a windowless application. The problem is that I'm using a framework that shows some alert boxes sometimes, and I'm not able to suppress them (at least not without a crash).
I've set LSBackgroundOnly to 1 (in the info.plist app file), but this doesn't seem to work. Any chance I'm missing something here (maybe some other settings that I need to set)?
I've also tried creating a category for NSWindow:
Overwritten alloc and allocWithZone: so that no window gets created. This works on some situations, but starting a modal session throws exception (because sending a nil parameter to beginModalSessionForWindow:).
Tried overwriting NSApplication's beginModalSessionForWindow: and returned nil everytime, but this leads to EXC_BADACCESS somewhere in the framework (runModalSession: and endModalSession: were also overwritten, but the crash is before any of them being called).
Any other ideas will be welcome.
In case it helps, the framework that I'm using is Twain.Framework, so if instead anyone knows a way to block all UI from twain (this includes error alerts shown when a device is not connected) it would be great if you share.
It's not clear what you're hoping for. Are you hoping for an app that doesn't normally display windows but does allow the third-party framework to show its alerts? Or are you hoping to find a way to prevent the framework from showing its alerts so your app absolutely never has any windows?
If it's the former, you're probably looking for LSUIElement. That's an app which can have some UI elements, but doesn't show in the Dock or the Command-Tab application switcher and doesn't have a menu bar.
I managed to make it 'windowless' (some windows are still shown, but at least they're not visible, and they're automatically closed). I only handled modal windows, since these where shown in my situation.
I made a category for NSWindow, so that all windows are invisible, and can't become key or main windows:
-(BOOL)canBecomeKeyWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
-(BOOL)canBecomeMainWindow{
[self setOpaque:NO];
self.alphaValue = 0;
return NO;
}
Subclassed NSApplication:
-(NSInteger)runModalForWindow:(NSWindow *)aWindow{
return NSCancelButton;
}
-(NSModalSession)beginModalSessionForWindow:(NSWindow *)aWindow{
NSModalSession session = [super beginModalSessionForWindow:aWindow];
[aWindow performSelector:#selector(close) withObject:nil afterDelay:1];
return session;
}
However, this didn't help much. In case of runModalForWindow: everything was good, since the owner of the window received a response. In case of beginModalSessionForWindow: the application got stuck, most probably because the window's owner was waiting for a specific event (e.g. button click) which it never received.
I've got an app that changes the screen brightness with [UIScreen mainScreen].brightness = newBrightness, and I want to restore the brightness to it's previous state when the user finishes using it.
I've tried these two delegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
- (void)applicationWillResignActive:(UIApplication *)application
But without much success. I suspect my app must be in the foreground to change the brightness? When I change the brightness in didEnterBackgroundMethod, it has no effect at all. When I use willResignActive it does restore the brightness if I switch to another app, but it has no effect when I press the home button.
Are there any notifications or delegate methods that are executed before the app leaves the foreground?
It seems this happens to others as well: see this S.O. post.
Only way around it seems to be forgetting about setBrightness and simulating it by overlaying a black-semi-transparent on your view...
OLD ANSWER:
willResignActive should also be called when you press the home button before the application enters the background state.
This method is called to let your application know that it is about to move from the active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. An application in the inactive state continues to run but does not dispatch incoming events to responders.
This is also the behavior I witness. So, my guess (but it's just a guess) is that your app is not set to support background, so that when pressing the home button it is terminated. In this case applicationDidEnterBackground is not called.
I would suggest to check the info.plist file in your project for the UIApplicationExitsOnSuspend or "Select Application does not run in background" key.
Furthermore, you could try and put some breakpoints (or NSLog traces) in those functions and check whether they are effectively called as expected.
According to Apple´s DevForum it seems to be a bug that Apple don´t want to fix soon.