Application.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. #if MAC
  4. #include "../Platforms/Mac/MyApplication.h"
  5. #elif IOS
  6. #include "../Platforms/iOS/iOS.h"
  7. #endif
  8. namespace EE{
  9. /******************************************************************************/
  10. Bool LogInit=false;
  11. ASSERT(MEMBER_SIZE(ImageTypeInfo, format )==SIZE(UInt));
  12. ASSERT(MEMBER_SIZE(VtxIndBuf , _prim_type)==SIZE(UInt));
  13. #if WINDOWS_OLD
  14. #define SM_DIGITIZER 94
  15. #define SM_MAXIMUMTOUCHES 95
  16. ASSERT(SIZE(D3DVECTOR )==SIZE(Vec )); // shaders
  17. //ASSERT(SIZE(D3DXVECTOR4)==SIZE(Vec4 )); // shaders
  18. //ASSERT(SIZE(D3DXMATRIX )==SIZE(Matrix4)); // shaders
  19. static Bool ShutCOM=false;
  20. #elif LINUX
  21. static Atom _NET_WM_ICON;
  22. static int (*OldErrorHandler)(::Display *d, XErrorEvent *e);
  23. static int ErrorHandler (::Display *d, XErrorEvent *e)
  24. {
  25. if(e->error_code==BadWindow)return 0;
  26. return OldErrorHandler ? OldErrorHandler(d, e) : 0;
  27. }
  28. #endif
  29. /******************************************************************************/
  30. Application App;
  31. /******************************************************************************/
  32. Application::Application()
  33. {
  34. #if 0 // there's only one 'Application' global 'App' and it doesn't need clearing members to zero
  35. flag=0;
  36. active_wait=0;
  37. background_wait=0;
  38. cipher=null;
  39. receive_data=null;
  40. save_state=null;
  41. paused=null;
  42. resumed=null;
  43. drop=null;
  44. quit=null;
  45. exit=null;
  46. low_memory=null;
  47. notification=null;
  48. _active=_initialized=_minimized=_maximized=_close=_closed=_del_self_at_exit=_elevated=false;
  49. #if WINDOWS_NEW
  50. _loop=false;
  51. #endif
  52. _stay_awake=AWAKE_OFF;
  53. //_mem_leaks=0; don't set this as it could have been already modified
  54. _process_id=_parent_process_id=0;
  55. _hwnd=null;
  56. _window_pos=_window_size=_window_resized=_desktop_size.zero();
  57. _desktop_area=_bound=_bound_maximized.zero();
  58. #if WINDOWS_OLD
  59. _style_window=_style_window_maximized=_style_full=0;
  60. _hinstance=null;
  61. #endif
  62. #if WINDOWS
  63. _icon=null;
  64. #endif
  65. #endif
  66. x =-1;
  67. y = 1;
  68. _lang = EN;
  69. _orientation= DIR_UP;
  70. _thread_id = GetThreadId();
  71. _back_text = "Running in background";
  72. }
  73. Application& Application::name(C Str &name) {T._name=name; return T;}
  74. /******************************************************************************/
  75. Memx<Notification> Notifications;
  76. #if ANDROID
  77. static void RemoveNotification(Int id)
  78. {
  79. JNI jni;
  80. if(jni && ActivityClass)
  81. if(JMethodID removeNotification=jni->GetStaticMethodID(ActivityClass, "removeNotification", "(I)V"))
  82. jni->CallStaticVoidMethod(ActivityClass, removeNotification, jint(id));
  83. }
  84. static void SetNotification(Int id, C Str &title, C Str &text, Bool dismissable)
  85. {
  86. JNI jni;
  87. if(jni && ActivityClass)
  88. if(JMethodID setNotification=jni->GetStaticMethodID(ActivityClass, "setNotification", "(ILjava/lang/String;Ljava/lang/String;Z)V"))
  89. if(JString j_title=JString(jni, title))
  90. if(JString j_text =JString(jni, text ))
  91. jni->CallStaticVoidMethod(ActivityClass, setNotification, jint(id), j_title(), j_text(), jboolean(dismissable));
  92. }
  93. extern "C" JNIEXPORT void JNICALL Java_com_esenthel_Native_notification(JNIEnv *env, jclass clazz, jint id, jboolean selected)
  94. {
  95. if(Notifications.absToValidIndex(id)>=0) // if present
  96. {
  97. Notification &notification=Notifications.absElm(id);
  98. if(!selected)notification._visible=false; // if was dismissed then set as hidden (not visible), so calling 'hide' won't do anything and calling 'set' could restore it
  99. if(auto call=App.notification)call(notification, selected);else Notifications.removeAbs(id, true); // if there's no callback then remove the notification
  100. }else
  101. if(selected)RemoveNotification(id); // if not found and selected (not dismissed), then remove manually
  102. }
  103. #endif
  104. Notification::~Notification()
  105. {
  106. hide();
  107. }
  108. Notification::Notification()
  109. {
  110. user=null;
  111. _dismissable=_visible=false;
  112. }
  113. void Notification::hide()
  114. {
  115. if(_visible)
  116. {
  117. #if ANDROID
  118. Int abs=Notifications.absIndex(this);
  119. if( abs>=0)RemoveNotification(abs);
  120. #endif
  121. _visible=false;
  122. }
  123. }
  124. void Notification::remove()
  125. {
  126. Notifications.removeData(this, true);
  127. // !! do not do anything here because this notification is no longer valid !!
  128. }
  129. void Notification::set(C Str &title, C Str &text, Bool dismissable)
  130. {
  131. if(!Equal(_title, title, true)
  132. || !Equal(_text , text , true)
  133. || _dismissable!=dismissable
  134. || !_visible)
  135. {
  136. T._title =title;
  137. T._text =text;
  138. T._dismissable=dismissable;
  139. #if ANDROID
  140. Int valid=Notifications.validIndex(this); if(valid>=0) // if present
  141. {
  142. Int abs=Notifications.validToAbsIndex(valid); if(abs>=0)
  143. {
  144. SetNotification(abs, title, text, dismissable);
  145. T._visible=true;
  146. }
  147. }
  148. #endif
  149. }
  150. }
  151. void HideNotifications()
  152. {
  153. #if ANDROID // on Android, force closing app does not hide notifications, so if there's a non-dismissable notification, it will get stuck
  154. //REPA(Notifications)if(!Notifications[i].dismissable())Notifications.removeValid(i, true);
  155. Notifications.clear(); // actually hide all notifications, so if a notification is shown, and app force closed, then the notification has ID belonging to a Notification that no longer exists (because force close removed it), so it can't be found in the 'Notifications' list anymore
  156. #endif
  157. }
  158. Application& Application::backgroundText(C Str &text)
  159. {
  160. if(!Equal(T._back_text, text, true))
  161. {
  162. T._back_text=text;
  163. #if ANDROID
  164. if(!App.active())SetNotification(-1, App.name(), text, false); // adjust existing, -1=BACKGROUND_NOTIFICATION_ID from Java
  165. #endif
  166. }
  167. return T;
  168. }
  169. /******************************************************************************/
  170. #if WINDOWS || ANDROID
  171. Bool Application::minimized()C {return _minimized;}
  172. Bool Application::maximized()C {return _maximized;}
  173. #elif LINUX
  174. Bool Application::minimized()C {return WindowMinimized(hwnd());}
  175. Bool Application::maximized()C {return _maximized;} // '_maximized' is obtained in 'ConfigureNotify' system message
  176. #elif MAC
  177. Bool Application::minimized()C {return _minimized;} // '_minimized' is obtained in 'windowDidMiniaturize, windowDidDeminiaturize'
  178. Bool Application::maximized()C {return WindowMaximized(hwnd());}
  179. #elif IOS
  180. Bool Application::minimized()C {return false;}
  181. Bool Application::maximized()C {return true ;}
  182. #elif WEB
  183. Bool Application::minimized()C {return false;}
  184. Bool Application::maximized()C {return _maximized;}
  185. #endif
  186. Bool Application::mainThread()C {return GetThreadId()==_thread_id;}
  187. UInt Application::parentProcessID()C
  188. {
  189. #if WINDOWS_OLD
  190. if(!_parent_process_id)
  191. {
  192. HANDLE snap =CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  193. if( snap!=INVALID_HANDLE_VALUE)
  194. {
  195. PROCESSENTRY32 proc; proc.dwSize=SIZE(PROCESSENTRY32);
  196. if(Process32First(snap, &proc))do
  197. {
  198. if(proc.th32ProcessID==processID()){_parent_process_id=proc.th32ParentProcessID; break;}
  199. }while(Process32Next(snap, &proc));
  200. CloseHandle(snap);
  201. }
  202. }
  203. #endif
  204. return _parent_process_id;
  205. }
  206. DIR_ENUM Application::orientation()C
  207. {
  208. #if IOS
  209. //switch([[UIDevice currentDevice] orientation]) this is faulty (if the app starts rotated, then this has wrong value that doesn't get updated until device is rotated)
  210. switch([UIApplication sharedApplication].statusBarOrientation)
  211. {
  212. default : return DIR_UP;
  213. case UIInterfaceOrientationPortraitUpsideDown: return DIR_DOWN;
  214. case UIInterfaceOrientationLandscapeLeft : return DIR_RIGHT;
  215. case UIInterfaceOrientationLandscapeRight : return DIR_LEFT;
  216. }
  217. #else
  218. return _orientation;
  219. #endif
  220. }
  221. /******************************************************************************/
  222. Application& Application::icon(C Image &icon)
  223. {
  224. #if WINDOWS_OLD
  225. HICON hicon=CreateIcon(icon);
  226. if(hwnd())
  227. {
  228. SendMessage(Hwnd(), WM_SETICON, ICON_BIG , (LPARAM)hicon);
  229. SendMessage(Hwnd(), WM_SETICON, ICON_SMALL, (LPARAM)hicon);
  230. }
  231. if(_icon)DestroyIcon(_icon); _icon=hicon;
  232. #elif LINUX
  233. if(XDisplay && hwnd() && _NET_WM_ICON)
  234. {
  235. Image temp; C Image *src=(icon.is() ? &icon : null);
  236. if(src && src->compressed())if(src->copyTry(temp, -1, -1, 1, IMAGE_B8G8R8A8, IMAGE_SOFT, 1))src=&temp;else src=null;
  237. if(src && src->is() && src->lockRead())
  238. {
  239. Memt<long> data; data.setNum(2+src->w()*src->h());
  240. data[0]=src->w();
  241. data[1]=src->h();
  242. FREPD(y, src->h())
  243. FREPD(x, src->w())
  244. {
  245. Color col=src->color(x, y);
  246. VecB4 c(col.b, col.g, col.r, col.a);
  247. data[2+x+y*src->w()]=c.u;
  248. }
  249. XChangeProperty(XDisplay, Hwnd(), _NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)data.data(), data.elms());
  250. src->unlock();
  251. }else
  252. {
  253. XDeleteProperty(XDisplay, Hwnd(), _NET_WM_ICON);
  254. }
  255. XFlush(XDisplay);
  256. _icon.del(); // delete at end in case it's 'icon'
  257. }else
  258. {
  259. // remember it so it will be set later
  260. icon.copyTry(_icon, -1, -1, 1, IMAGE_B8G8R8A8, IMAGE_SOFT, 1);
  261. }
  262. #endif
  263. return T;
  264. }
  265. /******************************************************************************/
  266. Application& Application::lang(LANG_TYPE lang)
  267. {
  268. if(T._lang!=lang)
  269. {
  270. T._lang=lang;
  271. Gui.setText();
  272. }
  273. return T;
  274. }
  275. /******************************************************************************/
  276. #if MAC
  277. static Bool AssertionIDValid=false;
  278. static IOPMAssertionID AssertionID;
  279. #endif
  280. Application& Application::stayAwake(AWAKE_MODE mode)
  281. {
  282. if(_stay_awake!=mode)
  283. {
  284. _stay_awake=mode;
  285. #if DESKTOP
  286. if(mode==AWAKE_SCREEN) // if we want to keep the screen on
  287. if(!(active() || FlagTest(App.flag, APP_WORK_IN_BACKGROUND))) // however the app is not focused
  288. mode=AWAKE_OFF; // then disable staying awake
  289. #endif
  290. #if WINDOWS_OLD
  291. SetThreadExecutionState(ES_CONTINUOUS|((mode==AWAKE_OFF) ? 0 : (mode==AWAKE_SCREEN) ? ES_DISPLAY_REQUIRED : ES_SYSTEM_REQUIRED));
  292. #elif WINDOWS_NEW
  293. static Windows::System::Display::DisplayRequest DR; // can't be set as a global var, because crash will happen at its constructor due to system not initialized yet
  294. if(mode==AWAKE_OFF)DR.RequestRelease();else DR.RequestActive();
  295. #elif MAC
  296. if(AssertionIDValid){IOPMAssertionRelease(AssertionID); AssertionIDValid=false;} // release current
  297. if(mode && IOPMAssertionCreateWithName((mode==AWAKE_SCREEN) ? kIOPMAssertionTypeNoDisplaySleep : kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR("Busy"), &AssertionID)==kIOReturnSuccess)AssertionIDValid=true;
  298. #elif ANDROID
  299. if(mode==AWAKE_SYSTEM) // if we want to keep the system on
  300. if(!(active() || FlagTest(App.flag, APP_WORK_IN_BACKGROUND))) // however the app is not focused
  301. mode=AWAKE_OFF; // then disable staying awake
  302. JNI jni;
  303. if(jni && ActivityClass)
  304. if(JMethodID stayAwake=jni->GetStaticMethodID(ActivityClass, "stayAwake", "(I)V"))
  305. jni->CallStaticVoidMethod(ActivityClass, stayAwake, jint(mode));
  306. #elif IOS
  307. [UIApplication sharedApplication].idleTimerDisabled=(mode!=AWAKE_OFF);
  308. #elif LINUX
  309. // TODO: add 'stayAwake' support for Linux
  310. #endif
  311. }
  312. return T;
  313. }
  314. /******************************************************************************/
  315. void Application::setActive(Bool active)
  316. {
  317. if(T.active()!=active)
  318. {
  319. T._active=active;
  320. #if WINDOWS_OLD
  321. if(D.full()) // full screen
  322. {
  323. #if DX11
  324. if(!SwapChainDesc.Windowed && SwapChain) // DX10+ true full screen
  325. {
  326. SyncLocker locker(D._lock);
  327. if(active)
  328. {
  329. SwapChain->SetFullscreenState(true, null);
  330. SwapChain->ResizeTarget(&SwapChainDesc.BufferDesc);
  331. }else
  332. {
  333. WindowMinimize(true);
  334. SwapChain->SetFullscreenState(false, null);
  335. }
  336. }
  337. #endif
  338. // OpenGL || DX non-exclusive
  339. #if DX9 || DX11
  340. if(!D.exclusive())
  341. #endif
  342. {
  343. if(active){SetDisplayMode( ); WindowReset (true );}
  344. else {WindowMinimize(true); SetDisplayMode( );}
  345. }
  346. }
  347. #elif MAC
  348. if(D.full() && !active)WindowHide();
  349. SetDisplayMode();
  350. #elif LINUX
  351. if(D.full() && !active)WindowMinimize();
  352. SetDisplayMode();
  353. #endif
  354. Time .skipUpdate();
  355. InputDevices.acquire (active);
  356. if(_initialized)
  357. {
  358. if(active){if(T.resumed)T.resumed();}
  359. else {if(T. paused)T. paused();}
  360. }
  361. #if DESKTOP || ANDROID // also on Android in case a new Activity/Window was created, call this after potential 'paused/resumed' in case user modifies APP_WORK_IN_BACKGROUND which affect 'stayAwake'
  362. if(_stay_awake){AWAKE_MODE temp=_stay_awake; _stay_awake=AWAKE_OFF; stayAwake(temp);} // reset sleeping when app gets de/activated
  363. #endif
  364. _initialized=true;
  365. #if IOS
  366. if(EAGLView *view=GetUIView())[view setUpdate];
  367. #endif
  368. }
  369. }
  370. void Application::close()
  371. {
  372. if(quit)quit();else _close=true;
  373. }
  374. /******************************************************************************/
  375. Bool Application::testInstance()
  376. {
  377. if(flag&APP_EXIT_IMMEDIATELY)return false;
  378. if(flag&(APP_ON_RUN_EXIT|APP_ON_RUN_WAIT))
  379. {
  380. Memt<UInt> id; ProcList(id);
  381. REPA(id)if(processID()!=id[i] && StartsPath(ProcName(id[i]), exe())) // use 'StartsPath' because on Mac 'ProcName' returns the file inside APP folder
  382. {
  383. if(flag&APP_ON_RUN_WAIT)ProcWait(id[i]);else
  384. {
  385. if(Ptr hwnd=ProcWindow(id[i]))WindowActivate(hwnd);
  386. return false;
  387. }
  388. }
  389. }
  390. return true;
  391. }
  392. void Application::deleteSelf()
  393. {
  394. if(_del_self_at_exit)
  395. {
  396. #if WINDOWS_OLD
  397. Str base=_GetBase(exe());
  398. #if 0 // this won't work for various reasons, possibly 'createMem' does not support labels and goto (because each command is executed separately), and possibly because 'ConsoleProcess' is a child process which will get closed if the parent (the executable) gets closed, however the child can only delete the file once the parent process is no longer running
  399. ConsoleProcess cp;
  400. cp.createMem(S+":Repeat\n"
  401. +"del \"" +base+"\" >> NUL\n" // delete EXE
  402. +"if exist \""+base+"\" goto Repeat", // if exists then try again
  403. GetPath(exe()));
  404. #else
  405. if(HasUnicode(base))
  406. {
  407. if(!App.renameSelf(GetPath(exe())+'\\'+FFirst("temp ", "tmp")))return;
  408. base=_GetBase(exe());
  409. }
  410. Str bat=S+exe()+".bat";
  411. FileText f; if(f.write(bat, ANSI)) // BAT doesn't support wide chars
  412. {
  413. f.putText(S+":Repeat\n"
  414. +"del \"" + base+"\" >> NUL\n" // delete EXE
  415. +"if exist \""+ base+"\" goto Repeat\n"
  416. +"del \"" +GetBase(bat)+"\" >> NUL"); // delete BAT
  417. f.del();
  418. Run(bat, S, true);
  419. }
  420. #endif
  421. #elif MAC
  422. FDelDirs(exe()); // under Mac OS application is not an "exe" file, but a folder with "app" extension
  423. #elif LINUX
  424. FDelFile(exe());
  425. #endif
  426. }
  427. }
  428. Bool Application::renameSelf(C Str &dest)
  429. {
  430. if(FRename(_exe, dest))
  431. {
  432. _exe=dest;
  433. return true;
  434. }
  435. return false;
  436. }
  437. void Application::deleteSelfAtExit()
  438. {
  439. _del_self_at_exit=true;
  440. }
  441. void Application::detectMemLeaks()
  442. {
  443. if((flag&APP_MEM_LEAKS) && _mem_leaks)
  444. {
  445. #if WINDOWS
  446. _cexit();
  447. if(Int m=Abs(_mem_leaks))
  448. {
  449. ListMemLeaks();
  450. showError(S+m+" Memory Leak(s)");
  451. }
  452. _exit(-1); // manual exit after cleaning with '_cexit'
  453. #elif 0 && MAC
  454. LogN("Application Memory Leaks Remaining:");
  455. #endif
  456. }
  457. }
  458. #if WINDOWS_NEW
  459. ref struct Exiter sealed
  460. {
  461. Exiter(Platform::String ^title, Platform::String ^error)
  462. {
  463. if(auto dialog=ref new Windows::UI::Popups::MessageDialog(error, title)) // this does not require 'FixNewLine'
  464. {
  465. App._closed=true; // disable callback processing
  466. dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("OK", ref new Windows::UI::Popups::UICommandInvokedHandler(this, &Exiter::onOK)));
  467. dialog->ShowAsync();
  468. //try // this can crash if app didn't finish initializing, however using try/catch didn't help
  469. {
  470. Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->ProcessEvents(Windows::UI::Core::CoreProcessEventsOption::ProcessUntilQuit);
  471. }
  472. //catch(...){ExitNow();}
  473. }
  474. }
  475. void onOK(Windows::UI::Popups::IUICommand^ command)
  476. {
  477. Windows::ApplicationModel::Core::CoreApplication::Exit();
  478. }
  479. };
  480. #elif ANDROID
  481. extern "C" JNIEXPORT void JNICALL Java_com_esenthel_Native_closedError(JNIEnv *env, jclass clazz) {ExitNow();}
  482. #endif
  483. void Application::showError(CChar *error)
  484. {
  485. if(Is(error))
  486. {
  487. if(D.full() && App.hwnd())
  488. {
  489. WindowHide();
  490. #if DX11 // hiding window on DX10+ is not enough, try disabling Fullscreen
  491. //ChangeDisplaySettings(null, 0); this didn't help
  492. if(SwapChain
  493. #if WINDOWS_OLD
  494. && !SwapChainDesc.Windowed // if true fullscreen
  495. #endif
  496. )
  497. {
  498. if(App.threadID()==GetThreadId()) // we can make call to 'SetFullscreenState' only on the main thread, calling it on others made no difference
  499. {
  500. SyncLocker locker(D._lock); // we should always be able to lock on the main thread
  501. if(SwapChain)SwapChain->SetFullscreenState(false, null);
  502. }else App._close=true; // request the main thread to close the app (but don't call 'App.close' because that would call quit)
  503. }
  504. #endif
  505. }
  506. CChar *title=MLTC(u"Error", PL,u"Błąd", RU,u"Ошибка", PO,u"Erro", CN,u"错误");
  507. #if WINDOWS
  508. OutputDebugString(WChar(error)); OutputDebugStringA("\n"); // first write to console
  509. #endif
  510. #if WINDOWS_OLD
  511. //SetCursor(LoadCursor(null, IDC_ARROW)); // reset cursor first, in case app had it disabled, actually this didn't help
  512. ClipCursor(null); // disable cursor clipping first
  513. MessageBox(null, WChar(error), WChar(title), MB_OK|MB_TOPMOST|MB_ICONERROR); // use OS 'MessageBox' instead of EE 'WindowMsgBox' to avoid memory allocation when creating Str objects, because this may be called when out of memory
  514. #elif WINDOWS_NEW
  515. Exiter(ref new Platform::String(WChar(title)), ref new Platform::String(WChar(error)));
  516. #elif LINUX // on Linux additionally display the error in the console in case the OS doesn't support displaying messages boxes
  517. fputs(UTF8(error), stdout); fputs("\n", stdout); fflush(stdout); // without the flush, messages won't be displayed immediately
  518. WindowMsgBox(title, error, true);
  519. #elif ANDROID
  520. // first write to console, use '__android_log_write' with 'ANDROID_LOG_ERROR' instead of 'Log' which uses 'ANDROID_LOG_INFO'
  521. Memc<Str> lines; Split(lines, error, '\n'); // android has limit for too long messages
  522. FREPA(lines){Str8 line=UTF8(lines[i]); if(line.is())__android_log_write(ANDROID_LOG_ERROR, "Esenthel", line.is() ? line : " ");} // '__android_log_write' will crash if text is null or ""
  523. #if 0 // output the error into root of SD Card so users can send the text file
  524. Str path=SystemPath(SP_SD_CARD); if(!path.is())path=SystemPath(SP_APP_DATA_PUBLIC); if(!path.is())path=SystemPath(SP_APP_DATA);
  525. FileText f; if(f.append(path.tailSlash(true)+"Esenthel Error.txt"))f.putText(error);
  526. #endif
  527. if(ActivityClass)
  528. {
  529. if(!minimized()) // if app is not minimized then we can show message box, this works OK even when app is starting (InitPre)
  530. {
  531. JNI jni;
  532. if(jni && ActivityClass)
  533. if(JMethodID messageBox=jni->GetStaticMethodID(ActivityClass, "messageBox", "(Ljava/lang/String;Ljava/lang/String;Z)V"))
  534. if(JString ti=JString(jni, title))
  535. if(JString te=JString(jni, error))
  536. jni->CallStaticVoidMethod(ActivityClass, messageBox, ti(), te(), jboolean(true));
  537. for(; !AndroidApp->destroyRequested && ALooper_pollAll(-1, null, null, null)>=0; )Time.wait(1); // since the message box is only queued, we need to wait until it's actually displayed, need to check for 'destroyRequested' as well, in case the system decided to close the app before 'closedError' got called
  538. }else // can't display a message box if app is minimized, so display a toast instead
  539. {
  540. Str message=S+App.name()+" exited"; if(Is(error))message.line()+=error;
  541. JNI jni;
  542. if(jni && ActivityClass)
  543. if(JMethodID toast=jni->GetStaticMethodID(ActivityClass, "toast", "(Ljava/lang/String;)V"))
  544. if(JString text=JString(jni, message))
  545. {
  546. jni->CallStaticVoidMethod(ActivityClass, toast, text());
  547. Time.wait(4000); // wait 4 seconds because toast will disappear as soon as we crash
  548. }
  549. }
  550. }
  551. #elif IOS
  552. if(NSStringAuto str=error)NSLog(@"%@", str()); // first write to console
  553. // display message box
  554. if(NSString *ns_title=AppleString(title)) // have to use 'AppleString' because it will get copied in the local function below
  555. {
  556. if(NSString *ns_text=AppleString(error)) // have to use 'AppleString' because it will get copied in the local function below
  557. {
  558. dispatch_async(dispatch_get_main_queue(), ^{ // this is needed in case we're calling from a secondary thread
  559. if(UIAlertController *alert_controller=[UIAlertController alertControllerWithTitle:ns_title message:ns_text preferredStyle:UIAlertControllerStyleAlert])
  560. {
  561. [alert_controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {ExitNow();} ]];
  562. [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alert_controller animated:YES completion:nil];
  563. //[alert_controller release]; release will crash
  564. }
  565. });
  566. [ns_text release];
  567. }
  568. [ns_title release];
  569. }
  570. _closed=true; if(EAGLView *view=GetUIView())[view setUpdate]; // disable callback processing and stop updating
  571. [[NSRunLoop mainRunLoop] run];
  572. #elif WEB // on Web display the error as both console output and message box
  573. fputs(UTF8(error), stdout); // first write to console
  574. WindowMsgBox(title, error, true);
  575. #else
  576. WindowMsgBox(title, error, true);
  577. #endif
  578. }
  579. }
  580. void Application::lowMemory()
  581. {
  582. // call this first before releasing caches
  583. if(low_memory)low_memory();
  584. // release memory from caches (process in order from parents to base elements)
  585. DelayRemoveNow();
  586. Environments.delayRemoveNow();
  587. Objects .delayRemoveNow();
  588. Meshes .delayRemoveNow();
  589. PhysBodies .delayRemoveNow();
  590. WaterMtrls .delayRemoveNow();
  591. Materials .delayRemoveNow();
  592. GuiSkins .delayRemoveNow();
  593. Panels .delayRemoveNow();
  594. PanelImages .delayRemoveNow();
  595. TextStyles .delayRemoveNow();
  596. Fonts .delayRemoveNow();
  597. ImageAtlases.delayRemoveNow();
  598. Images .delayRemoveNow();
  599. }
  600. /******************************************************************************/
  601. static RectI GetDesktopArea()
  602. {
  603. RectI recti(0, 0, App.desktopW(), App.desktopH());
  604. #if WINDOWS_OLD
  605. RECT rect; SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); recti.set(rect.left, rect.top, rect.right, rect.bottom);
  606. #elif LINUX
  607. if(XDisplay)
  608. if(Atom FIND_ATOM(_NET_WORKAREA))
  609. {
  610. Atom type =NULL;
  611. int format=0;
  612. unsigned long items =0, bytes_after=0;
  613. unsigned char *data =null;
  614. if(!XGetWindowProperty(XDisplay, DefaultRootWindow(XDisplay), _NET_WORKAREA, 0, 16, 0, XA_CARDINAL, &type, &format, &items, &bytes_after, &data))
  615. if(long *l=(long*)data)if(items>=4)
  616. {
  617. long left =l[0],
  618. top =l[1],
  619. width =l[2],
  620. height=l[3];
  621. recti.set(left, top, left+width, top+height);
  622. }
  623. if(data)XFree(data);
  624. }
  625. #elif MAC
  626. NSRect rect=[[NSScreen mainScreen] visibleFrame];
  627. recti.min.x= Round(rect.origin.x); recti.max.x=recti.min.x+Round(rect.size.width );
  628. recti.max.y=App.desktopH()-Round(rect.origin.y); recti.min.y=recti.max.y-Round(rect.size.height);
  629. #elif WEB
  630. // it's not possible to get correct results, because on Chrome: this value is adjusted by "System DPI/Scaling", but not 'D.browserZoom', and does not change when zooming. Because "System DPI/Scaling" is unknown, it can't be calculated.
  631. recti.min.set(JavaScriptRunI("screen.availLeft" ), JavaScriptRunI("screen.availTop" ));
  632. recti.max.set(JavaScriptRunI("screen.availWidth"), JavaScriptRunI("screen.availHeight"))+=recti.min;
  633. #endif
  634. return recti;
  635. }
  636. static Str GetAppPathName()
  637. {
  638. #if WINDOWS
  639. wchar_t module[MAX_LONG_PATH]; GetModuleFileName(null, module, Elms(module)); return module;
  640. #elif APPLE
  641. Str app;
  642. if(CFBundleRef bundle=CFBundleGetMainBundle())
  643. {
  644. if(CFURLRef url=CFBundleCopyBundleURL(bundle))
  645. {
  646. Char8 url_path[MAX_UTF_PATH]; CFURLGetFileSystemRepresentation(url, true, (UInt8*)url_path, Elms(url_path));
  647. app=FromUTF8(url_path);
  648. CFRelease(url);
  649. }
  650. CFRelease(bundle);
  651. }
  652. return app;
  653. #elif LINUX
  654. char path[MAX_UTF_PATH]; path[0]='\0'; ssize_t r=readlink("/proc/self/exe", path, SIZE(path)); if(InRange(r, path))path[r]='\0';
  655. return FromUTF8(path);
  656. #elif ANDROID
  657. return AndroidAppPath; // obtained externally
  658. #elif WEB
  659. return S;
  660. #endif
  661. }
  662. static Bool GetProcessElevation()
  663. {
  664. #if WINDOWS_OLD
  665. Bool elevated=false;
  666. HANDLE token =null;
  667. if(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
  668. {
  669. TOKEN_ELEVATION elevation;
  670. DWORD size;
  671. if(GetTokenInformation(token, TokenElevation, &elevation, SIZE(elevation), &size)) // elevation supported (>= Windows Vista)
  672. {
  673. elevated=(elevation.TokenIsElevated!=0);
  674. }else // elevation not supported (< Windows Vista)
  675. {
  676. elevated=true;
  677. }
  678. }
  679. if(token){CloseHandle(token); token=null;}
  680. return elevated;
  681. #elif WINDOWS_NEW
  682. return false;
  683. #else
  684. return true;
  685. #endif
  686. }
  687. /******************************************************************************/
  688. #if WINDOWS_OLD
  689. static BOOL CALLBACK EnumResources(HMODULE hModule, LPCWSTR lpType, LPWSTR lpName, LONG_PTR user)
  690. {
  691. if(HRSRC resource=FindResource(hModule, lpName, lpType))
  692. {
  693. Paks.addMem(LockResource(LoadResource(null, resource)), SizeofResource(null, resource), (Cipher*)user, false);
  694. return true;
  695. }
  696. Exit(MLTC(u"Can't load resource data from exe", PL,u"Nie można wczytać danych z pliku exe"));
  697. return false;
  698. }
  699. #endif
  700. void LoadEmbeddedPaks(Cipher *cipher)
  701. {
  702. #if WINDOWS_OLD
  703. EnumResourceNames(App._hinstance, L"PAK", EnumResources, IntPtr(cipher)); // iterate through all "PAK" resources embedded in .exe file
  704. SetLastError(0); // clear error 1813 of not found resource type
  705. #elif MAC
  706. for(FileFind ff(App.exe()+"/Contents/Resources", "pak"); ff(); )Paks.add(ff.pathName(), cipher, false); // iterate all PAK files inside APP resources folder
  707. #elif LINUX
  708. File f; if(f.readStdTry(App.exe()))for(Long next=f.size(); f.pos(next-2*4); )
  709. {
  710. Long skip=f.getUInt(); // !! use Long and not UInt, because of "-skip" below, which would cause incorrect behavior
  711. UInt end =f.getUInt();
  712. #define CC4_CHNK CC4('C', 'H', 'N', 'K')
  713. if(end==CC4_CHNK && f.skip(-skip))
  714. {
  715. next=f.pos();
  716. if(f.getUInt()==CC4_CHNK)
  717. {
  718. UInt size=f.getUInt();
  719. if( size+4*4==skip) // 2xCHNK + 2xSIZE = 4 TOTAL
  720. {
  721. ULong total_size, applied_offset;
  722. f.limit(total_size, applied_offset, size);
  723. switch(f.getUInt())
  724. {
  725. case CC4('P', 'A', 'K', 0):
  726. {
  727. Paks.addTry(App.exe(), cipher, false, f.posAbs()); // use 'addTry' unlike on other platforms, because this could be not EE data (very unlikely)
  728. }break;
  729. case CC4('I', 'C', 'O', 'N'):
  730. {
  731. Image icon; if(icon.ImportTry(f, -1, IMAGE_SOFT, 1))if(icon.is())App.icon(icon);
  732. }break;
  733. }
  734. f.unlimit(total_size, applied_offset);
  735. continue; // try next chunk
  736. }
  737. }
  738. }
  739. break;
  740. }
  741. #endif
  742. Paks.rebuild();
  743. }
  744. /******************************************************************************/
  745. Bool Application::create0()
  746. {
  747. #if WINDOWS_OLD
  748. ShutCOM=OK(CoInitialize(null)); // required by: creating shortctuts - IShellLink, Unicode IME support, ITaskbarList3, Visual Studio Installation detection - SetupConfiguration, SHOpenFolderAndSelectItems
  749. TouchesSupported=(GetSystemMetrics(SM_MAXIMUMTOUCHES)>0);
  750. #elif WINDOWS_NEW
  751. TouchesSupported=(Windows::Devices::Input::TouchCapabilities().TouchPresent>0);
  752. #elif MAC
  753. [MyApplication sharedApplication]; // this allocates 'NSApp' application as our custom class
  754. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  755. [NSApp finishLaunching];
  756. [NSApp setDelegate:[[MyAppDelegate alloc] init]]; // don't release delegate, instead it's released in 'MyApplication' dealloc
  757. #elif LINUX
  758. XInitThreads();
  759. XDisplay=XOpenDisplay(null);
  760. OldErrorHandler=XSetErrorHandler(ErrorHandler); // set custom error handler, since I've noticed that occasionally BadWindow errors get generated, so just ignore them
  761. if(XDisplay)FIND_ATOM(_NET_WM_ICON);
  762. #endif
  763. T._thread_id =GetThreadId(); // !! adjust the thread ID here, because on WINDOWS_NEW it will be a different value !!
  764. T._elevated =GetProcessElevation();
  765. T._process_id =PLATFORM(GetCurrentProcessId(), getpid());
  766. T._desktop_size=D.screen();
  767. T._desktop_area=GetDesktopArea(); // !! call after getting '_desktop_size' !!
  768. T._exe =GetAppPathName();
  769. Time.create(); // set first, to start measuring init time
  770. InitHash ();
  771. InitMisc ();
  772. InitIO (); // init IO early in case we want to output logs to files
  773. InitMesh ();
  774. InitSRGB ();
  775. InitSocket();
  776. InitWindow();
  777. InitStream();
  778. InitState ();
  779. Kb.init();
  780. D.init();
  781. #if WEB
  782. InitSound (); // on WEB init sound before the 'Preload' and 'InitPre' so we can play some music while waiting
  783. #endif
  784. return true;
  785. }
  786. Bool Application::create1()
  787. {
  788. if(LogInit)LogN("InitPre");
  789. InitPre();
  790. #if LINUX
  791. if(!XDisplay && !(flag&APP_ALLOW_NO_XDISPLAY))Exit("Can't open XDisplay");
  792. #endif
  793. if(!testInstance())return false;
  794. windowCreate();
  795. InitSound ();
  796. if(!InputDevices.create())Exit(MLTC(u"Can't create DirectInput", PL,u"Nie można utworzyć DirectInput"));
  797. if(!D .create())return false;
  798. #if ANDROID
  799. if(_stay_awake){AWAKE_MODE temp=_stay_awake; _stay_awake=AWAKE_OFF; stayAwake(temp);} // on Android we need to apply this after window was created
  800. #endif
  801. if(LogInit)LogN("Init");
  802. if(!Init ())return false;
  803. return true;
  804. }
  805. Bool Application::create()
  806. {
  807. return create0() && create1();
  808. }
  809. static void FadeOut()
  810. {
  811. #if DESKTOP
  812. if(App.flag&APP_FADE_OUT)
  813. {
  814. Bool fade_sound=PlayingAnySound(), fade_window=!D.full();
  815. Byte alpha=WindowGetAlpha();
  816. #if WINDOWS_OLD
  817. ANIMATIONINFO ai; ai.cbSize=SIZE(ai); SystemParametersInfo(SPI_GETANIMATION, SIZE(ai), &ai, 0); if(ai.iMinAnimate)fade_window=false; // if Windows has animations enabled, then don't fade manually
  818. #elif WINDOWS_NEW || LINUX // WindowsNew and Linux don't support 'WindowAlpha'
  819. fade_window=false;
  820. #endif
  821. if(!fade_window)WindowHide();
  822. if(fade_sound || fade_window)
  823. {
  824. const Int step=1;
  825. const Flt vol=SoundVolume.global(), length=0.2f;
  826. const Dbl end_time=Time.curTime()+length-step/1000.0f;
  827. for(;;)
  828. {
  829. Flt remaining=end_time-Time.curTime(), frac=Max(0, remaining/length);
  830. if(fade_sound ){SoundVolume.global(vol*frac); UpdateSound();}
  831. if(fade_window)WindowAlpha(Round(alpha*frac));
  832. #if MAC // on Mac we have to update events, otherwise 'WindowAlpha' won't do anything. We have to do it even when not fading window (when exiting from fullscreen mode) because without it, the window will be drawn as a restored window
  833. for(; NSEvent *event=[NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; ) // 'distantPast' will not wait for any new events but return those that happened already
  834. [NSApp sendEvent:event];
  835. #endif
  836. if(remaining<=0)break; // check this after setting values, to make sure we will set 0
  837. UpdateThreads();
  838. Time.wait(Min(Round(remaining*1000), step));
  839. }
  840. }
  841. }
  842. #endif
  843. }
  844. void Application::del()
  845. {
  846. { // do brackets to make sure that any temp objects created here are destroyed before 'detectMemLeaks' is called
  847. if(LogInit)LogN("ShutState");
  848. ShutState ();
  849. if(LogInit)LogN("Shut");
  850. Shut ();
  851. _initialized=false; setActive(false); // set '_initialized' to false to prevent from calling custom 'paused' callback in 'setActive', call 'setActive' because app will no longer be active, this is needed in case 'InputDevices.acquire' is called at a later stage, this is also needed because currently we need to disable disable magnetometer callback for WINDOWS_NEW (otherwise it will crash on Windows Phone when closing app)
  852. FlushIO ();
  853. ShutObj ();
  854. Physics.del ();
  855. D .del ();
  856. FadeOut ();
  857. ShutSound ();
  858. ShutEnum ();
  859. ShutAnimation ();
  860. ShutStream ();
  861. ShutSocket ();
  862. windowDel ();
  863. ShutWindow ();
  864. Paks .del(); // !! delete after deleting sound !! because sound streaming can still use file data
  865. InputDevices .del(); // !! delete after deleting window !! because releasing some joypads may take some time and window would be left visible
  866. HideNotifications();
  867. #if WINDOWS_OLD
  868. if(ShutCOM){ShutCOM=false; CoUninitialize();} // required by 'CoInitialize'
  869. #elif LINUX
  870. if(XDisplay){XCloseDisplay(XDisplay); XDisplay=null;}
  871. #elif MAC
  872. stayAwake(AWAKE_OFF); // on Mac disable staying awake, because we've created 'AssertionID' for it
  873. #endif
  874. deleteSelf();
  875. }
  876. #if 1 // reduce mem leaks logging on Mac
  877. _exe.del(); _name.del(); _cmd_line.del(); _back_text.del();
  878. #endif
  879. _closed=true; // !! this needs to be set before 'detectMemLeaks' because that may trigger calling destructors !!
  880. detectMemLeaks();
  881. }
  882. /******************************************************************************/
  883. void Application::update()
  884. {
  885. Time .update();
  886. InputDevices.update();
  887. Renderer .update();
  888. D .fadeUpdate();
  889. _callbacks.update();
  890. if(!(UpdateState() && DrawState()))_close=true;
  891. InputDevices.clear();
  892. }
  893. /******************************************************************************/
  894. void Application::coInitialize(UInt dwCoInit)
  895. {
  896. #if WINDOWS_OLD
  897. ShutWindow(); // close interfaces
  898. if(ShutCOM)CoUninitialize(); // close COM
  899. ShutCOM=OK(CoInitializeEx(null, dwCoInit)); // init COM
  900. InitWindow(); // init interfaces
  901. #endif
  902. }
  903. /******************************************************************************/
  904. void Break()
  905. {
  906. #if WINDOWS
  907. __debugbreak();
  908. #elif LINUX
  909. asm("int3");
  910. #elif !(MOBILE && !IOS_SIMULATOR) && !WEB // everything except MobileDevice and Web
  911. __asm{int 3};
  912. #endif
  913. }
  914. void ExitNow()
  915. {
  916. if(App.flag&APP_BREAKPOINT_ON_ERROR)Break();
  917. #if WEB
  918. emscripten_exit_with_live_runtime(); // '_exit' would allow calling global destructors, 'emscripten_exit_with_live_runtime' does not allow it because it gets not caught and the browser stops, alternative is to use 'abort'
  919. #else
  920. _exit(-1);
  921. #endif
  922. }
  923. void ExitEx(CChar *error)
  924. {
  925. if(App.exit)App.exit(error);
  926. FlushIO();
  927. App.showError(error);
  928. ExitNow();
  929. }
  930. void Exit(C Str &error)
  931. {
  932. if(App.flag&APP_CALLSTACK_ON_ERROR)
  933. {
  934. Str stack; if(GetCallStack(stack))ExitEx(Str(error).line()+"Current Call Stack:\n"+stack);
  935. }
  936. ExitEx(error);
  937. }
  938. void StartEEManually(Ptr dll_module_instance)
  939. {
  940. #if WINDOWS_OLD
  941. App._hinstance=(dll_module_instance ? (HINSTANCE)dll_module_instance : GetModuleHandle(null));
  942. if(App.create())App.loop();
  943. App.del ();
  944. #else
  945. Exit("'StartEEManually' is unsupported on this platform");
  946. #endif
  947. }
  948. /******************************************************************************/
  949. } // namespace EE
  950. /******************************************************************************/
  951. #if MAC || LINUX
  952. int main(int argc, char *argv[])
  953. {
  954. for(Int i=1; i<argc; i++) // start from #1, because #0 is always the executable name (if file name has spaces, then they're included in the #0 argv)
  955. {
  956. if(i>1)App._cmd_line+='\n'; // separate with new lines, to allow having arguments with spaces in value to be presented as one argument (for example, on Linux running: "./Project test" abc def "123 456" gives 4 arguments: {"./Project test", "abc", "def", "123 456"}
  957. App._cmd_line+=argv[i];
  958. }
  959. if(App.create())App.loop();
  960. App.del ();
  961. return 0;
  962. }
  963. #elif IOS
  964. int main(int argc, char *argv[])
  965. {
  966. extern Bool DontRemoveThisOriOSAppDelegateClassWontBeLinked ; DontRemoveThisOriOSAppDelegateClassWontBeLinked =false;
  967. extern Bool DontRemoveThisOrMyViewControllerClassWontBeLinked; DontRemoveThisOrMyViewControllerClassWontBeLinked=false;
  968. extern Bool DontRemoveThisOrEAGLViewClassWontBeLinked ; DontRemoveThisOrEAGLViewClassWontBeLinked =false;
  969. return UIApplicationMain(argc, argv, nil, nil);
  970. }
  971. #endif
  972. /******************************************************************************/