I'm trying to use FSEvents in my sandboxed app to monitor some directories. I implemented the following class:
#implementation SNTracker
- (id)initWithPaths:(NSArray *)paths {
self=[super init];
if (self) {
trackedPaths=paths;
CFTimeInterval latency=1.0;
FSEventStreamContext context={0,(__bridge void *)self,NULL,NULL,NULL};
FSEventStreamRef eeventStream=FSEventStreamCreate(kCFAllocatorDefault,&callback,&context,(__bridge CFArrayRef)trackedPaths,kFSEventStreamEventIdSinceNow,latency,kFSEventStreamCreateFlagUseCFTypes|kFSEventStreamCreateFlagWatchRoot|kFSEventStreamCreateFlagFileEvents);
FSEventStreamScheduleWithRunLoop(eeventStream,[[NSRunLoop mainRunLoop] getCFRunLoop],kCFRunLoopDefaultMode);
FSEventStreamStart(eeventStream);
}
return self;
}
static void callback(ConstFSEventStreamRef streamRef,void *clientCallBackInfo,size_t numEvents,void *eventPaths,const FSEventStreamEventFlags eventFlags[],const FSEventStreamEventId eventIds[]) {
NSLog(#"asd");
}
The problem is that "asd" never gets printed (i.e. the callback function is never called). When I disable "Enable App Sandboxing" in the Summary of my main target in Xcode, the callback gets called. Am I doing something wrong? The only entitlement I've given to the sandboxed app is read-write access to user selected files.
And the usere has selected the path you are trying to monitor via FSEvent? Since if he hasn't, you won't be allow to access it and thus also not monitor it. A path can only be monitored as long as you are allowed to access it.
Related
I'm developing a screensaver for Mac OS X and I need to do some method swizzling so I made a small experiment:
#implementation CAHTTPCookieStorage
+ (void) highjack {
NSLog(#"Attempting to highjack cookies.");
Class originalClass = [NSHTTPCookieStorage class];
Method originalMeth = class_getClassMethod(originalClass, #selector(sharedHTTPCookieStorage));
Method replacementMeth = class_getClassMethod([self class], #selector(patchedSharedHTTPCookieStorage));
method_exchangeImplementations(originalMeth, replacementMeth);
}
+ (NSHTTPCookieStorage*) patchedSharedHTTPCookieStorage {
NSLog(#"Cookies have been highjacked!!!!");
return [CAHTTPCookieStorage patchedSharedHTTPCookieStorage];
}
#end
I'm calling CAHTTPCookieStorage.highjack() from my app from AppDelegate.init() and from my screensaver from the ScreenSaverViewSubclass.init(...). While running my app or the screensaver in preview mode (inside the system preferences), it works fine, but when I run it as a proper screensaver I can see the message "Attempting to highjack cookies." but never "Cookies have been highjacked!!!!".
Any ideas what might be going wrong? Maybe an issue with threads? is method swizzling per thread?
Further investigation on this proved that this was true only in 10.10 in dual monitor mode but not in 10.9 with two monitors or 10.10 with a single monitor. I'm not sure what caused it, but using the +load method for doing the swizzling solved it:
+ (void) load {
NSLog(#"Attempting to highjack cookies.");
Class originalClass = [NSHTTPCookieStorage class];
Method originalMeth = class_getClassMethod(originalClass, #selector(sharedHTTPCookieStorage));
Method replacementMeth = class_getClassMethod([self class], #selector(patchedSharedHTTPCookieStorage));
method_exchangeImplementations(originalMeth, replacementMeth);
}
How can I use IKScannerDeviceView to scan a document inside of my app?
I tried adding an IKScannerDeviceView into my view through IB and setting its delegate to my app delegate (which implements the IKScannerDeviceViewDelegate), but when I run the app I get a view with the buttons Show Details and Scan, and only Show Details is enabled and when I click it nothing happens.
I have a scanner plugged in and I can scan through Image Capture, but not through my app.
Does anybody have a good tutorial on how to use it?
I was finally able to figure out how to use IKScannerDeviceView.
Your class must implement the following delegates:
IKScannerDeviceViewDelegate, ICScannerDeviceDelegate, ICDeviceBrowserDelegate
and you need to have an IKScannerDeviceView in your window, with its delegate set to the class implementing IKScannerDeviceViewDelegate
To start using it, you must create an ICDeviceBrowser like so:
ICDeviceBrowser *mDeviceBrowser = [[ICDeviceBrowser alloc] init];
mDeviceBrowser.delegate = self;
mDeviceBrowser.browsedDeviceTypeMask = ICDeviceLocationTypeMaskLocal|ICDeviceLocationTypeMaskRemote|ICDeviceTypeMaskScanner;
[mDeviceBrowser start];
Then implement the delegate methods in a manner similar to this:
- (void)scannerDeviceDidBecomeAvailable:(ICScannerDevice*)scanner;
{
[scanner requestOpenSession];
}
- (void)deviceBrowser:(ICDeviceBrowser*)browser didAddDevice:(ICDevice*)addedDevice moreComing:(BOOL)moreComing
{
if ( (addedDevice.type & ICDeviceTypeMaskScanner) == ICDeviceTypeScanner )
{
[scannerView setScannerDevice:(ICScannerDevice*)addedDevice];
}
}
-(void)didRemoveDevice:(ICDevice*)removedDevice
{
[removedDevice requestCloseSession];
}
Then if all goes right, your IKScannerDeviceView will be able to interact with your scanner!
hi I'm using DiskArbitration.framework to get list of disks
+(NSArray*)arrayOfDisks {
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (session) {
DARegisterDiskAppearedCallback(session, NULL, driveGo, NULL);
DASessionScheduleWithRunLoop(session,
CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(session);
}
return nil;
}
void driveGo(DADiskRef disk, void *context) {
NSLog(#"%s", DADiskGetBSDName(disk));
}
it logs up just fine, but how can I return array back?
it just loop so I even dont know how to check is it done or not.
You could change your method and store the array in a private variable:
(void*) driveGo(DADiskRef disk, void *context) {
NSLog(#"%s", DADiskGetBSDName(disk));
self->_myArray = DADiskGetBSDName(disk);
}
it just loop so I even dont know how to check is it done or not.
There is no “done”. Your callback will be called for every “disk” that is currently known to DiskArb and every “disk” that appears from then on. There is no separation between the two.
Most applications should work with that, rather than against it. Let DiskArb tell you the moment new “disks” appear, change (DescriptionChanged), are mounted or unmounted (also DescriptionChanged), or disappear. Keep your information up to date as those changes come in, and always have the current state.
Most applications do not need to get a complete and fixed snapshot of the current set of mounted volumes. But if, for some reason, you do, you might try the getmntinfo function instead.
I'm trying to implement a custom URL scheme for my application. I've added the necessary lines for my Info.plist. After calling the specified url (eg.: myapp://) the application launches.
If I want to handle the URL, I've found these steps:
#interface EventHandler : NSObject {
}
#end
#implementation EventHandler
- (id)init {
self = [super init];
if (self) {
NSLog(#"eventHandler::init");
NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:#selector(applicationDidFinishLaunching:)
// name:NSApplicationWillFinishLaunchingNotification
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
[appleEventManager setEventHandler:self andSelector:#selector(handleGetURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
}
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
NSString* url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
NSLog(#"%#", url);
}
#end
The above code is working if the application is running, but if the URL gets called and the application was terminated, the event is not caught. I think this is because this: NSApplicationDidFinishLaunchingNotification.
Changing it to NSApplicationWillFinishLaunchingNotification causes that non events caught. Maybe Qt handles it before me, but I can't find a workaround for the problem.
I was also trying to get my Qt-based application handle a custom URL scheme on the Mac and went down the same path as the original poster. It turns out that Qt4 already supports URL events on the Mac, and there's no need to write Objective-C code to receive them. This is in fact the reason that you didn't receive any URL events when you set the event handler in response to NSApplicationWillFinishLaunchingNotification: Qt registers its own handler afterward.
When a URL with your custom scheme is triggered, your Qt application will receive a FileOpenEvent. Note that it is the QApplication instance which receives the event. You can catch it by making your application subclass QApplication or by installing an event filter on the standard QApplication. I opted for this second approach.
Here's the eventFilter method of my custom event filter class, FileOpenEventFilter. It just emits the signal urlOpened when the event contains a non-empty URL. It also saves the last opened URL in case my main window isn't completely initialized when the event arrives (which happens in my app when it's not already running when the custom URL is clicked.)
bool FileOpenEventFilter::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::FileOpen)
{
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
if (!fileEvent->url().isEmpty())
{
m_lastUrl = fileEvent->url().toString();
emit urlOpened(m_lastUrl);
}
else if (!fileEvent->file().isEmpty())
{
emit fileOpened(fileEvent->file());
}
return false;
}
else
{
// standard event processing
return QObject::eventFilter(obj, event);
}
}
I register my handler in my application delegate's applicationWillFinishLaunching: method, and I don't miss any events. You're probably initializing your EventHandler object too late to get that notification. If you want to keep it as a separate class, that's ok, but you should create your object and register it with NSAppleEventManager within the applicationWillFinishLaunching: method of your application delegate.
I have a Cocoa plug-in that is loaded into an existing Carbon application.
When the plug-in is first loaded, the Carbon application calls an initialization function, Plugin_Init() and in that function I set up the environment like so:
//this is the global autorelease pool
static NSAutoreleasePool* globalPool = nil;
void Plugin_Init()
{
NSApplicationLoad(); //loads the Cocoa event loop etc
//create an autorelease pool
globalPool=[[NSAutoreleasePool alloc] init];
//callback functions are registered here
Plugin_defineFunction("doSomething",doSomething,0);
}
However, the Carbon app does not send any notifications when the application is about to terminate.
Is it actually necessary to clean up the "global" autorelease pool that I've created when the app terminates?
I tried registering for the Carbon app quit event by adding a call to the registerForApplicationQuitNotification() function below, but when the application terminated I received warnings that I was calling -release on an invalid autorelease pool. Is there a problem with how I'm handling the Carbon events?
//handles the Carbon application quit notification
static pascal OSStatus handleApplicationQuitEvent(EventHandlerCallRef nextHandler, EventRef evt, void *ud)
{
OSStatus err = noErr;
UInt32 evtkind;
evtkind = GetEventKind( evt );
if ( evtkind == kEventAppQuit )
{
//release the global autorelease pool
[globalPool release];
}
// call the rest of the handlers
err = CallNextEventHandler( nextHandler, evt);
return err;
}
//registers for the Carbon application quit notification
void registerForApplicationQuitNotification()
{
// install an event handler to tear down some globals on Quit
static EventHandlerUPP app = NULL;
EventTypeSpec list[] = {
{kEventClassApplication, kEventAppQuit},
};
app = NewEventHandlerUPP( handleApplicationQuitEvent );
if (!app)
return;
InstallApplicationEventHandler(app, GetEventTypeCount(list), list, NULL, NULL);
}
It's quite likely that NSApplicationLoad sets up NSApplication's first autorelease pool, which will be below yours in the autorelease pool stack (since it was created first). In the background, it will drain this pool and create a new one as it needs to; the first time this happens, your pool goes away, since it was above Cocoa's pool in the stack.
The simple solution is to cut out your global pool and let NSApplication create it. An alternative would be to create and drain local pools within each handler function, particularly if you don't actually need anything from the Application Kit in your plug-in.
If the autorelease pool is the only cleanup you need to do, then no, you don't need to register for a quit notification. Any pool you create is still within the application's address space, which will be freed by the OS when the process terminates.
Also, autorelease pools are typically created per-thread. If your callbacks invoked on different threads, you may need to create a pool per thread. Note that Cocoa also needs to be told that it's operating in a multithreaded environment; see the Threads section of the NSAutoreleasePool reference.