I am developing a C++ app and I need to display a NSWindow with a WebKit WebView inside it. I've coded up the Objective-C class which will manage creating and displaying the window but the WebView contained inside it does not display. Here is my code. Any idea on what is wrong and how I can fix it?
I'm compiling the below code with
$g++ -x objective-c++ -framework Cocoa -framework WebKit Foo.m main.m -o test
Foo.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface Foo :NSObject {
NSWindow *window;
WebView *view;
}
- (void)displayWindow;
#end
Foo.m
#import "Foo.h"
#implementation Foo
- (id)init {
self = [super init];
// Window Container
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(500.0f,500.0f,250.0f,250.0f)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreNonretained
defer:NO];
// WebView
view = [[WebView alloc] initWithFrame:NSMakeRect(0, 0, 250.0f, 250.0f)
frameName:#"Frame"
groupName:nil];
[[view mainFrame] loadHTMLString:#"<html><head></head><body><h1>Hello</h1></body></html>"
baseURL:nil];
return self;
}
- (void)displayWindow {
NSLog(#"In Display window");
[window setContentView:view];
[window setLevel:NSStatusWindowLevel];
[window orderFrontRegardless];
sleep(5); // leave it up for 5 seconds
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
main.m
#import "Foo.h"
int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
Foo *foo = [[Foo alloc] init];
[foo displayWindow];
[foo release];
[pool release];
return 0;
}
You need to run the run loop. If you just order the window in and then exit, that's exactly what will happen: The window will appear, and then (five seconds later) your program will exit. You can run the run loop by telling the application (which you create but don't otherwise use) to run.
On the main thread of a Cocoa app, sleep is always the wrong answer. The same goes for its Cocoa cousins, +[NSThread sleepUntilDate:] and +[NSThread sleepForTimeInterval:]. The run loop will let you tell it to run for a fixed amount of time, but that won't get the application running; you do need to send the application the run message, which provides no opportunity to exit after a fixed interval.
The solution there is to first create an NSTimer object whose target is the application and whose selector is #selector(terminate:). Create it scheduled and non-repeating, with the interval set to five seconds. (Creating it scheduled means you don't need to schedule it separately—it is already ready to go from the moment you create it.) Then, send the application the run message. Five seconds later, the run loop will fire the timer, which will tell the application to terminate itself. This is assuming that you actually have a good reason to make your application quit after five seconds.
As noted by Yuji, every window in modern Cocoa should use NSBackingStoreBuffered.
And don't forget to release what you have created; you currently are forgetting that in the case of the view. See the Memory Management Programming Guide for Cocoa.
Once you have this working, I suggest moving toward a more typical architecture for this application:
Create a subclass of NSObject, and make an instance of that class your application's delegate.
Put the window and its WebView into a nib, and have the app delegate create a window controller to load and own the contents of that nib.
The app delegate should also be responsible for loading the page into the WebView and for setting up the self-termination timer.
Finally, create a nib to hold your application's main menu (the contents of the menu bar) and the application delegate. Interface Builder has a template for the first part; you create the app delegate object by dragging a blank Object in from the Library, setting its class on the ⌘6 Inspector, and dragging the connection from the application to the object. Then, you can reduce main to the single line that Xcode's project templates put in it: return NSApplicationMain(argc, argv);.
Doing all this will help your understanding of Cocoa, as well as your maintenance of the application—cramming everything into main will not scale.
You should also read the Cocoa Fundamentals Guide, if you haven't already.
Don't make it sleep. It stops the execution of the main thread, in which the GUI is dealt with. Instead, you need to run the run loop. Also, Cocoa needs to set itself up. So, call [[NSApplication sharedApplication] run] to set it up correctly and run the event loop.
Also, don't use backing mode other than buffered mode. Other modes are remnants from the time immemorial, and only NSBackingStoreBuffered should be used. As discussed in this Apple document, the non-retained mode is a remnant to support Classic Blue Box (OS 9 virtualizer), and newer classes like WebKit just can't operate within it.
So, what you need to do is practically:
change NSBackingStoreNonretained to NSBackingStoreBuffered.
Remove the line
sleep(5);
add a line
[[NSApplication sharedApplication] run];
after
[foo displayWindow];
Also, in order for an app to receive events from the window server correctly, you need to pack it into an app bundle. Compile it into a binary called foo, and create the following structure:
foo.app/
foo.app/Contents/
foo.app/Contents/MacOS/
foo.app/Contents/MacOS/foo <--- this is the executable
Then you can double-click foo.app from the Finder, or just call ./foo from the command line.
Related
I have an open source MS-DOS emulator for iOS here:
https://github.com/MattAndrzejczuk/MSDOS-for-iOS
This app runs well when using my older iOS devices such as the old iPhone 5S. but right after iOS 13.0 was released, the app delegate has trouble rendering SDL due to NOT calling UIKit methods on the main thread, but I'm not quite sure if this crash is due to OpenGL being completely deprecated, or, if maybe the newer SceneDelegate and AppDelegate changes which seemed to have been overhauled in iOS 13 are the cause of this issue. Just for some context, I've noticed that I cannot create a new Xcode project with a basic hello world label and build it directly for iOS 12 devices without doing this:
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
// ADD THIS TO DEFAULT APPDELEGATE.H:
#property (strong, nonatomic) UIWindow *window;
#end
This solution below, only somewhat fixed the problem:
Sources/dospad/Shared/DosEmuThread.m
#import "DosEmuThread.h"
extern int SDL_main(int argc, char *argv[]);
#implementation DosEmuThread
#synthesize started;
- (void)start
{
if (started) {
NSLog(#"DosEmuThread %p already started", self);
return;
}
NSLog(#"Start dosbox in new thread");
started = YES;
[NSThread detachNewThreadSelector:#selector(run) toTarget:self withObject:nil];
}
- (void) run {
#autoreleasepool {
/// UNCOMMENTING THIS SOMEWHAT FIXES THE ISSUE:
//dispatch_async(dispatch_get_main_queue(), ^{
char *argv[1] = {"dosbox"};
SDL_main(1, argv);
self.started = NO;
//});
}
}
#end
This fixes the app, in that I can reach the DOS prompt, but once I try to open something like this:
C:\ cd WAR2A
C:\ WAR2.EXE
I just get stuck at a blank screen, so obviously the quick fix of putting the dos thread on the main thread will break once SDL tries to run a full screen EXE app.
I'd really like to have the ability to emulate an x86 machine to run classic DOS games and even support Windows 95 for iOS, if there are alternative git repos out there that can do something similar, please let me know.
I am currently programming an application using glfw in MacOS X. My only problem is that the application doesn't use an AppDelegate, but does all the initialization in the main.cpp file like a command line utility. I specifically want to implement the function
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
and I already registered the file extensions in the plist file and when I try to open them I get an error message saying "The document "Doc.xxx" could not be opened. MyApp cannot open files in the "xxx" format.
I attempted to create a singleton that sets itself as the delegate but it just doesn't call the function. Can anyone comment on the possibility of creating a class and implementing the NSApplicationDelegate functions?
Basically there is nothing in your code to receive events from the OS and to pump the events received.
The solution, according to another stack overflow Q/A, appears to be to invoke NSApplication manually. You still need to decide where to hook your OpenGL render loop into as [NSApp run] doesn't return until the app finishes. Another solution could be to make a custom NSApplication replacement. http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
Copied pretty much verbatim from this answer taking into account the modifications for ARC: https://stackoverflow.com/a/2154666/1874323
#autoreleasepool {
AppDelegate * delegate = [[AppDelegate alloc] init];
NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate];
[NSApp run]; // Note this never returns until the app finishes, so you need to decide where to hook your code into
}
As always you can find documentation from the Apple website:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
We are building a content editor that brings up a "cocos Player" in an NSWindow for test purposes. The user can test some content and then close the window.
So I need to be able to shutdown cocos and re-start within the same app.
Everything is working if I use the CC_MAC_USE_DISPLAY_LINK_THREAD threading model. I had to make a fix in CCDirectorMac to get this working. In CCDirectorMac | stopAnimation I had to set the _runningThread to nil since it is not set to nil by the #if and #elif when using CC_MAC_USE_DISPLAY_LINK_THREAD.
Anyway so now I am able to "end" a director and then re-start it later with no issues.
My question though is this: If I am building an AppKit editor with occasional use of cocos2D whould my threading model really be CC_MAC_USE_MAIN_THREAD as is suggested in the documentation?
When I do use CC_MAC_USE_MAIN_THREAD I get a HANG in in stopAnimation on the line:
CVDisplayLinkStop(displayLink);
I think the main thread would be fine and would avoid threading issues for our tool. Performance is not a concern. I can't find any sample code that shuts down and restarts cocos2d in an NSWindow ... so my assumption here is that I am in untested waters (or little tested waters).
My steps to shutdown/restart are:
Call [[CCDirector sharedDirector] end]
This calls stopAnimation
Then re-initialize cocos2d the same way I did originally
Any advice on threading models for a Mac desktop app ... and why CVDisplayLinkStop hangs would be greatly appreciated.
Thanks in advance.
Ok, I figured it out after reading this post and its answers on the Apple mailing list: http://lists.apple.com/archives/quartz-dev/2006/Oct/msg00056.html
When using CC_MAC_USE_MAIN_THREAD, the display link thread uses performSelector:onThread:waitUntilDone: to run drawScene: on the main thread. It passes YES for the waitUntilDone: parameter, so the display link thread blocks until the main thread can process the drawScene: call.
Here's the relevant fragment of the cocos2d code. MyDisplayLinkCallback is called on the display link thread.
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
CVReturn result = [(CCDirectorDisplayLink*)displayLinkContext getFrameForTime:outputTime];
return result;
}
- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime
{
#if (CC_DIRECTOR_MAC_THREAD == CC_MAC_USE_DISPLAY_LINK_THREAD)
...
#else
// Display link thread blocks here:
[self performSelector:#selector(drawScene) onThread:_runningThread withObject:nil waitUntilDone:YES];
#endif
return kCVReturnSuccess;
}
The problem appears when the main thread tries to run CVDisplayLinkStop() which blocks until the display link callback in the display link thread finishes. Since the callback is at the same time waiting for the main thread to process its drawScene: call, both threads become deadlocked.
- (void) stopAnimation
{
...
if( displayLink ) {
// Main thread blocks here:
CVDisplayLinkStop(displayLink);
...
}
So, now for my workaround. I added a line to run the main thread's runloop in order to force the drawScene: call to be executed, which unblocks the display link thread. That way, when you call CVDisplayLinkStop() you're safe. Here's my addition (CCDirectorMac.m line 473 in cocos2d 2.1 release):
- (void) stopAnimation
{
...
if( displayLink ) {
#if (CC_DIRECTOR_MAC_THREAD != CC_MAC_USE_DISPLAY_LINK_THREAD)
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
#endif
CVDisplayLinkStop(displayLink);
...
}
I'm not sure this is the right thing to do, there's probably a better way to deal with this, but this workaround is good enough for me at the moment.
Thanks for this post, it helped me figure out how to solve the same deadlock, in a non-cocos2d app, by processing the display link callback on a separate thread, never on the main thread.
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime,
CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void *userInfo)
{
static dispatch_queue_t sDisplayQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sDisplayQueue = dispatch_queue_create("com.company.app.displayLink", NULL);
});
dispatch_sync(sDisplayQueue, ^{
<stuff>
});
return kCVReturnSuccess;
}
I have made a button so that when it's pressed by the user and a particular row(s) are selected it does something.
So far I have this:
if (pickerView selectedRowInComponent:0) {
[mailComposerTwo setToRecipients:[NSArray arrayWithObjects:#"email#blah.com",nil]];
}
It works on its own. But when I do the if statement multiple times it crashes.
An ways of making it work?
Any help appreciated, thanx.
The problem probably lies with your mail composer, not the picker view. When you show the composer, make sure that you only create it if it hasn't already created.
Also, make sure you release it after you show it:
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
...[configure the picker]
[rootContainer presentModalViewController:picker animated:YES];
[picker release];
NSArray *finalList = [[NSArray alloc]init];
//put all your if statements
if (pickerView selectedRowInComponent:0)
{
[finalList arrayByAddingObjectsFromArray:#[#"email#address.com",#"second#address.com",...];
}
if (pickerView selectedRowInComponent:1)
{
[finalList arrayByAddingObjectsFromArray:#[#"another#address.com",#"fourth#address.com",...];
}
//end of if statements
[mailComposerTwo setToRecipients:finalList];
[self presentViewController:yourInitializedMessageController animated:YES completion:^{NSLog(#"message controller is presented");}];
This will do a single method call rather than continually reassigning which for some odd reason is causing your exception. presentModalViewController:animated: has been deprecated as of iOS 6.0? if not 7.0 I believe.
NOTE! Make the message controller a property of the main view controller. It is good practice so that it is not auto-released by iOS if you need to bring it back up. However if you use MFMessageComposer iOS will keep messenger allocated or running in a thread somewhere so initializing a view controller for it is quick.
I'm invoking a NSOpenPanel from a thread created by boost C++.
the panel behaves erratically and doesn't respond well to mouse, that is clicking on objects does nothing sometime when clicking on top level combo box does improve response.
do i've to run a separate runloop I'm doing a runModalForDirectory which should take care of running its own loop.
I've also created a separate objc class which does performSelectorOnMainThread to show panel in main thread but still the behavior is same.
[ps performSelectorOnMainThread:#selector(showOpenPanel) withObject:nil
waitUntilDone:YES
modes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
I've also tried with waitUntilDone:NO and running a CFRunLoopRunInMode which isn't helping either.
- (bool) showOpenPanel
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setAllowsMultipleSelection:YES];
[op setTitle:#"Choose File"];
[op setMessage:#"Choose file for Importing."];
[op setFloatingPanel:true];
bool result =[op runModalForDirectory:NSHomeDirectory() file:nil types:self.fileTypes];
if (result == NSOKButton) {
[self setSelectedFiles:[op filenames]];
[self setLastShowResult:true];
}
else {
[self setLastShowResult:false];
}
[self setPanelIsDone:true];
return self.lastShowResult;
}
NSOpenPanel is part of AppKit. AppKit functions and classes can only be safely used on the main thread.
Show us the code you used with performSelectorOnMainThread so we can help figure out why you might still be seeing problems. I suspect you're calling individual methods with it--don't; it won't work the way you expect. Call back to the main thread for the totality of your interaction with NSOpenPanel.