I writing my first OS X command line program which is a server style program. It's job is to process various information and respond to other events.
I have the following code in my main.m
int main(int argc, const char * argv[]) {
#autoreleasepool {
PIPieman *pieman = [[[PIPieman alloc] init] autorelease];
[pieman start];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
while (!pieman.finished && [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
}
return 0;
}
I got this code from various documents and the basic idea is that once pieman.finished is set to YES, that the program then exits.
The problem I have is that the flag is being set by code inside pieman, but the run loop is not being triggered, so the program does not exit. I've been looking for ways to trigger the run loop and there seem to be a variety, but none that feel like a good solution. For example I could reduce the beforeDate: to a few seconds to cause periodic triggering of the run loop.
My preference would be that something triggers the run loop on the change of the finished boolean value.
Any suggestions?
You need to tell the run loop to return from runMode:beforeDate:. The NSRunLoop class doesn't define a message to do that, but NSRunLoop is built on CFRunLoop, and the CFRunLoopStop function does what you need.
#implementation PIPieman
...
- (void)setFinished:(BOOL)finished {
_finished = finished;
CFRunLoopStop(CFRunLoopGetMain());
}
Related
I'm trying to make a simple macOS Cocoa application using NSStatusItem to create a clickable icon on the system status bar. However, when I launch my application, I get this warning and the icon doesn't show up:
2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)
Here's a minimal reproducible example for my application:
#import <AppKit/AppKit.h>
NSStatusItem* statusItem;
int main (int argc, char* argv[]) {
statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
statusItem.button.title = #"foobar";
statusItem.visible = YES;
[NSApplication.sharedApplication run];
return 0;
}
I compiled and ran the example like this:
MacBook-Air-5:Mocha ericreed$ clang -o Mocha_bug_example -framework AppKit -fobjc-arc Mocha_bug_example.m
MacBook-Air-5:Mocha ericreed$ ./Mocha_bug_example
2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)
[Application hung until I pressed Ctrl+C]
^C
MacBook-Air-5:Mocha ericreed$
Note: disabling automatic reference counting and adding [statusItem release]; after calling run as this similar question suggested made no visible difference.
This is how to add status bar item to command line app mac osx cocoa
Adapting apodidae's answer to Swift. Just put this in the main.swift file:
let app = NSApplication()
let statusItem = NSStatusBar.system.statusItem(withLength: -1)
statusItem.button!.title = "Hello, world!"
app.run()
I don't understand the finer details of the NSReleasePool as apodidae included, but it works for me without that.
#import <Cocoa/Cocoa.h>
int main(){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSApplication *application = [NSApplication sharedApplication];
NSStatusItem* statusItem;
statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
statusItem.button.title = #"foobar";
statusItem.visible = YES;
[application run];
[pool drain];
return 0;
}
Save file with the name 'statusBar_SO.m'
Compile from Terminal:
clang statusBar_SO.m -framework Cocoa -o statusBar && ./statusBar
This is not the kind of thing you can do in main().
Except for extrememly unusual situations, you should never modify the main() that comes with the application template, and it must call NSApplicationMain():
int main(int argc, char *argv[])
{
// start the application
return NSApplicationMain(argc, (const char **) argv);
}
The Cocoa framework doesn't get initialized until you call NSApplicationMain() and is generally unusable until then.
This kind of setup should be done in applicationWillFinishLaunching or applicationDidFinishLaunching.
Update
The original poster is not using Xcode and is willing to brave the wilderness alone. ;)
This also implies that their application bundle will not have a main NIB file that would normally create and connect the application delegate object, main menu, and so forth.
There are intrepid individuals who have braved this territory and you can read about it in Creating a Cocoa application without NIB files.
I am porting an app reading data from a BT device to Mac. On the mac-specific code, I have a class with the delegate methods for the BT callbacks, like -(void) rfcommChannelData:(...)
On that callback, I fill a buffer with the received data. I have a function called from the app:
-(int) m_timedRead:(unsigned char*)buffer length:(unsigned long)numBytes time:(unsigned int)timeout
{
double steps=0.01;
double time = (double)timeout/1000;
bool ready = false;
int read,total=0;
unsigned long restBytes = numBytes;
while(!ready){
unsigned char *ptr = buffer+total;
read = [self m_readRFCOMM:(unsigned char*)ptr length:(unsigned long)restBytes];
total+=read;
if(total>=numBytes){
ready=true; continue;
}
restBytes = numBytes-total;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, .4, false);
time -= steps;
if(time<=0){
ready=true; continue;
}
}
My problem is that this RunLoop makes the whole app un extremely slow. If I don't use default mode, and create my on runloop with a runlooptimer, the callback method rfcommChannelData never gets called. I create my one runloop with the following code:
// CFStringRef myCustomMode = CFSTR("MyCustomMode");
// CFRunLoopTimerRef myTimer;
// myTimer = CFRunLoopTimerCreate(NULL,CFAbsoluteTimeGetCurrent()+1.0,1.0,0,0,foo,NULL);
// CFRunLoopAddTimer(CFRunLoopGetCurrent(), myTimer, myCustomMode);
// CFRunLoopTimerInvalidate(myTimer);
// CFRelease(myTimer);
Any idea why the default RunLoop slows down the whole app, or how to make my own run loop allow callbacks from rfcommchannel being triggered?
Many thanks,
Anton Albajes-Eizagirre
If you're working on the main thread of a GUI app, don't run the run loop internally to your own methods. Install run loop sources (or allow asynchronous APIs of the frameworks install sources on your behalf) and just return to the main event loop. That is, let flow of execution return out of your code and back to your caller. The main event loop runs the run loop of the main thread and, when sources are ready, their callbacks will fire which will probably call your methods.
I am using the code below to check if an application is running and close it. Can someone provide an example of how to request an application calose and wait for it to close before proceeding?
+ (BOOL)isApplicationRunningWithName:(NSString *)applicationName {
BOOL isAppActive = NO;
NSDictionary *aDictionary;
NSArray *selectedApps = [[NSWorkspace sharedWorkspace] runningApplications];
for (aDictionary in selectedApps) {
if ([[aDictionary valueForKey:#"NSApplicationName"] isEqualToString: applicationName]) {
isAppActive = YES;
break;
}
}
return isAppActive;
}
+ (void)stopApplication:(NSString *)pathToApplication {
NSString *appPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:pathToApplication];
NSString *identifier = [[NSBundle bundleWithPath:appPath] bundleIdentifier];
NSArray *selectedApps = [NSRunningApplication runningApplicationsWithBundleIdentifier:identifier];
// quit all
[selectedApps makeObjectsPerformSelector:#selector(terminate)];
}
You can use Key-Value Observing to observe the terminated property of each running application. This way, you'll get notified when each application terminates, without having to poll.
One way would be to periodically call isApplicationRunningWithName on a timer, and wait until that function returns NO.
The commandline timelimit will let you send a close signal to an app, wait x seconds, then kill it (or send any other signal you like, kill is -9) if hasn't obeyed the "warning" signal.
(Note: I haven't tried compiling it on Mac, but I believe it's fairly POSIX-compliant code and not Linux-specific as it runs on BSD and others.)
I got a problem that leads to my program reporting malloc corruption when I use NSOpenPanel. My code is mainly C (not using Xcode) and what I do is something like this:
main(..)
{
[NSApplication sharedApplication];
... create window etc, no problem
[NSApp run];
}
Later I call something like this
openFileDialog(..)
{
// tried to create NSAutoreleasePool and things here bit still breaks
NSOpenPanel* open = [NSOpenPanel openPanel];
int res = [open runModal]
...
}
After exiting the function (or a bit later) I will get
test (1948,0x7fff7d852960) malloc: * error for object 0x7ff19b879608:
incorrect checksum for freed object - object was probably modified after being freed.
* set a breakpoint in malloc_error_break to debug
Ideas?
I need to run a complex (ie long) task after the user clicks on a button.
The button opens a sheet and the long running operation is started using dispatch_async and other Grand Central Dispatch stuff.
I've written the code and it works fine but I need help to understand if I've done everything correctly or if I've ignored (due to my ignorance) any potential problem.
The user clicks the button and opens sheet, the block contains the long task (in this example it only runs a for(;;) loop
The block contains also the logic to close the sheet when task completes.
-(IBAction)openPanel:(id)sender {
[NSApp beginSheet:panel
modalForWindow:[self window]
modalDelegate:nil
didEndSelector:NULL
contextInfo:nil];
void (^progressBlock)(void);
progressBlock = ^{
running = YES; // this is a instance variable
for (int i = 0; running && i < 1000000; i++) {
[label setStringValue:[NSString stringWithFormat:#"Step %d", i]];
[label setNeedsDisplay: YES];
}
running = NO;
[NSApp endSheet:panel];
[panel orderOut:sender];
};
//Finally, run the block on a different thread.
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
}
The panel contains a Stop button that allows user to stop the task before its completion
-(IBAction)closePanel:(id)sender {
running = NO;
[NSApp endSheet:panel];
[panel orderOut:sender];
}
This code has a potential problem where it sets value of the status text. Basically all objects in AppKit are only allowed to be called from the main thread and can break in weird ways if they're not. You're calling the setStringValue: and setNeedsDisplay: methods on the label from whatever thread the global queue is running on. To fix this you should write the loop like so:
for (int i = 0; running && i < 1000000; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[label setStringValue:[NSString stringWithFormat:#"Step %d", i]];
[label setNeedsDisplay: YES];
});
}
This will set the label text from the main thread as AppKit expects.