OpenClonk
C4AppDelegate.mm
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 // Roughly adapted from the original C4AppDelegate.m; haxxed to death by teh Gurkendoktor.
17 // Look at main() to get an idea for what happens here.
18 
19 #include "C4Include.h"
20 #include "game/C4Application.h"
21 #include "game/C4Game.h"
22 
25 #ifdef USE_SDL_MAINLOOP
26 #import "SDL/SDL.h"
27 #endif
28 
29 /* The main class of the application, the application's delegate */
30 @implementation C4AppDelegate
31 
33 {
34  return (C4AppDelegate*)[[NSApplication sharedApplication] delegate];
35 }
36 
38 {
40 }
41 
42 #ifdef USE_COCOA
43 @synthesize addViewportForPlayerMenuItem, editorWindowController, kickPlayerMenuItem, recordMenuItem, netMenu, gameWindowController, toggleFullScreen;
44 #endif
45 
46 - (id) init
47 {
48  self = [super init];
49  if (self)
50  {
51  NSArray* processArguments = [[NSProcessInfo processInfo] arguments];
52  gatheredArguments = [NSMutableArray arrayWithCapacity:[processArguments count]+2];
53  [gatheredArguments addObjectsFromArray:processArguments];
54  }
55  return self;
56 }
57 
58 - (void)awakeFromNib
59 {
60  NSAppleEventManager* appleEvents = [NSAppleEventManager sharedAppleEventManager];
61  [appleEvents setEventHandler:self
62  andSelector:@selector(getUrl:withReplyEvent:)
63  forEventClass:kInternetEventClass andEventID:kAEGetURL];
64  [appleEvents setEventHandler:self
65  andSelector:@selector(handleQuitEvent:withReplyEvent:)
66  forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
67 }
68 
69 //handler for the quit apple event
70 - (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
71 {
72  NSLog(@"Quit message via Dock or Tab-Switcher");
73  [self suggestQuitting:self];
74 }
75 
76 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
77 {
78  NSString* pathExtension = [[filename pathExtension] lowercaseString];
79 
80  NSArray* clonkFileNameExtensions = @[@"ocd", @"ocs", @"ocf", @"ocg"];
81  if ([clonkFileNameExtensions containsObject:pathExtension])
82  {
83  // later decide whether to install or run
84  addonSupplied = filename;
85  if (running)
86  {
87  // if application is already running install immediately
88  [self installAddOn];
89  }
90  }
91  return YES;
92 }
93 
94 - (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
95 {
96  NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
97  [gatheredArguments addObject:url];
98 }
99 
100 - (void) quitAndMaybeRestart
101 {
102  // free app stuff
103  Application.Quit();
105  {
106  NSString* filename = [[NSBundle mainBundle] bundlePath];
107  NSString* cmd = [@"open " stringByAppendingString: filename];
108  system([cmd UTF8String]);
109  }
110 }
111 
112 // return the directory where Clonk.app lives
113 - (NSString*)clonkDirectory { return [NSBundle.mainBundle.bundlePath stringByDeletingLastPathComponent]; }
114 
115 - (void) applicationDidFinishLaunching: (NSNotification *) note
116 {
117  [[NSFileManager defaultManager] changeCurrentDirectoryPath:[self clonkDirectory]];
118  if (!([self argsLookLikeItShouldBeInstallation] && [self installAddOn]))
119  {
120  [NSApp activateIgnoringOtherApps:YES];
121 
122  [self makeFakeArgs];
123 
124 #ifdef USE_SDL_MAINLOOP
125  running = true;
126  SDL_main(newArgc, newArgv);
127  running = NO;
128  [self quitAndMaybeRestart];
129  [NSApp terminate:self];
130 #endif
131 
132 #ifdef USE_COCOA
133  // Init application
134  if (!Application.Init(args.size(), &args[0]))
135  {
136  Application.Clear();
137  [NSApp terminate:self];
138  }
139  [[NSRunLoop currentRunLoop] performSelector:@selector(delayedRun:) target:self argument:self order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
140  if (!lionAndBeyond())
141  [toggleFullScreen setTarget:self]; // revert to old pre-Lion fullscreen
142 #endif
143  }
144  else
145  {
146  [NSApp terminate:self];
147  }
148 }
149 
150 #ifdef USE_COCOA
151 - (void) delayedRun:(id)sender
152 {
154  running = YES;
155  //while (!Application.fQuitMsgReceived)
156  // Application.ScheduleProcs();
157  //[NSApp replyToApplicationShouldTerminate:YES];
158  //running = NO;
159  //[self quitAndMaybeRestart];
160  //[NSApp terminate:self];
161 }
162 #endif
163 
164 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)application
165 {
167  [self suggestQuitting:self];
168  return NSTerminateNow;
169 }
170 
171 - (void)terminate:(NSApplication*)sender
172 {
173 #ifdef USE_SDL_MAINLOOP
174  // Post an SDL_QUIT event
175  SDL_Event event;
176  event.type = SDL_QUIT;
177  SDL_PushEvent(&event);
178 #endif
179 #ifdef USE_COCOA
181 #endif
182 }
183 
184 // arguments that should be converted to a c char* array and then passed on to SDL_main
185 - (NSMutableArray*)gatheredArguments
186 {
187  return gatheredArguments;
188 }
189 
190 // Look for -psn argument which generally is a clue that the application should open a file (double-clicking, calling /usr/bin/open and such)
192 {
193  // not having this check leads to deletion of Clonk folder -.-
194  if (!addonSupplied)
195  return NO;
196  for (unsigned int i = 0; i < [gatheredArguments count]; i++)
197  {
198  NSString* arg = [gatheredArguments objectAtIndex:i];
199  if ([arg hasPrefix:@"-psn"])
200  return YES;
201  }
202  return NO;
203 }
204 
205 - (void) infoWithFormat:(NSString*) formatString andArgument:(const char*) arg
206 {
207  NSRunInformationalAlertPanel([NSString stringWithCString:LoadResStr("IDS_ADDON_INSTALLTITLE") encoding:NSUTF8StringEncoding],
208  [NSString stringWithFormat: formatString, arg],
209  @"OK", nil, nil);
210 }
211 
212 - (void) minimalConfigurationInitialization
213 {
214  Config.Init();
215  Config.Load();
216  Reloc.Init();
217  Languages.Init();
219 }
220 
221 // Copies the add-on to the clonk directory
222 - (BOOL) installAddOn
223 {
224  if (!addonSupplied)
225  return NO;
226 
227  // load configuration + localization so LoadResStr can be used
228  [self minimalConfigurationInitialization];
229 
230  // Build destination path.
231  NSString* justFileName = [addonSupplied lastPathComponent];
232  NSString* destPath = [self clonkDirectory];
233  NSString* formatString;
234 
235  // Already installed?
236  for (C4Reloc::iterator it = Reloc.begin(); it != Reloc.end(); ++it)
237  {
238  if ([addonSupplied hasPrefix:[NSString stringWithCString:(*it).strBuf.getData() encoding:NSUTF8StringEncoding]])
239  {
240  [gatheredArguments addObject:addonSupplied];
241  return NO; // run scenarios when they are already containd in one of the Reloc directories
242  }
243  else if (it->pathType == C4Reloc::PATH_PreferredInstallationLocation)
244  destPath = [NSString stringWithCString:it->strBuf.getData() encoding:NSUTF8StringEncoding];
245  }
246  destPath = [destPath stringByAppendingPathComponent:justFileName];
247 
248  NSFileManager* fileManager = [NSFileManager defaultManager];
249  if ([fileManager fileExistsAtPath:destPath])
250  // better to throw it into the trash. everything else seems so dangerously destructive
251  [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[self clonkDirectory] destination:@"" files:[NSArray arrayWithObject:justFileName] tag:0];
252  if ([fileManager copyItemAtPath:addonSupplied toPath:destPath error:NULL])
253  {
254  formatString = [NSString stringWithCString:LoadResStr("IDS_ADDON_INSTALLSUCCESS") encoding:NSUTF8StringEncoding];
255  }
256  else
257  {
258  formatString = [NSString stringWithCString:LoadResStr("IDS_ADDON_INSTALLFAILURE") encoding:NSUTF8StringEncoding];
259  }
260  [self infoWithFormat:formatString andArgument:[justFileName cStringUsingEncoding:NSUTF8StringEncoding]];
261  return YES; // only return NO when the scenario should be run rather than installed
262 }
263 
264 // convert gatheredArguments to c array
265 - (void)makeFakeArgs
266 {
267  NSArray* nonCocoaArgs = [gatheredArguments filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString* arg, NSDictionary *bindings)
268  {
269  return
270  !(
271  [arg hasPrefix:@"-NS"] ||
272  [arg isEqualToString:@"YES"]
273  );
274  }
275  ]];
276  int argCount = [nonCocoaArgs count];
277  args.resize(argCount);
278  for (int i = 0; i < argCount; i++)
279  {
280  args[i] = strdup([[nonCocoaArgs objectAtIndex:i] cStringUsingEncoding:NSUTF8StringEncoding]);
281  }
282 }
283 
284 - (void) applicationDidBecomeActive:(NSNotification*)notification
285 {
286 #ifdef USE_COCOA
287  if (gameWindowController)
288  [gameWindowController.window makeKeyAndOrderFront:self];
289 #endif
290 }
291 
292 @end
293 
294 #ifdef main
295 # undef main
296 #endif
297 
298 static void _ExceptionHandler(NSException* exception)
299 {
300  NSAlert* alert = [NSAlert alertWithMessageText:[exception description] defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@""];
301  [alert runModal];
302 }
303 
304 /* Main entry point to executable - should *not* be SDL_main! */
305 int main (int argc, const char **argv)
306 {
307  NSSetUncaughtExceptionHandler(&_ExceptionHandler);
308  return NSApplicationMain(argc, argv);
309 }
int main(int argc, const char **argv)
C4Config Config
Definition: C4Config.cpp:930
C4Game Game
Definition: C4Globals.cpp:52
C4Application Application
Definition: C4Globals.cpp:44
C4Language Languages
Definition: C4Language.cpp:42
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
C4Reloc Reloc
Definition: C4Reloc.cpp:21
bool lionAndBeyond()
bool fQuitMsgReceived
Definition: C4App.h:81
bool Init(int argc, char *argv[])
Definition: C4AppSDL.cpp:87
void Clear() override
void Quit() override
char LanguageEx[CFG_MaxString+1]
Definition: C4Config.h:38
C4ConfigGeneral General
Definition: C4Config.h:255
bool Init()
Definition: C4Config.cpp:801
bool Load(const char *config_file=nullptr)
Definition: C4Config.cpp:327
bool IsRunning
Definition: C4Game.h:140
bool Init()
Definition: C4Language.cpp:55
bool LoadLanguage(const char *strLanguages)
Definition: C4Language.cpp:361
@ PATH_PreferredInstallationLocation
Definition: C4Reloc.h:28
iterator end() const
Definition: C4Reloc.cpp:149
void Init()
Definition: C4Reloc.cpp:23
iterator begin() const
Definition: C4Reloc.cpp:142
void StartOnCurrentThread()
BOOL isEditorAndGameRunning()
BOOL argsLookLikeItShouldBeInstallation()
NSMutableArray * gatheredArguments
Definition: C4AppDelegate.h:33
std::vector< char * > args
Definition: C4AppDelegate.h:40
NSString * addonSupplied
Definition: C4AppDelegate.h:34
C4AppDelegate * instance()