Overriding main.m with run loop but still getting to the NSApplicationMain call? - objective-c

Apologies if this is a stupid/easy question, but still getting used to everything in Mac land.
Dave was kind enough to answer a question for me here:
Modify NSEvent to send a different key than the one that was pressed
which resulted in the following code, which works great:
#import <Cocoa/Cocoa.h>
CGEventRef myCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
//0x0b is the virtual keycode for "b"
//0x09 is the virtual keycode for "v"
if (CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode) == 0x0B) {
CGEventSetIntegerValueField(event, kCGKeyboardEventKeycode, 0x09);
}
return event;
}
int main(int argc, char *argv[]) {
//return NSApplicationMain(argc, (const char **)argv);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CFRunLoopSourceRef runLoopSource;
CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, myCGEventCallback, NULL);
if (!eventTap) {
NSLog(#"Couldn't create event tap!");
exit(1);
}
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
CFRunLoopRun();
CFRelease(eventTap);
CFRelease(runLoopSource);
[pool release];
exit(0);
}
Thanks to some helpful error messages (can't have two "main" methods) I figured out that I had to put this code in the main.m file (right?). That means I'm overwriting the default method:
int main(int argc, char *argv[]) {
return NSApplicationMain(argc, (const char **)argv);
}
Which means none of my other Objective-C based code is firing. But if I uncomment that bit (or make any other attempt to call NSApplicationMain) then the main.m run loop is what doesn't run.
I imagine this is fairly simple for a seasoned Mac guy, but I'm having a hard time wrapping my head around it. Thanks.

I don't see any reason why the same idea won't work elsewhere in your code. Can you put it in the -applicationDidFinishLaunching: method of your app delegate? If you do, you will not need the CFRunLoop() call, since the run loop will already be running. Nor will you need the autorelease pool bit.

Related

Watching for FileSystem Events on MacOS 10.10

I'm trying to code a little command line tool using Xcode (under MacOS 10.10) that watches a specific folder and informs me about changes to files in that folder.
I'm following the guide given in https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html
Here is my current code:
#import <Foundation/Foundation.h>
#include <CoreServices/CoreServices.h>
void mycallback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
int i;
char **paths = eventPaths;
printf("Callback called\n");
for (i=0; i<numEvents; i++) {
int count;
/* flags are unsigned long, IDs are uint64_t */
printf("Change %llu in %s, flags %lu\n", eventIds[i], paths[i], eventFlags[i]);
}
}
int main(int argc, const char * argv[]) {
// #autoreleasepool {
// insert code here...
NSLog(#"Starting to watch ");
/* Define variables and create a CFArray object containing
CFString objects containing paths to watch.
*/
CFStringRef mypath = CFSTR("/Users/testuser/");
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
void *callbackInfo = NULL; // could put stream-specific data here.
FSEventStreamRef stream;
CFAbsoluteTime latency = 3.0; /* Latency in seconds */
/* Create the stream, passing in a callback */
stream = FSEventStreamCreate(NULL,
&mycallback,
callbackInfo,
pathsToWatch,
kFSEventStreamEventIdSinceNow, /* Or a previous event ID */
latency,
kFSEventStreamCreateFlagNone /* Flags explained in reference */
);
/* Create the stream before calling this. */
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(),kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
CFRunLoopRun();
return 0;
}
The code compiles and can be started. However, no events are being fired. I'm pretty new to Xcode and also never ever used a callback function before. So I guess that this is a pretty dumb mistake that I made.
I'd appreciate any hints that may help.
Thanks in advance
Norbert
Update: The code was updated with a working solution from the answer.
According to the documentation, after starting event stream to send events, you should call CFRunLoopRun.
Try to change your while() loop to:
CFRunLoopRun();
Update. My output:
$ ./fsevent
2015-05-17 13:51:29.718 fsevent[898:23601] Starting to watch
Callback called
Change 1165579 in /Users/baf/src/tests/, flags 66560
Callback called
Change 1165594 in /Users/baf/src/tests/, flags 66048
It appears you have FSEventStreamRef stream; commented out. In order to start watching events you'll want to uncomment that line. It also looks like your callback print statement is also commented out, although that appears to be just for debugging perhaps.

Why is this autorelease error occurring with ARC enabled?

I wrote a small CLI program to delete specific Safari cookies for me. Functionally it's fine, but it's throwing up warnings about objects being "autoreleased with no pool in place". My project has ARC enabled, hence why I don't have any autorelease pools.
Here's my code:
// NSLog replacement from http://stackoverflow.com/a/3487392/1376063
void IFPrint (NSString *format, ...) {
va_list args;
va_start(args, format);
fputs([[[NSString alloc] initWithFormat:format arguments:args] UTF8String], stdout);
fputs("\n", stdout);
va_end(args);
}
int main(int argc, const char * argv[])
{
NSString *urlSearchString;
if (argc > 1) {
urlSearchString = [[NSString alloc] initWithUTF8String:argv[1]];
}
else {
IFPrint(#"No URL provided, quitting.");
return 1;
}
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSString *filterString = [[NSString alloc] initWithFormat:#"domain ENDSWITH '%#'", urlSearchString];
NSPredicate *filter = [NSPredicate predicateWithFormat:filterString];
NSArray *matchedCookies = [cookieStorage.cookies filteredArrayUsingPredicate:filter];
for (int i = 0; i < matchedCookies.count; i++) {
[cookieStorage deleteCookie:[matchedCookies objectAtIndex:i]];
}
IFPrint(#"Removed %li cookies", matchedCookies.count);
return 0;
}
The message I get is:
objc[15502]: Object 0x107b2bf00 of class NSThread autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears in the Xcode debugger or when running the release binary directly (slight digression: shouldn't these messages be stripped out of the "release" build?). The line that causes it seems to be:
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
Similarly, if I run it without passing an argument, I get a similar message:
objc[15630]: Object 0x100114ed0 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
objc[15630]: Object 0x100114f80 of class __NSCFData autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears to come from the IFPrint function I'm using (however this doesn't show up when I use the IFPrint when I provide a proper argument).
I'm a bit out of my depth here, can anyone show me where (and how) I've gone wrong?
ARC still requires an autorelease pool. Methods like [NSPredicate predicateWithFormat:filterString] continue to release an autoreleased object (though you no longer need to concern yourself all that much since ARC handles it). Furthermore the internal implementation of any library method you call may create arbitrarily many autoreleased objects while running.
You should wrap your code in an autorelease pool via the #autoreleasepool mechanism.
Wrap the entire body of main with #autoreleasepool like so:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// your code
}
}
All you need to do is add an autoreleasepool in your main.
int main(int argc, const char * argv[])
{
#autoreleasepool
{
//Your code
}
}

Mixing Objective-C with C and code organization

I'm working on a desktop application that watch folders using the fileevent api, so basically this is my code :
#import "PNAppDelegate.h"
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
[(__bridge PNAppDelegate *)clientCallBackInfo reloadStatus];
};
#implementation PNAppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *pathsToWatch = [NSArray arrayWithObject: #"/Users/romainpouclet/Projects/foo"];
void *appPointer = (__bridge void *)self;
FSEventStreamContext context = {0, appPointer, NULL, NULL, NULL};
FSEventStreamRef stream;
CFAbsoluteTime latency = 3.0;
stream = FSEventStreamCreate(NULL,
&callback,
&context,
(__bridge CFArrayRef) pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagNone);
NSLog(#"Schedule with run loop");
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
[self reloadStatus];
}
-(void)reloadStatus
{
}
#end
No problem, it works pretty well for a POC as simple as this one, BUT it feels kinda ugly (and it probably is, I'm not really used to mix Objective-C and C). So here are my questions :
where should I declare my callback? It feels weird having it at the top of my file, just because it worked there.
is it possible to have some kind of #selector-based approach instead of callbacks? (I find them reassuring :D)
Thanks for your time !
Why not put the callback declaration in either PNAppDelegate.h, or its own header file (if you don't want to spread it around your app). That way you can just include the header file and put the function definition anywhere you want. Doing so is standard C functionality.
// Header file callback.h
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]);
// PNAppDelegate.m
#import "PNAppDelegate.h"
#import "callback.h"
#implementation PNAppDelegate
...
#end
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
[(__bridge PNAppDelegate *)clientCallBackInfo reloadStatus];
};
You are correct, that code IS ugly. However, bridging C and Obj-C is no small task, so you really only have a few options:
Create an Objective-C wrapper around the C-based API. This would be my recommended approach, especially if the API is not too complex. It gives you the advantage of using either delegates or blocks, instead of functions.
Use blocks for callbacks, by getting their internal function pointer:
// internal structure of a block
struct blockPtr {
void *__isa;
int __flags;
int __reserved;
void *__FuncPtr;
void *__descriptor;
};
int main()
{
#autoreleasepool {
__block int b = 0;
void (^blockReference)(void *) = ^(void *arg) {
NSLog(#"<%s>: %i", arg, b++);
};
void *blockFunc = ((__bridge struct blockPtr *) blockReference)->__FuncPtr;
void (*castedFunction)(void *, void *) = blockFunc;
// the first argument to any block funciton is the block
// reference itself, similar to how the first argument to
// any objc function is 'self', however, in most cases you
// don't need the block reference (unless reading __block variables), it's just difficult to
// get that first argument from inside the block
castedFunction((__bridge void *) blockReference, "one");
castedFunction((__bridge void *) blockReference, "two");
}
}
I really don't think this is practical in most situations, but if you can find a way to make it work, more power to you.
Stick with how you are currently doing it. It sucks, but that is how C works.

CGDisplayRegisterReconfigurationCallback: callback not called

Why doesn't the function displayChanged get fired in the following code?
#import <Cocoa/Cocoa.h>
static void displayChanged(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void *userInfo) {
NSLog(#"%#, %#", displayID, flags);
}
int main(int argc, const char * argv[])
{
#autoreleasepool {
CGDisplayRegisterReconfigurationCallback(displayChanged, NULL);
CFRunLoopRun();
}
return 0;
}
I'm physically removing (and plugging in) my external display, but the function is never run.
Why?
Just found the solution on this other question
Before calling CFRunLoopRun you have to call NSApplicationLoad to establish a connection with the window server. This is the fixed code for the original question:
#import <Cocoa/Cocoa.h>
static void displayChanged(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void *userInfo) {
NSLog(#"%u, %u", displayID, flags);
}
int main(int argc, const char * argv[])
{
#autoreleasepool {
CGDisplayRegisterReconfigurationCallback(displayChanged, NULL);
NSApplicationLoad(); // establish a connection to the window server. In <Cocoa/Cocoa.h>
CFRunLoopRun(); // run the event loop
}
return 0;
}
I couldn't get CGDisplayRegisterReconfigurationCallback to work, so I used distributed notifications instead:
int main(int argc, const char * argv[]) {
#autoreleasepool {
[[NSDistributedNotificationCenter defaultCenter] addObserverForName:#"com.apple.BezelServices.BMDisplayHWReconfiguredEvent" object:nil queue:nil usingBlock:^(NSNotification *notification) {
NSLog(#"Displays changed!");
}];
CFRunLoopRun();
}
return 0;
}
If you're using AppKit (and have a running NSApplication event loop), you can listen for the NSApplicationDidChangeScreenParametersNotification notification. Or, you can implement the -applicationDidChangeScreenParameters: method in your application delegate, which amounts to the same thing.

CoreMidi.framework sending midi commands

there is an app called FreeStyler, that you can control using midi commands. In my mac app I want to send midi signals.
Can someone show an example of this?
Elijah
This is what it took to send a note to my blofeld synth. I hope it helps. You can use MIDIObjectGetProperties to find the uniqueIDs for all the midi devices connected to your mac.
#import <Foundation/Foundation.h>
#import <CoreMIDI/CoreMIDI.h>
MIDIEndpointRef getEndpointWithUniqueID(MIDIUniqueID id){
MIDIObjectRef endPoint;
MIDIObjectType foundObj;
MIDIObjectFindByUniqueID(id, &endPoint, &foundObj);
return (MIDIEndpointRef) endPoint;
}
MIDIClientRef getMidiClient(){
MIDIClientRef midiClient;
NSString *outPortName =#"blofeldOut";
MIDIClientCreate((CFStringRef)outPortName, NULL, NULL, &midiClient);
return midiClient;
}
MIDIPortRef getOutPutPort(){
MIDIPortRef outPort;
NSString *outPortName =#"blofeldOut";
MIDIOutputPortCreate(getMidiClient(), (CFStringRef)outPortName, &outPort);
return outPort;
}
MIDIPacketList getMidiPacketList(){
MIDIPacketList packetList;
packetList.numPackets = 1;
MIDIPacket* firstPacket = &packetList.packet[0];
firstPacket->timeStamp = 0; // send immediately
firstPacket->length = 3;
firstPacket->data[0] = 0x90;
firstPacket->data[1] = 60;
firstPacket->data[2] = 64;
// TODO: add end note sequence
return packetList;
}
void play_note(void) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MIDIPacketList packetList=getMidiPacketList();
MIDIUniqueID blofeldEndpointID = -934632258;
MIDIEndpointRef blofeldEndpoint = getEndpointWithUniqueID(blofeldEndpointID);
MIDISend(getOutPutPort(), blofeldEndpoint, &packetList);
MIDIEndpointDispose(blofeldEndpoint);
[pool drain];
}
int main (int argc, const char * argv[]) {
play_note();
return 0;
}
Your application will need to use the CoreMIDI framework to send or receive MIDI, which I can tell you from experience is not a lot of fun to work with directly. You might want to try the vvopensource framework, which is a MIDI framework designed for cocoa.