I have written an OSX app that uses iCloud document storage. Whenever I open it in Mountain Lion (not on Lion), an iCloud window opens that looks like the following:
Is there a way to prevent this from happening on launch?
Updates:
1) applicationShouldOpenUntitledFile: is not getting called (yes, I'm sure I'm listening in my delegate.
2) If I force quit the app, the next time it opens, I don't get the dialog. But, if I go through the normal Quit process, it does appear.
Update 2 (also added as an answer, to help people that may stumble across this question in the future):
The applicationShouldOpenUntitledFile: from the duplicate question was not working. After lots of experimentation, I figured out that if I remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array, the window is no longer opened. I've added that answer to the duplicate question as well.
Putting below codes in your App Delegate lets you bypass that iCloud pop up New Document screen. Tested for High Sierra.
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
// Schedule "Checking whether document exists." into next UI Loop.
// Because document is not restored yet.
// So we don't know what do we have to create new one.
// Opened document can be identified here. (double click document file)
NSInvocationOperation* op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(openNewDocumentIfNeeded) object:nil];
[[NSOperationQueue mainQueue] addOperation: op];
}
-(void)openNewDocumentIfNeeded
{
NSUInteger documentCount = [[[NSDocumentController sharedDocumentController] documents]count];
// Open an untitled document what if there is no document. (restored, opened).
if(documentCount == 0){
[[NSDocumentController sharedDocumentController]openUntitledDocumentAndDisplay:YES error: nil];
}
}
The applicationShouldOpenUntitledFile: from iCloud enabled - Stop the open file displaying on application launch? was not working. After lots of experimentation, I figured out that if I remove the NSDocumentClass key and value from my Info.plist in the CFBundleDocumentTypes array, the window is no longer opened.
Related
"[GCController controllers]" does not contain any controllers that were connected prior to application launch
TLDR;
I am trying to implement gamepad input on macOS using the Game Controller Framework. When invoked in my code, [GameController controllers] always returns an empty list until new controllers are connected. It never reflects gamepads connected to macOS prior to application launch, except if you disconnect them and reconnect them while the app is running. Does anyone know what I need to do to make controllers populate with pre-launch connections?
Full question
Now that Apple has added support for Xbox and Playstation controllers to the GameController framework, I'm trying to use it for gamepad input on a C++ game engine I'm developing. I'm using the framework instead of IOKit in order to "future-proof" my games to support additional controller types in the future, as well as to simplify my own input handling code.
Like many other game engines, I've foregone using NSApplicationMain() and nib files in favor of implementing my own event loop and setting up my game window programmatically. While my "Windows style" event loop appears to be working correctly, I've discovered that [GCController controllers] does not. The array it returns is always empty at launch, and will only ever reflect controllers that are connected while the game is running. Disconnecting a pre-connected controller does not trigger my GCControllerDidDisconnectNotification callback.
Here is a simplified version of my event loop:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// Create application
[NSApplication sharedApplication];
// Set up custom app delegate
CustomAppDelegate * delegate = [[CustomAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch app
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES]; // Strictly speaking, not necessary
[NSApp finishLaunching]; // NSMenu is set up at this point in applicationWillFinishLaunching:.
// Initialize game engine (window is created here)
GenericEngineCode_Init(); // <-- Where I want to call [GCController controllers]
NSEvent *e;
do
{
do
{
// Pump messages
e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e)
{
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (e);
} while (GenericEngineCode_Run()); // Steps the engine, returns false when quitting.
GenericEngineCode_Cleanup();
}
return 0;
}
I've confirmed that even when using [NSApp run] instead of [NSApp finishLaunching], the behavior is the same. As best as I can tell, the problem is that there's something NSApplicationMain() does that I'm not doing, but that function is a black box -- I can't identify what I need to do to get controllers to populate correctly. Does anyone know what I'm missing?
The closest thing I could find to an explanation of this problem is this answer, which suggests that my app isn't getting didBecomeActive notifications, or that at the least, the private _GCControllerManager isn't getting a CBApplicationDidBecomeActive message. I'm not a professional macOS developer, though: I don't know if this actually applies to my situation, or how I'd go about correcting the problem if it does.
After a huge amount of time searching, I found the answer on my own. It turns out that my code wasn't the problem -- the problem was that my Info.plist file was having its CFBundleIdentifier value stripped out due to a problem with my build system. It appears that the Game Controller Framework needs the bundle identifier to correctly populate [GCController controllers] at launch. While a missing CFBundleIdentifier would have been a problem anyway, as a Windows person it didn't occur to me that the identifier might be used for things besides the App Store, so I let it slide until now.
If someone else has this problem, make sure that CFBundleIdentifier isn't missing or empty in Info.plist in your assembled app bundle. In my case with Premake, I had to manually set PRODUCT_BUNDLE_IDENTIFIER with xcodebuildsettings so that $(PRODUCT_BUNDLE_IDENTIFIER) would get properly replaced in Info.plist.
One of my app's feature is detect keyDown event and do someting for some specific keys.
So, I use CGEventTapCreate to solve my problem and my code is like this:
if let tap = CGEventTapCreate(.CGHIDEventTap, .HeadInsertEventTap, .Default, CGEventMask(1 << CGEventType.KeyDown.rawValue), callBack, nil) {
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode)
CGEventTapEnable(tap, true)
CFRunLoopRun()
}
Also, I deal with the process trust like:
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString: false]
let processTrusted = AXIsProcessTrustedWithOptions(options)
if processTrusted {
// do CGEventTapCreate()
} else {
// check again 1s later
performSelector("checkMethod", withObject: nil, afterDelay: 1)
}
It will be called per second until processTrusted is true.
Then strange thing happened:
When I run the app in Xcode and add trust Xcode in Privacy_Accessibility it all work fine. But When I run it with archive the app and Export as a Mac Application to my desktop, CGEventTapCreate() just not work.
After that I found this post Enable access for assistive devices programmatically on 10.9, it notice me that I need to relaunch the app after AXIsTrustedProcess().
You know, it's not a good idea to tell users to relaunch the app themselves. So I try to relaunch it programmatically and add these code into if processTrusted {}:
NSWorkspace.sharedWorkspace().launchApplication(NSProcessInfo.processInfo().processName)
NSApplication.sharedApplication().terminate(nil)
In other words, when I tick the app in Privacy_Accessibility, it will relaunch automatically.
Here comes another strange thing:
The app truly terminate and launch automatically. But when it finish relaunch, the app's Privacy_Accessibility is not tick.
It's really confuse me a lot, I hope someone will tell me the right way to deal with process trust and execute CGEventTapCreate() correctly.
Thanks!
Polling AXIsProcessTrusted and automatically relaunching is a nice touch. Users are often confused by this process, so anything that helps make it easier for them is good.
I'm pretty sure that an application that you launch inherits your current permissions, but don't have references handy to back that up. I have found that when debugging an application with CGEventTaps, I also have to give Xcode the Accessibility permissions that my app requests/requires.
But you can work around this by getting the system to launch your app for you. Try:
[NSTask launchedTaskWithLaunchPath:#"/bin/sh" arguments:[NSArray arrayWithObjects:#"-c", [NSString stringWithFormat:#"sleep 3 ; /usr/bin/open '%#'", [[NSBundle mainBundle] bundlePath]], nil]];
This question already has answers here:
iCloud enabled - Stop the open file displaying on application launch?
(6 answers)
Closed 7 years ago.
When you open an iCloud enabled document based app on Mac without any currently open documents the open file dialog will appear. How do you prevent that open file dialog from appearing on startup? I have a welcome screen I prefer to show instead.
To verify your statement I created a fresh document based application project in XCode and ran it. I don't get an open file dialog! I do get a blank new document opened though. Is that what you meant? I could not find any documented way of suppressing this initial blank document being opened. I managed to suppress this behavior with the following hack, using the initializer of your Document class:
- (instancetype)init {
self = [super init];
if (self) {
// Add your subclass-specific initialization here.
}
NSLog(#"Document init");
if (alreadysuppressed)
return self;
alreadysuppressed = 1;
return nil;
}
As you can see, it makes use of a variable (called 'alreadysuppressed' here) to remember if the suppression was already done, so it will be done once per application run. I know it's a hack but it works for the generic document based application.
If you are really getting the file open dialog instead of the above behavior then I suggest adding a similar hack to your application delegate class:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSLog(#"applicationShouldOpenUntitledFile: %d", alreadysuppressed);
if (! alreadysuppressed) {
alreadysuppressed = 1;
return NO;
}
return YES;
}
Though I could not test this scenario as I am not getting the file open dialog in the generic document based application.
This is a pretty weird issue. I have a table in my Cocoa application that displays a list of recently opened files. You can double click on an entry to open the associated file. Trouble is, though, after the file is opened once, whether through the Open panel, the Recent Documents menu, or through the aforementioned table, it can't be opened again until the application has quit and re-opened. Other documents, however, can be opened, but once they're closed they can't be opened again either.
This is pretty odd behavior, and I'm not sure what's causing it. But it's certainly annoying. For reference, the Release on Closed attribute of the window from Xcode does nothing and, if selected, does not do anything. I can't think of any other attributes which might cause this behavior. For reference, here's a photo of the attributes panel:
Here's the code for the table which opens the recently opened file:
- (void)respondToRecentFileDoubleClick {
NSInteger clickedRow = [_recentFileBrowser clickedRow];
if (clickedRow != -1) { // We're in the row.
NSDocumentController *docControl = [NSDocumentController sharedDocumentController];
NSURL *selectedDocument = (NSURL *)[docControl recentDocumentURLs][clickedRow];
NSLog(#"Selected row %ld.", (long)clickedRow);
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:selectedDocument display:YES completionHandler:nil];
}
}
The documentation for openDocumentWithContentsOfURL: says that the document won't be opened if it's already opened, but in this case, all of the document windows are closed, so that can't be what causes this behavior. And the NSLog() statement inside the if block prints, so I know the code is being executed.
Anyone know what might be causing this bizarre issue?
From the Xcode image, it appears that you are using your own WindowController. The default close menu item is wired up to call performClose. The performClose method is implemented in NSWindow. So, what is happening is that the window is closing, but the document is not removed from the open document list. Try adding < NSWindowDelegate> to your WindowController interface (in the .h file). Then add to your WindowController .m file:
- (void) windowWillClose:(NSNotification *)notification {
[ourdoc close];
}
Substitute whatever variable you using to hold your document reference for ourdoc. Typically the method setDocument will get called with your document reference. (Also in your WindowController.)
- (void) setDocument:(NSDocument *)document {
ourdoc = (yourNSDocumentsubclass *)document;
}
completionHandler:nil change for debugging:
completionHandler:^(NSDocument *doc, BOOL documentWasAlreadyOpened, NSError *error) {
if (documentWasAlreadyOpened) {
NSLog(#"document was already opened");
NSArray *rats = [[NSDocumentController sharedDocumentController] documents];
NSLog(#"%s seriously: %#", __PRETTY_FUNCTION__, rats);
}
}
My application allows the user to rename documents that are currently open. This is trivial, and works fine, with one really annoying bug I can't figure out. When a file is renamed, AppKit (kindly) warns the user the next time they try to save the document. The user says "OK" and everything continues as normal. This makes sense when something external to the application changed the document, but not when it was actually done by the document itself.
The code goes something like this:
-(void)renameDocumentTo:(NSString *)newName {
NSURL *newURL = [[[self fileURL] URLByDeletingLastPathComponent]
URLByAppendingPathComponent:newName];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager moveItemAtURL:[self fileURL] toURL:newURL];
NSDictionary *attrs = [fileManager attributesForItemAtPath:[newURL path] error:NULL];
[self setFileURL:newURL];
[self setFileModificationDate:[attrs fileModificationDate]];
}
One would think that expressly setting the new URL and modification date on the document would be enough, but sadly it's not. Cocoa still generates the warning.
I've tried changing the order (setting the new URL on the document, THEN renaming the file) but this doesn't help.
I've also tried a fix suggested by a user on an old post over at CocoaDev:
[self performSelector:#selector(_resetMoveAndRenameSensing)];
Even this does not stop the warning however, and I'm guessing there has to be a proper way to do this using the documented API. How does Xcode handle things when a user clicks a file on the project tree and renames it to something else. It doesn't warn the user about the rename, since the user actually performed the rename.
What do I need to do?
There isn't much on this in the main docs. Instead, have a look at the 10.5 release notes: http://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html%23X10_5Notes under the heading "NSDocument Checking for Modified Files At Saving Time"
(In the case of Xcode, it has a long history and I wouldn't be surprised if if doesn't use NSDocument for files within the project)
It is worth noting that moving a file does not change its modification date, so calling -setFileModificationDate: is unlikely to have any effect.
So one possibility could be to bypass NSDocument's usual warning like so:
- (void)saveDocument:(id)sender;
{
if (wasRenamed)
{
[self saveToURL:[self fileURL] ofType:[self fileType] forSaveOperation:NSSaveOperation delegate:nil didSaveSelector:nil contextInfo:NULL];
wasRenamed = NO;
}
else
{
[super saveDocument:sender];
}
}
Ideally you also need to check for the possibility of:
Ask app to rename the doc
Renamed file is then modified/moved by another app
User goes to save the doc
At that point you want the usual warning sheet to come up. Could probably be accomplished by something like:
- (void)renameDocumentTo:(NSString *)newName
{
// Do the rename
[self setFileURL:newURL];
wasRenamed = YES; // MUST happen after -setFileURL:
}
- (void)setFileURL:(NSURL *)absoluteURL;
{
if (![absoluteURL isEqual:[self fileURL]]) wasRenamed = NO;
[super setFileURL:absoluteURL];
}
- (void)setFileModificationDate:(NSDate *)modificationDate;
{
if (![modificationDate isEqualToDate:[self fileModificationDate]]) wasRenamed = NO;
[super setFileModificationDate:modificationDate];
}
Otherwise, your only other choice I can see is to call one of the standard save/write methods with some custom parameters that prompt your document subclass to move the current doc rather than actually save it. Would be trickier I think. Perhaps define your own NSSaveOperationType?
With this technique the doc system should understand that the rename was part of a save-like operation, but it would need quite a bit of experimentation to be sure.
Much inspired from #Mike's answer, I got the "moved to" message not to show up anymore by re-routing NSSaveOperation to NSSaveAsOperation. In my NSDocument subclass:
I overload saveDocumentWithDelegate:didSaveSelector:contextInfo: to determine the save URL and document type (assigning those to self); if the old fileURL exists, I move that to the new location
Inside saveDocumentWithDelegate:didSaveSelector:contextInfo: I redirect the call to [self saveToURL:self.fileURL ofType:self.fileType forSaveOperation:NSSaveAsOperation completionHandler: ...] instead of [super saveDocumentWithDelegate:didSaveSelector:contextInfo:]
This works for me.
Isn't it possible to programmatically answer the question for the user?
Or you can save immediately after renaming, this way a user gets every answer in one go.
I see that this problem is up and running for some time, so telling you to read the reference won't do any good i guess..
Hope i helped a little bit although it doesn't fix your problem directly