[SOLVED] QTimer and AppNap on OS X 10.9 Mavericks with Qt 4.8.5



  • Hi,

    I'm working on timer app for our project management software activecollab, and i'm using QTimer to record the amount of time spent. Qt version is 4.8.5.

    When i start the QTimer, after a while application being in background it enters the 'app nap' state which is an suspended app state where QTimer stops working until i enter active state like focusing the app. This is obviously wrong and i'm trying to find a way for my app to disable App Nap.

    The best solution would be for app to disable AppNap feature when i start the timer, but i would be satisfied with completely disable-ing App Nap.

    What is the best way of accomplishing this? If there isn't a straightforward way is there any workaround i can use to disable App Nap. I know that end users can disable App Nap by going to app info and checking "disable app nap" but i don't agree on the fact that users have to fiddle around with application properties to make their app working properly.

    thanks


  • Moderators

    Hi,

    Since App Nap is a Mavericks-specific feature, you'll probably need to use native Mavericks API to manage it (assuming that Apple even provides such API). Qt does not provide the tools to configure App Nap.

    [quote]I’m working on timer app for our project management software activecollab, and i’m using QTimer to record the amount of time spent.[/quote]QTimer is for triggering timed events. To measure time, use QElapsedTimer.



  • I think you can set LSAppNapIsDisabled to true in your Info.plist, for your app to opt out of AppNap.



  • @JKSH regarding QElaspedTimer, i'm taking over the work done by previous employee, and he envisioned the QTimer to triggers every second and ads up every second to count number of seconds passed. I must agree that QElapsedTimer would be better solution.

    @sandy.martel Great hint. I will try that and let you know if that worked.



  • [quote author="sandy.martel" date="1391044610"]I think you can set LSAppNapIsDisabled to true in your Info.plist, for your app to opt out of AppNap.
    [/quote]

    Unfortunately, it didn't help.


  • Moderators

    [quote author="godzaman" date="1391047951"]@JKSH regarding QElaspedTimer, i'm taking over the work done by previous employee, and he envisioned the QTimer to triggers every second and ads up every second to count number of seconds passed. I must agree that QElapsedTimer would be better solution.[/quote]If your product is supposed to have a resolution of 1s, then you'll want to investigate the accuracy you're getting. I believe you'll find that your data is inaccurate.

    Your QTimer is highly unlikely to fire exactly every 1s, especially on desktop platforms. You can easily accumulate an error of 5 seconds after 5 minutes have passed: http://qt-project.org/forums/viewthread/32970


  • Lifetime Qt Champion

    Hi,

    Have a look at NSProcessInfo, it seams to be the class that should allow you to interact with App Nap



  • [quote author="JKSH" date="1391052268"]If your product is supposed to have a resolution of 1s, then you'll want to investigate the accuracy you're getting. I believe you'll find that your data is inaccurate.

    Your QTimer is highly unlikely to fire exactly every 1s, especially on desktop platforms. You can easily accumulate an error of 5 seconds after 5 minutes have passed: http://qt-project.org/forums/viewthread/32970[/quote]Thanks, we'll consider refactoring timer.

    [quote author="SGaist" date="1391070607"]Hi,
    Have a look at NSProcessInfo, it seams to be the class that should allow you to interact with App Nap[/quote]

    Great, that pointed me in right direction, but i must admit that i don't know how to call/use native cocoa api from qt code.


  • Lifetime Qt Champion

    You'll need to use Objective-C++, in short provide a C++ header and use Objective-C in the implementation (mm file) e.g.

    @
    appnappsuspender.h

    class AppNapSuspender
    {
    public:
    AppNapSuspender();
    void suspend();
    void resume();

    private:
    int _activityId;
    };

    appnappsuspender.mm

    AppNapSuspender::AppNapSuspender():
    _activityId(0)
    {}

    void AppNapSuspender::suspend()
    {
    _activityId = [[NSProcessInfo processInfo] beginActivityWithOptions: NSActivityUserInitiated reason:@"Good reason"];
    }

    void AppNapSuspender::resume()
    {
    [[NSProcessInfo processInfo] endActivity:_activityId];
    }
    @

    Not test nor compiled, just to give you a base.



  • Awesome, whole day of trying to figure out how to execute objective c code lead me to the similar class, but now i'm having problems with return value of NSProcessInfo beginActivityWithOptions method, as method returns objective c id type, and i cannot find the right way to typecast the response of the method (activityId in your case). I tried casting it to int and long int but compiler raised error that i cannot cast to 'smaller' type, so i tried defining activityID as static void * activityId, to which i casted (id) response of method. Compiler let me through, but linker raised error
    @Undefined symbols for architecture x86_64:
    "AppNapDisabler::activity", referenced from:
    AppNapDisabler::preventAppNap() in AppNapDisabler.o
    AppNapDisabler::allowAppNap() in AppNapDisabler.o
    ld: symbol(s) not found for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)@

    Now i'm properly stuck.


  • Lifetime Qt Champion

    Sorry, I misread the return type of the function, we'll have to do it differently:

    @
    class AppNapSuspenderPrivate;
    class AppNapSuspender {
    public:
    AppNapSuspender();
    ~AppNapSuspender();

    void suspend();
    void resume();
    

    private:
    AppNapSuspenderPrivate *p;
    };

    #include "appnapsuspender.h"
    #include <Foundation/NSProcessInfo.h>

    class AppNapSuspenderPrivate
    {
    public:
    id<NSObject> activityId;
    };

    AppNapSuspender::AppNapSuspender() :
    p(new AppNapSuspenderPrivate)
    {}
    AppNapSuspender::~AppNapSuspender()
    {
    delete p;
    }

    void AppNapSuspender::suspend()
    {
    p->activityId = [[NSProcessInfo processInfo ] beginActivityWithOptions: NSActivityUserInitiated reason:@"Good reason"];
    }

    void AppNapSuspender::resume()
    {
    [[NSProcessInfo processInfo ] endActivity:p->activityId];
    }
    @

    And very important, add:

    @LIBS += -framework Foundation@

    To you pro file



  • Thanks,

    Done that, no errors, no warnings, no linker problems, everything compiles and runs...

    ... but app does enter "app nap" mode, and i debugged it, it executes the nsprocessinfo beginwithactivity method, but after a while (3-4) minutes, app goes into nap again. Maybe there is a time limit for activity :|


  • Lifetime Qt Champion

    I haven't seen something like that but I may have missed it.
    Do you have an operation that takes that long ? Or is it one short that you repeat ?



  • well, it's a timer app that can run in background for hours and while timer is active, app should not enter app nap because as app is programmed currently (not by me) timer executes every second and counts those seconds as a total timer duration. I know it's better to remember only timestamps and then calculate number of seconds passed, but we don't have time right now for bigger refactoring, as this needs to be quick fix.


  • Lifetime Qt Champion

    One other thing that you can try then is use QProcess when starting your application and run this command

    @defaults write <app domain name> NSAppSleepDisabled -bool YES@



  • @SGaist thank you a lot. I think this solved the issue. I'm really grateful for time you spent helping me.


  • Lifetime Qt Champion

    You're welcome !

    Now that your application doesn't go to sleep anymore, please update the thread title prepending [solved] so other forum users may know a solution has been found :)



  • Deal! Thanks again.



  • Hi, after fighting this for days, I finally stumbled on the very helpful info from #SGaist, which appears to work great. One detail that baffled me: for some reason, NSAppSleepDisabled gets removed from my app's plist every time it quits. So, as he describes, you must re-set it each time you launch. A mystery to me.



  • I came to same conclusion, set the NSAppSleepDisabled every time app starts. I know it's not the cleanest way, but it does the job.



  • Adding the following entry in the Info.plist file solved the problem for me:

    @<key>LSAppNapIsDisabled</key>
    <true/>@



  • Hello. I'll also go on the record and state that the LSAppNapIsDisabled entry in Info.plist seems to make Qt more responsive in certain situations. It's not a silver bullet; I'm still ironing out other issues. It is quite useful in general, though!



  • How were you able to get the "defaults write <app domain name> NSAppSleepDisabled -bool YES" to work with QProcess on the Mac? I have tried setting up a QProcess object using arguments and just the entire command as one string and it doesn't seem to set this properly. Yet when I run the same command from a Terminal, it sets it correctly.



  • [quote author="janfaroe" date="1405541281"]Adding the following entry in the Info.plist file solved the problem for me:

    @<key>LSAppNapIsDisabled</key>
    <true/>@[/quote]

    I've also tried this. Placing these entries in my .app plist file, restarting the app and after a time, the "App Nap" value changes to "Yes" in Activity Monitor.

    The only way I can get App Nap to turn off is to use this command from the Terminal:

    defaults -write _domain appname_NSAppSleepDisabled -bool YES



  • Anything you set in the Info.plist is read into the application's Info Dictionary, from where it is then used (or not...). I'm a bit surprised that calling defaults works from the application itself, but that probably means that the corresponding key/value pair is not read before entering the main function.

    All that to say that there might be another solution which doesn't even involve ObjC (if could, but many ObjC classes are "toll-free bridged" with regular C types available through the CoreFoundation framework ;)).

    Here's a bit of code I use to turn an application into an "agent" (i.e. something that can present widgets, like a systray icon+menu", but doesn't appear in the Dock or App Switcher, and doesn't get a global menubar).
    (Replace Q_OS_MAC with Q_OS_OSX in Qt5)

    @
    #ifdef Q_OS_MAC
    #include <CoreFoundation/CoreFoundation.h>
    #endif
    @

    and then before creating the QApplication instance:

    @
    #ifdef Q_OS_MAC
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    if( mainBundle ){
    // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
    // for regular executables it is obtained in another way.
    CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
    if( infoDict ){
    // Add or set the "LSUIElement" key with/to value "1". This can simply be a CFString.
    CFDictionarySetValue(infoDict, CFSTR("LSUIElement"), CFSTR("1"));
    // That's it. We're now considered as an "agent" by the window server, and thus will have
    // neither menubar nor presence in the Dock or App Switcher.
    }
    }
    #endif
    @



  • Anything you set in the Info.plist is read into the application's Info Dictionary, from where it is then used (or not...). I'm a bit surprised that calling defaults works from the application itself, but that probably means that the corresponding key/value pair is not read before entering the main function.

    All that to say that there might be another solution which doesn't even involve ObjC (if could, but many ObjC classes are "toll-free bridged" with regular C types available through the CoreFoundation framework ;)).

    Here's a bit of code I use to turn an application into an "agent" (i.e. something that can present widgets, like a systray icon+menu", but doesn't appear in the Dock or App Switcher, and doesn't get a global menubar).
    (Replace Q_OS_MAC with Q_OS_OSX in Qt5)

    @
    #ifdef Q_OS_MAC
    #include <CoreFoundation/CoreFoundation.h>
    #endif
    @

    and then before creating the QApplication instance:

    @
    #ifdef Q_OS_MAC
    CFBundleRef mainBundle = CFBundleGetMainBundle();
    if( mainBundle ){
    // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist,
    // for regular executables it is obtained in another way.
    CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle);
    if( infoDict ){
    // Add or set the "LSUIElement" key with/to value "1". This can simply be a CFString.
    CFDictionarySetValue(infoDict, CFSTR("LSUIElement"), CFSTR("1"));
    // That's it. We're now considered as an "agent" by the window server, and thus will have
    // neither menubar nor presence in the Dock or App Switcher.
    }
    }
    #endif
    @


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.