ecere/gui/drivers/Emscripten: Multi-touch support
[sdk] / ecere / src / gui / drivers / AndroidInterface.ec
1 #define _Noreturn
2
3 namespace gui::drivers;
4
5 import "Window"
6 import "Interface"
7 import "Condition"
8
9 #define uint _uint
10 #define set _set
11 #include <errno.h>
12 #include <locale.h>
13 #include <pthread.h>
14 #include <unistd.h>
15
16 #include <android/configuration.h>
17 #include <android/looper.h>
18 #include <android/native_activity.h>
19 #include <android/sensor.h>
20 #include <android/log.h>
21 #include <android/window.h>
22
23 #include <jni.h>
24 #undef set
25 #undef uint
26
27 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "ecere-app", __VA_ARGS__))
28 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "ecere-app", __VA_ARGS__))
29 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "ecere-app", __VA_ARGS__))
30 #ifndef _DEBUG
31 #define LOGV(...)  ((void)0)
32 #else
33 #define LOGV(...)  ((void)__android_log_print(ANDROID_LOG_VERBOSE, "ecere-app", __VA_ARGS__))
34 #endif
35
36 // *** NATIVE APP GLUE ********
37 enum LooperID { main = 1, input = 2, user = 3 };
38 enum AppCommand : byte
39 {
40    error = 0, inputChanged, initWindow, termWindow, windowResized, windowRedrawNeeded,
41    contentRectChanged, gainedFocus, lostFocus,
42    configChanged, lowMemory, start, resume, saveState, pause, stop, destroy
43 };
44
45 class AndroidPollSource
46 {
47 public:
48    void * userData;
49    LooperID id;
50    virtual void any_object::process();
51 };
52
53 class AndroidAppGlue : Thread
54 {
55    void* userData;
56    virtual void onAppCmd(AppCommand cmd);
57    virtual int onInputEvent(AInputEvent* event);
58    virtual void main();
59
60    ANativeActivity* activity;
61    AConfiguration* config;
62    void* savedState;
63    uint savedStateSize;
64
65    ALooper* looper;
66    AInputQueue* inputQueue;
67    ANativeWindow* window;
68    ARect contentRect;
69    AppCommand activityState;
70    bool destroyRequested;
71    char * moduleName;
72
73 private:
74    Mutex mutex { };
75    Condition cond { };
76
77    int msgread, msgwrite;
78
79    unsigned int Main()
80    {
81       config = AConfiguration_new();
82       AConfiguration_fromAssetManager(config, activity->assetManager);
83
84       print_cur_config();
85
86       looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
87       ALooper_addFd(looper, msgread, LooperID::main, ALOOPER_EVENT_INPUT, null, cmdPollSource);
88
89       mutex.Wait();
90       running = true;
91       cond.Signal();
92       mutex.Release();
93
94       main();
95
96       destroy();
97       return 0;
98    }
99
100    void destroy()
101    {
102       free_saved_state();
103       mutex.Wait();
104       if(inputQueue)
105          AInputQueue_detachLooper(inputQueue);
106       AConfiguration_delete(config);
107       destroyed = true;
108       cond.Signal();
109       mutex.Release();
110    }
111
112    AndroidPollSource cmdPollSource
113    {
114       this, main;
115
116       void process()
117       {
118          AppCommand cmd = read_cmd();
119          pre_exec_cmd(cmd);
120          onAppCmd(cmd);
121          post_exec_cmd(cmd);
122       }
123    };
124    AndroidPollSource inputPollSource
125    {
126       this, input;
127
128       void process()
129       {
130          AInputEvent* event = null;
131          if(AInputQueue_getEvent(inputQueue, &event) >= 0)
132          {
133             //int handled = 0;
134             LOGV("New input event: type=%d\n", AInputEvent_getType(event));
135             if(AInputQueue_preDispatchEvent(inputQueue, event))
136                return;
137             /*handled = */onInputEvent(event);
138             //AInputQueue_finishEvent(inputQueue, event, handled);
139          }
140          else
141             LOGE("Failure reading next input event: %s\n", strerror(errno));
142       }
143    };
144
145    bool running;
146    bool stateSaved;
147    bool destroyed;
148    AInputQueue* pendingInputQueue;
149    ANativeWindow* pendingWindow;
150    ARect pendingContentRect;
151
152    void free_saved_state()
153    {
154       mutex.Wait();
155       if(savedState)
156          free(savedState);
157       savedState = 0;
158       savedStateSize = 0;
159       mutex.Release();
160    }
161
162    AppCommand read_cmd()
163    {
164       AppCommand cmd;
165       if(read(msgread, &cmd, sizeof(cmd)) == sizeof(cmd))
166       {
167          if(cmd == saveState)
168             free_saved_state();
169          return cmd;
170       }
171       else
172          LOGE("No data on command pipe!");
173       return error;
174    }
175
176    void print_cur_config()
177    {
178       char lang[2], country[2];
179       AConfiguration_getLanguage(config, lang);
180       AConfiguration_getCountry(config, country);
181
182       LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
183               "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
184               "modetype=%d modenight=%d",
185               AConfiguration_getMcc(config),
186               AConfiguration_getMnc(config),
187               lang[0], lang[1], country[0], country[1],
188               AConfiguration_getOrientation(config),
189               AConfiguration_getTouchscreen(config),
190               AConfiguration_getDensity(config),
191               AConfiguration_getKeyboard(config),
192               AConfiguration_getNavigation(config),
193               AConfiguration_getKeysHidden(config),
194               AConfiguration_getNavHidden(config),
195               AConfiguration_getSdkVersion(config),
196               AConfiguration_getScreenSize(config),
197               AConfiguration_getScreenLong(config),
198               AConfiguration_getUiModeType(config),
199               AConfiguration_getUiModeNight(config));
200    }
201
202    void pre_exec_cmd(AppCommand cmd)
203    {
204       PrintLn("pre_exec_cmd: ", cmd);
205       switch(cmd)
206       {
207          case inputChanged:
208             mutex.Wait();
209             if(inputQueue)
210                AInputQueue_detachLooper(inputQueue);
211             inputQueue = pendingInputQueue;
212             if(inputQueue)
213                AInputQueue_attachLooper(inputQueue, looper, LooperID::input, null, inputPollSource);
214             cond.Signal();
215             mutex.Release();
216             break;
217          case initWindow:
218             mutex.Wait();
219             window = pendingWindow;
220             cond.Signal();
221             mutex.Release();
222             break;
223          case termWindow:
224             cond.Signal();
225             break;
226          case resume:
227          case start:
228          case pause:
229          case stop:
230             mutex.Wait();
231             activityState = cmd;
232             cond.Signal();
233             mutex.Release();
234             break;
235          case configChanged:
236             AConfiguration_fromAssetManager(config, activity->assetManager);
237             print_cur_config();
238             break;
239          case destroy:
240             destroyRequested = true;
241             break;
242       }
243    }
244
245    void post_exec_cmd(AppCommand cmd)
246    {
247       PrintLn("post_exec_cmd: ", cmd);
248       switch(cmd)
249       {
250          case termWindow:
251             mutex.Wait();
252             window = null;
253             cond.Signal();
254             mutex.Release();
255             break;
256          case saveState:
257             mutex.Wait();
258             stateSaved = true;
259             cond.Signal();
260             mutex.Release();
261             break;
262          case resume:
263             free_saved_state();
264             break;
265       }
266    }
267
268    void write_cmd(AppCommand cmd)
269    {
270       if(write(msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd))
271          LOGE("Failure writing android_app cmd: %s\n", strerror(errno));
272    }
273
274    void set_input(AInputQueue* inputQueue)
275    {
276       mutex.Wait();
277       pendingInputQueue = inputQueue;
278       write_cmd(inputChanged);
279       while(inputQueue != pendingInputQueue)
280          cond.Wait(mutex);
281       mutex.Release();
282    }
283
284    void set_window(ANativeWindow* window)
285    {
286       mutex.Wait();
287       if(pendingWindow)
288          write_cmd(termWindow);
289       pendingWindow = window;
290       if(window)
291          write_cmd(initWindow);
292       while(window != pendingWindow)
293          cond.Wait(mutex);
294       mutex.Release();
295    }
296
297    void set_activity_state(AppCommand cmd)
298    {
299       mutex.Wait();
300       write_cmd(cmd);
301       while(activityState != cmd)
302          cond.Wait(mutex);
303       mutex.Release();
304    }
305
306    void cleanup()
307    {
308       mutex.Wait();
309       write_cmd(destroy);
310       while(!destroyed)
311          cond.Wait(mutex);
312       mutex.Release();
313       close(msgread);
314       close(msgwrite);
315    }
316
317    void setSavedState(void * state, uint size)
318    {
319       if(savedState)
320          free(savedState);
321       savedState = null;
322       if(state)
323       {
324          savedState = malloc(size);
325          savedStateSize = size;
326          memcpy(savedState, state, size);
327       }
328       else
329          savedStateSize = 0;
330    }
331
332    public void Create()
333    {
334       int msgpipe[2];
335       if(pipe(msgpipe))
336          LOGE("could not create pipe: %s", strerror(errno));
337       msgread = msgpipe[0];
338       msgwrite = msgpipe[1];
339
340       Thread::Create();
341
342       // Wait for thread to start.
343       mutex.Wait();
344       while(!running) cond.Wait(mutex);
345       mutex.Release();
346    }
347 }
348
349 // Callbacks
350 static void onDestroy(ANativeActivity* activity)
351 {
352    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
353    LOGI("Destroy: %p\n", activity);
354    app.cleanup();
355    app.Wait();
356    delete androidActivity;
357    delete __androidCurrentModule;
358    LOGI("THE END.");
359 }
360
361 static void onStart(ANativeActivity* activity)
362 {
363    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
364    LOGI("Start: %p\n", activity);
365    app.set_activity_state(start);
366 }
367
368 static void onResume(ANativeActivity* activity)
369 {
370    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
371    LOGI("Resume: %p\n", activity);
372    app.set_activity_state(resume);
373 }
374
375 static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen)
376 {
377    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
378    void* savedState = null;
379    LOGI("SaveInstanceState: %p\n", activity);
380    app.mutex.Wait();
381    app.stateSaved = false;
382    app.write_cmd(saveState);
383    while(!app.stateSaved)
384       app.cond.Wait(app.mutex);
385    if(app.savedState)
386    {
387       savedState = app.savedState;
388       *outLen = app.savedStateSize;
389       app.savedState = null;
390       app.savedStateSize = 0;
391    }
392    app.mutex.Release();
393    return savedState;
394 }
395
396 static void onPause(ANativeActivity* activity)
397 {
398    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
399    LOGI("Pause: %p\n", activity);
400    app.set_activity_state(pause);
401 }
402
403 static void onStop(ANativeActivity* activity)
404 {
405    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
406    LOGI("Stop: %p\n", activity);
407    app.set_activity_state(stop);
408 }
409
410 static void onConfigurationChanged(ANativeActivity* activity)
411 {
412    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
413    LOGI("ConfigurationChanged: %p\n", activity);
414    app.write_cmd(configChanged);
415 }
416
417 static void onLowMemory(ANativeActivity* activity)
418 {
419    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
420    LOGI("LowMemory: %p\n", activity);
421    app.write_cmd(lowMemory);
422 }
423
424 static void onWindowFocusChanged(ANativeActivity* activity, int focused)
425 {
426    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
427    LOGI("WindowFocusChanged: %p -- %d\n", activity, focused);
428    app.write_cmd(focused ? gainedFocus : lostFocus);
429 }
430
431 static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window)
432 {
433    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
434    LOGI("NativeWindowCreated: %p -- %p\n", activity, window);
435    app.set_window(window);
436 }
437
438 static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window)
439 {
440    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
441    LOGI("NativeWindowDestroyed: %p -- %p\n", activity, window);
442    app.window = null;
443    app.set_window(null);
444 }
445
446 static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue)
447 {
448    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
449    LOGI("InputQueueCreated: %p -- %p\n", activity, queue);
450    app.set_input(queue);
451 }
452
453 static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue)
454 {
455    AndroidAppGlue app = (AndroidAppGlue)activity->instance;
456    LOGI("InputQueueDestroyed: %p -- %p\n", activity, queue);
457    app.inputQueue = null;
458    app.set_input(null);
459 }
460
461 default dllexport void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize)
462 {
463    AndroidAppGlue app;
464    char * moduleName;
465
466    // Determine our package name
467    JNIEnv* env=activity->env;
468    jclass clazz;
469    const char* str;
470    jboolean isCopy;
471    jmethodID methodID;
472    jobject result;
473
474    // *** Reinitialize static global variables ***
475    gotInit = false;
476    guiApplicationInitialized = false;
477    guiApp = null;
478    desktopW = 0; desktopH = 0;
479    clipBoardData = null;
480    __thisModule = null;
481    __androidCurrentModule = null;
482
483    LOGI("Creating: %p\n", activity);
484
485    //(*activity->vm)->AttachCurrentThread(activity->vm, &env, 0);
486    clazz = (*env)->GetObjectClass(env, activity->clazz);
487    methodID = (*env)->GetMethodID(env, clazz, "getPackageName", "()Ljava/lang/String;");
488    result = (*env)->CallObjectMethod(env, activity->clazz, methodID);
489    str = (*env)->GetStringUTFChars(env, (jstring)result, &isCopy);
490    // (*activity->vm)->DetachCurrentThread(activity->vm);
491    moduleName = strstr(str, "com.ecere.");
492    if(moduleName) moduleName += 10;
493    androidArgv[0] = moduleName;
494
495    // Create a base Application class
496    __androidCurrentModule = __ecere_COM_Initialize(true, 1, androidArgv);
497    // Load up Ecere
498    eModule_Load(__androidCurrentModule, "ecere", publicAccess);
499
500
501    if(activity->internalDataPath) PrintLn("internalDataPath is ", activity->internalDataPath);
502    if(activity->externalDataPath) PrintLn("externalDataPath is ", activity->externalDataPath);
503    {
504       char tmp[256];
505       PrintLn("cwd is ", GetWorkingDir(tmp, sizeof(tmp)));
506    }
507
508    ANativeActivity_setWindowFlags(activity, AWINDOW_FLAG_FULLSCREEN|AWINDOW_FLAG_KEEP_SCREEN_ON, 0 );
509    app = AndroidActivity { activity = activity, moduleName = moduleName };
510    incref app;
511    app.setSavedState(savedState, (uint)savedStateSize);
512    activity->callbacks->onDestroy = onDestroy;
513    activity->callbacks->onStart = onStart;
514    activity->callbacks->onResume = onResume;
515    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
516    activity->callbacks->onPause = onPause;
517    activity->callbacks->onStop = onStop;
518    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
519    activity->callbacks->onLowMemory = onLowMemory;
520    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
521    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
522    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
523    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
524    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
525    activity->instance = app;
526    app.Create();
527 }
528
529 // *** END OF NATIVE APP GLUE ******
530
531 default:
532 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
533 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp;
534 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown;
535 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
536 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove;
537 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick;
538 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown;
539 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp;
540 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick;
541 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown;
542 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp;
543 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick;
544 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown;
545 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp;
546 private:
547
548 static Module __androidCurrentModule;
549 static char * androidArgv[1];
550
551 static int desktopW, desktopH;
552 static char * clipBoardData;
553 static int mouseX, mouseY;
554
555 class AndroidInterface : Interface
556 {
557    class_property(name) = "Android";
558
559    // --- User Interface System ---
560    bool Initialize()
561    {
562       setlocale(LC_ALL, "en_US.UTF-8");
563       return true;
564    }
565
566    void Terminate()
567    {
568
569    }
570
571    #define DBLCLICK_DELAY  300   // 0.3 second
572    #define DBLCLICK_DELTA  1
573
574    bool ProcessInput(bool processAll)
575    {
576       bool eventAvailable = false;
577
578       if(androidActivity.ident < 0)
579          androidActivity.ident = (LooperID)ALooper_pollAll(0, null, &androidActivity.events, (void**)&androidActivity.source);
580
581       if(gotInit && androidActivity.window)
582       {
583          int w = ANativeWindow_getWidth(androidActivity.window);
584          int h = ANativeWindow_getHeight(androidActivity.window);
585          if(desktopW != w || desktopH != h)
586          {
587             guiApp.SetDesktopPosition(0, 0, w, h, true);
588             desktopW = w;
589             desktopH = h;
590             guiApp.desktop.Update(null);
591          }
592       }
593
594       while(androidActivity.ident >= 0)
595       {
596          AndroidPollSource source = androidActivity.source;
597
598          androidActivity.source = null;
599          if(source)
600             source.process(source.userData);
601
602          // If a sensor has data, process it now.
603          /*
604          if(androidActivity.ident == user)
605          {
606             if(androidActivity.accelerometerSensor)
607             {
608                ASensorEvent event;
609                while (ASensorEventQueue_getEvents(androidActivity.sensorEventQueue, &event, 1) > 0)
610                   LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x, event.acceleration.y, event.acceleration.z);
611             }
612          }
613          */
614
615          eventAvailable = true;
616          if(androidActivity.destroyRequested)
617          {
618             guiApp.desktop.Destroy(0);
619             eventAvailable = true;
620             androidActivity.ident = (LooperID)-1;
621          }
622          else if(processAll)
623             androidActivity.ident = (LooperID)ALooper_pollAll(0, null, &androidActivity.events, (void**)&androidActivity.source);
624          else
625             androidActivity.ident = (LooperID)-1;
626       }
627       return eventAvailable;
628    }
629
630    void Wait()
631    {
632       androidActivity.ident = (LooperID)ALooper_pollAll((int)(1000/18.2f), null, &androidActivity.events, (void**)&androidActivity.source);
633       // guiApp.WaitEvent();
634    }
635
636    void Lock(Window window)
637    {
638
639    }
640
641    void Unlock(Window window)
642    {
643
644    }
645
646    const char ** GraphicsDrivers(int * numDrivers)
647    {
648       static const char *graphicsDrivers[] = { "OpenGL" };
649       *numDrivers = sizeof(graphicsDrivers) / sizeof(char *);
650       return (const char **)graphicsDrivers;
651    }
652
653    void GetCurrentMode(bool * fullScreen, int * resolution, int * colorDepth, int * refreshRate)
654    {
655       *fullScreen = true;
656    }
657
658    void EnsureFullScreen(bool *fullScreen)
659    {
660       *fullScreen = true;
661    }
662
663    bool ScreenMode(bool fullScreen, int resolution, int colorDepth, int refreshRate, bool * textMode)
664    {
665       bool result = true;
666
667       return result;
668    }
669
670    // --- Window Creation ---
671    void * CreateRootWindow(Window window)
672    {
673       return androidActivity.window;
674    }
675
676    void DestroyRootWindow(Window window)
677    {
678
679    }
680
681    // -- Window manipulation ---
682
683    void SetRootWindowCaption(Window window, const char * name)
684    {
685
686    }
687
688    void PositionRootWindow(Window window, int x, int y, int w, int h, bool move, bool resize)
689    {
690
691    }
692
693    void OrderRootWindow(Window window, bool topMost)
694    {
695
696    }
697
698    void SetRootWindowColor(Window window)
699    {
700
701    }
702
703    void OffsetWindow(Window window, int * x, int * y)
704    {
705
706    }
707
708    void UpdateRootWindow(Window window)
709    {
710       if(!window.parent || !window.parent.display)
711       {
712          if(window.visible)
713          {
714             Box box = window.box;
715             box.left -= window.clientStart.x;
716             box.top -= window.clientStart.y;
717             box.right -= window.clientStart.x;
718             box.bottom -= window.clientStart.y;
719             // Logf("Update root window %s\n", window.name);
720             window.Update(null);
721             box.left   += window.clientStart.x;
722             box.top    += window.clientStart.y;
723             box.right  += window.clientStart.x;
724             box.bottom += window.clientStart.y;
725             window.UpdateDirty(box);
726          }
727       }
728    }
729
730
731    void SetRootWindowState(Window window, WindowState state, bool visible)
732    {
733    }
734
735    void FlashRootWindow(Window window)
736    {
737
738    }
739
740    void ActivateRootWindow(Window window)
741    {
742
743    }
744
745    // --- Mouse-based window movement ---
746
747    void StartMoving(Window window, int x, int y, bool fromKeyBoard)
748    {
749
750    }
751
752    void StopMoving(Window window)
753    {
754
755    }
756
757    // -- Mouse manipulation ---
758
759    void GetMousePosition(int *x, int *y)
760    {
761       *x = mouseX;
762       *y = mouseY;
763    }
764
765    void SetMousePosition(int x, int y)
766    {
767       mouseX = x;
768       mouseY = y;
769    }
770
771    void SetMouseRange(Window window, Box box)
772    {
773    }
774
775    void SetMouseCapture(Window window)
776    {
777    }
778
779    // -- Mouse cursor ---
780
781    void SetMouseCursor(Window window, int cursor)
782    {
783       if(cursor == -1)
784       {
785
786       }
787    }
788
789    // --- Caret ---
790
791    void SetCaret(int x, int y, int size)
792    {
793       Window caretOwner = guiApp.caretOwner;
794       Window window = caretOwner ? caretOwner.rootWindow : null;
795       if(window && window.windowData)
796       {
797       }
798    }
799
800    void ClearClipboard()
801    {
802       if(clipBoardData)
803       {
804          delete clipBoardData;
805       }
806    }
807
808    bool AllocateClipboard(ClipBoard clipBoard, uint size)
809    {
810       bool result = false;
811       if((clipBoard.text = new0 byte[size]))
812          result = true;
813       return result;
814    }
815
816    bool SaveClipboard(ClipBoard clipBoard)
817    {
818       bool result = false;
819       if(clipBoard.text)
820       {
821          if(clipBoardData)
822             delete clipBoardData;
823
824          clipBoardData = clipBoard.text;
825          clipBoard.text = null;
826          result = true;
827       }
828       return result;
829    }
830
831    bool LoadClipboard(ClipBoard clipBoard)
832    {
833       bool result = false;
834
835       // The data is inside this client...
836       if(clipBoardData)
837       {
838          clipBoard.text = new char[strlen(clipBoardData)+1];
839          strcpy(clipBoard.text, clipBoardData);
840          result = true;
841       }
842       // The data is with another client...
843       else
844       {
845       }
846       return result;
847    }
848
849    void UnloadClipboard(ClipBoard clipBoard)
850    {
851       delete clipBoard.text;
852    }
853
854    // --- State based input ---
855
856    bool AcquireInput(Window window, bool state)
857    {
858       return false;
859    }
860
861    bool GetMouseState(MouseButtons * buttons, int * x, int * y)
862    {
863       bool result = false;
864       if(x) *x = 0;
865       if(y) *y = 0;
866       return result;
867    }
868
869    bool GetJoystickState(int device, Joystick joystick)
870    {
871       bool result = false;
872       return result;
873    }
874
875    bool GetKeyState(Key key)
876    {
877       bool keyState = false;
878       return keyState;
879    }
880
881    void SetTimerResolution(uint hertz)
882    {
883       // timerDelay = hertz ? (1000000 / hertz) : MAXINT;
884    }
885
886    bool SetIcon(Window window, BitmapResource resource)
887    {
888       if(resource)
889       {
890          /*Bitmap bitmap { };
891          if(bitmap.Load(resource.fileName, null, null))
892          {
893          }
894          delete bitmap;*/
895       }
896       return true;
897    }
898 }
899
900 struct SavedState
901 {
902     float angle;
903     int x;
904     int y;
905 };
906
907 static AndroidActivity androidActivity;
908
909 default const char * AndroidInterface_GetLibLocation()
910 {
911    if(androidActivity)
912    {
913       static char loc[MAX_LOCATION];
914       sprintf(loc, "/data/data/com.ecere.%s/lib/lib", androidActivity.moduleName);
915       return loc;
916    }
917    return null;
918 }
919
920 static bool gotInit;
921
922 default float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
923         int32_t axis, size_t pointer_index);
924
925
926 static define AMETA_META_ON       = 0x00010000;
927 static define AMETA_META_LEFT_ON  = 0x00020000;
928 static define AMETA_META_RIGHT_ON = 0x00040000;
929
930 static Key keyCodeTable[] =
931 {
932     0, //AKEYCODE_UNKNOWN         = 0,
933     0, //AKEYCODE_SOFT_LEFT       = 1,
934     0, //AKEYCODE_SOFT_RIGHT      = 2,
935     0, //AKEYCODE_HOME            = 3,
936     0, //AKEYCODE_BACK            = 4,
937     0, //AKEYCODE_CALL            = 5,
938     0, //AKEYCODE_ENDCALL         = 6,
939     k0, //AKEYCODE_0               = 7,
940     k1, //AKEYCODE_1               = 8,
941     k2, //AKEYCODE_2               = 9,
942     k3, //AKEYCODE_3               = 10,
943     k4, //AKEYCODE_4               = 11,
944     k5, //AKEYCODE_5               = 12,
945     k6, //AKEYCODE_6               = 13,
946     k7, //AKEYCODE_7               = 14,
947     k8, //AKEYCODE_8               = 15,
948     k9, //AKEYCODE_9               = 16,
949     keyPadStar, //AKEYCODE_STAR            = 17,
950     Key { k3, shift = true }, //AKEYCODE_POUND           = 18,
951     up, //AKEYCODE_DPAD_UP         = 19,
952     down, //AKEYCODE_DPAD_DOWN       = 20,
953     left, //AKEYCODE_DPAD_LEFT       = 21,
954     right, //AKEYCODE_DPAD_RIGHT      = 22,
955     keyPad5, //AKEYCODE_DPAD_CENTER     = 23,
956     0, //AKEYCODE_VOLUME_UP       = 24,
957     0, //AKEYCODE_VOLUME_DOWN     = 25,
958     0, //AKEYCODE_POWER           = 26,
959     0, //AKEYCODE_CAMERA          = 27,
960     0, //AKEYCODE_CLEAR           = 28,
961     a, //AKEYCODE_A               = 29,
962     b, //AKEYCODE_B               = 30,
963     c, //AKEYCODE_C               = 31,
964     d, //AKEYCODE_D               = 32,
965     e, //AKEYCODE_E               = 33,
966     f, //AKEYCODE_F               = 34,
967     g, //AKEYCODE_G               = 35,
968     h, //AKEYCODE_H               = 36,
969     i, //AKEYCODE_I               = 37,
970     j, //AKEYCODE_J               = 38,
971     k, //AKEYCODE_K               = 39,
972     l, //AKEYCODE_L               = 40,
973     m, //AKEYCODE_M               = 41,
974     n, //AKEYCODE_N               = 42,
975     o, //AKEYCODE_O               = 43,
976     p, //AKEYCODE_P               = 44,
977     q, //AKEYCODE_Q               = 45,
978     r, //AKEYCODE_R               = 46,
979     s, //AKEYCODE_S               = 47,
980     t, //AKEYCODE_T               = 48,
981     u, //AKEYCODE_U               = 49,
982     v, //AKEYCODE_V               = 50,
983     w, //AKEYCODE_W               = 51,
984     x, //AKEYCODE_X               = 52,
985     y, //AKEYCODE_Y               = 53,
986     z, //AKEYCODE_Z               = 54,
987     comma, //AKEYCODE_COMMA           = 55,
988     period, //AKEYCODE_PERIOD          = 56,
989     leftAlt, //AKEYCODE_ALT_LEFT        = 57,
990     rightAlt, //AKEYCODE_ALT_RIGHT       = 58,
991     leftShift, //AKEYCODE_SHIFT_LEFT      = 59,
992     rightShift, //AKEYCODE_SHIFT_RIGHT     = 60,
993     tab, //AKEYCODE_TAB             = 61,
994     space, //AKEYCODE_SPACE           = 62,
995     0, //AKEYCODE_SYM             = 63,
996     0, //AKEYCODE_EXPLORER        = 64,
997     0, //AKEYCODE_ENVELOPE        = 65,
998     enter, //AKEYCODE_ENTER           = 66,
999     backSpace, //AKEYCODE_DEL             = 67,
1000     backQuote, //AKEYCODE_GRAVE           = 68,
1001     minus, //AKEYCODE_MINUS           = 69,
1002     plus, //AKEYCODE_EQUALS          = 70,
1003     leftBracket, //AKEYCODE_LEFT_BRACKET    = 71,
1004     rightBracket, //AKEYCODE_RIGHT_BRACKET   = 72,
1005     backSlash, //AKEYCODE_BACKSLASH       = 73,
1006     semicolon, //AKEYCODE_SEMICOLON       = 74,
1007     quote, //AKEYCODE_APOSTROPHE      = 75,
1008     slash, //AKEYCODE_SLASH           = 76,
1009     Key { k2, shift = true }, //AKEYCODE_AT              = 77,
1010     0, //AKEYCODE_NUM             = 78,      // Interpreted as an Alt
1011     0, //AKEYCODE_HEADSETHOOK     = 79,
1012     0, //AKEYCODE_FOCUS           = 80,   // *Camera* focus
1013     keyPadPlus, //AKEYCODE_PLUS            = 81,
1014     0, //AKEYCODE_MENU            = 82,
1015     0, //AKEYCODE_NOTIFICATION    = 83,
1016     0, //AKEYCODE_SEARCH          = 84,
1017     0, //AKEYCODE_MEDIA_PLAY_PAUSE= 85,
1018     0, //AKEYCODE_MEDIA_STOP      = 86,
1019     0, //AKEYCODE_MEDIA_NEXT      = 87,
1020     0, //AKEYCODE_MEDIA_PREVIOUS  = 88,
1021     0, //AKEYCODE_MEDIA_REWIND    = 89,
1022     0, //AKEYCODE_MEDIA_FAST_FORWARD = 90,
1023     0, //AKEYCODE_MUTE            = 91,
1024     0, //AKEYCODE_PAGE_UP         = 92,
1025     0, //AKEYCODE_PAGE_DOWN       = 93,
1026     0, //AKEYCODE_PICTSYMBOLS     = 94,
1027     0, //AKEYCODE_SWITCH_CHARSET  = 95,
1028     0, //AKEYCODE_BUTTON_A        = 96,
1029     0, //AKEYCODE_BUTTON_B        = 97,
1030     0, //AKEYCODE_BUTTON_C        = 98,
1031     0, //AKEYCODE_BUTTON_X        = 99,
1032     0, //AKEYCODE_BUTTON_Y        = 100,
1033     0, //AKEYCODE_BUTTON_Z        = 101,
1034     0, //AKEYCODE_BUTTON_L1       = 102,
1035     0, //AKEYCODE_BUTTON_R1       = 103,
1036     0, //AKEYCODE_BUTTON_L2       = 104,
1037     0, //AKEYCODE_BUTTON_R2       = 105,
1038     0, //AKEYCODE_BUTTON_THUMBL   = 106,
1039     0, //AKEYCODE_BUTTON_THUMBR   = 107,
1040     0, //AKEYCODE_BUTTON_START    = 108,
1041     0, //AKEYCODE_BUTTON_SELECT   = 109,
1042     0, //AKEYCODE_BUTTON_MODE     = 110,
1043     escape, //AKEYCODE_BUTTON_ESCAPE = 111,
1044     del, //AKEYCODE_BUTTON_ESCAPE    = 112,
1045     leftControl, // = 113
1046     rightControl, // = 114
1047     capsLock, // = 115
1048     scrollLock, // = 116
1049     0, // = 117      KEYCODE_META_LEFT
1050     0, // = 118      KEYCODE_META_RIGHT
1051     0, // = 119      KEYCODE_FUNCTION
1052     printScreen, // = 120      KEYCODE_SYSRQ
1053     pauseBreak, // = 121
1054     home, // = 122
1055     end, // = 123
1056     insert // = 124
1057 };
1058
1059 // Why don't we have this in the NDK :(
1060 // default int32_t AKeyEvent_getUnichar(const AInputEvent* key_event);
1061
1062 static Array<TouchPointerInfo> buildPointerInfo(AInputEvent * event)
1063 {
1064    uint count = (uint)AMotionEvent_getPointerCount(event);
1065    Array<TouchPointerInfo> infos { size = count };
1066    int i;
1067    for(i = 0; i < count; i++)
1068    {
1069       infos[i].point = { (int)AMotionEvent_getX(event, i), (int)AMotionEvent_getY(event, i) };
1070       infos[i].id = (int)AMotionEvent_getPointerId(event, i);
1071       infos[i].pressure = AMotionEvent_getPressure(event, i);
1072       infos[i].size = AMotionEvent_getSize(event, i);
1073    }
1074    return infos;
1075 }
1076
1077 class AndroidActivity : AndroidAppGlue
1078 {
1079    AndroidPollSource source;
1080    int events;
1081    LooperID ident;
1082    /*
1083    ASensorManager* sensorManager;
1084    const ASensor* accelerometerSensor;
1085    ASensorEventQueue* sensorEventQueue;
1086    */
1087    SavedState state;
1088
1089    int onInputEvent(AInputEvent* event)
1090    {
1091       static Time lastTime = 0;
1092       Window window = guiApp.desktop;
1093       uint type = AInputEvent_getType(event);
1094       if(type == AINPUT_EVENT_TYPE_MOTION)
1095       {
1096          uint actionAndIndex = AMotionEvent_getAction(event);
1097          //uint source = AInputEvent_getSource(event);
1098          uint action = actionAndIndex & AMOTION_EVENT_ACTION_MASK;
1099          //uint index  = (actionAndIndex & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
1100          //uint flags = AMotionEvent_getFlags(event);
1101          uint meta = AMotionEvent_getMetaState(event);
1102          //uint edge = AMotionEvent_getEdgeFlags(event);
1103          //int64 downTime = AMotionEvent_getDownTime(event);     // nanotime
1104          //int64 eventTime = AMotionEvent_getDownTime(event);
1105          //float axis;
1106          Modifiers keyFlags = 0;
1107          int x = (int)AMotionEvent_getX(event, 0);
1108          int y = (int)AMotionEvent_getY(event, 0);
1109          bool shift = (meta & AMETA_SHIFT_ON) ? true : false;
1110          bool alt = (meta & AMETA_ALT_ON) ? true : false;
1111          //bool sym = (meta & AMETA_SYM_ON) ? true : false;
1112
1113          keyFlags.shift = shift;
1114          keyFlags.alt = alt;
1115
1116          //PrintLn("Got a motion input event: ", action);
1117          /*
1118          if(action == 8) //AMOTION_EVENT_ACTION_SCROLL)
1119             axis = AMotionEvent_getAxisValue(event, 9, index); //AMOTION_EVENT_AXIS_VSCROLL);
1120          */
1121
1122          AInputQueue_finishEvent(inputQueue, event, 1);
1123          switch(action)
1124          {
1125             /*
1126             case 8: //AMOTION_EVENT_ACTION_SCROLL:
1127                window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit, (axis < 0) ? wheelUp : wheelDown, 0);
1128                break;
1129                */
1130             case AMOTION_EVENT_ACTION_DOWN:
1131             {
1132                Time time = GetTime();
1133                bool result = true;
1134                if(Abs(x - mouseX) < 40 && Abs(y - mouseY) < 40 && time - lastTime < 0.3)
1135                   if(!window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick, x, y, &keyFlags, false, true))
1136                      result = false;
1137                lastTime = time;
1138                mouseX = x, mouseY = y;
1139                if(result)
1140                   // TOCHECK: Should we do result = here?
1141                   window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, x, y, &keyFlags, false, true);
1142                if(result)
1143                {
1144                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1145                   window.MultiTouchMessage(down, infos, &keyFlags, false, true);
1146                   delete infos;
1147                }
1148                break;
1149             }
1150             case AMOTION_EVENT_ACTION_UP:
1151                mouseX = x, mouseY = y;
1152                if(window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp, x, y, &keyFlags, false, true))
1153                {
1154                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1155                   window.MultiTouchMessage(up, infos, &keyFlags, false, true);
1156                   delete infos;
1157                }
1158                break;
1159             case AMOTION_EVENT_ACTION_MOVE:
1160                mouseX = x, mouseY = y;
1161                if(window.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove, x, y, &keyFlags, false, true))
1162                {
1163                   Array<TouchPointerInfo> infos = buildPointerInfo(event);
1164                   window.MultiTouchMessage(move, infos, &keyFlags, false, true);
1165                   delete infos;
1166                }
1167                break;
1168             case AMOTION_EVENT_ACTION_CANCEL: break;
1169             case AMOTION_EVENT_ACTION_OUTSIDE: break;
1170             case AMOTION_EVENT_ACTION_POINTER_DOWN:
1171             {
1172                Array<TouchPointerInfo> infos = buildPointerInfo(event);
1173                window.MultiTouchMessage(pointerDown, infos, &keyFlags, false, true);
1174                delete infos;
1175                break;
1176             }
1177             case AMOTION_EVENT_ACTION_POINTER_UP:
1178             {
1179                Array<TouchPointerInfo> infos = buildPointerInfo(event);
1180                window.MultiTouchMessage(pointerUp, infos, &keyFlags, false, true);
1181                delete infos;
1182                break;
1183             }
1184          }
1185          return 1;
1186       }
1187       else if(type == AINPUT_EVENT_TYPE_KEY)
1188       {
1189          uint action = AKeyEvent_getAction(event);
1190          //uint flags = AKeyEvent_getFlags(event);
1191          uint keyCode = AKeyEvent_getKeyCode(event);
1192          uint meta = AKeyEvent_getMetaState(event);
1193          Key key = keyCodeTable[keyCode];
1194          bool shift = (meta & AMETA_SHIFT_ON) ? true : false;
1195          bool alt = (meta & AMETA_ALT_ON || meta & AMETA_ALT_LEFT_ON || meta & AMETA_ALT_RIGHT_ON) ? true : false;
1196          //bool metaMeta = (meta & AMETA_META_ON || meta & AMETA_META_LEFT_ON || meta & AMETA_META_RIGHT_ON) ? true : false;
1197          //bool sym = (meta & AMETA_SYM_ON) ? true : false;
1198          //unichar ch = AKeyEvent_getUnichar(event);
1199          unichar ch = 0;
1200
1201          key.shift = shift;
1202          key.alt = alt;
1203
1204          AInputQueue_finishEvent(inputQueue, event, 1);
1205
1206          // PrintLn("Got a key: action = ", action, ", flags = ", flags, ", keyCode = ", keyCode, ", meta = ", meta, ": key = ", (int)key);
1207
1208          if(key)
1209          {
1210             if(action == AKEY_EVENT_ACTION_DOWN || action == AKEY_EVENT_ACTION_MULTIPLE)
1211             {
1212                /*if(key == wheelDown || key == wheelUp)
1213                   window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit, key, ch);
1214                else*/
1215                {
1216                   char c = Interface::TranslateKey(key.code, shift);
1217                   if(c > 0) ch = c;
1218                   window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown, key, ch);
1219                }
1220             }
1221             else if(action == AKEY_EVENT_ACTION_UP)
1222                window.KeyMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp, key, ch);
1223          }
1224          return 1;
1225       }
1226       else
1227          AInputQueue_finishEvent(inputQueue, event, 0);
1228       return 0;
1229    }
1230
1231    void onAppCmd(AppCommand cmd)
1232    {
1233       switch(cmd)
1234       {
1235          case saveState:
1236             setSavedState(&state, sizeof(state));
1237             break;
1238          case initWindow:
1239             if(window)
1240             {
1241                int w, h;
1242                gotInit = true;
1243                ANativeWindow_setBuffersGeometry(window, 0, 0, 0); //format);
1244                w = ANativeWindow_getWidth(window);
1245                h = ANativeWindow_getHeight(window);
1246                guiApp.Initialize(false);
1247                guiApp.desktop.windowHandle = window;
1248                guiApp.interfaceDriver = null;
1249                guiApp.SwitchMode(true, null, 0, 0, 0, null, false);
1250
1251                if(desktopW != w || desktopH != h)
1252                {
1253                   guiApp.SetDesktopPosition(0, 0, w, h, true);
1254                   desktopW = w;
1255                   desktopH = h;
1256                }
1257                guiApp.desktop.Update(null);
1258             }
1259             break;
1260          case termWindow:
1261             guiApp.desktop.UnloadGraphics(false);
1262             break;
1263          case gainedFocus:
1264             guiApp.desktop.Update(null);
1265             guiApp.SetAppFocus(true);
1266             /*
1267             if(accelerometerSensor)
1268             {
1269                ASensorEventQueue_enableSensor(sensorEventQueue, accelerometerSensor);
1270                ASensorEventQueue_setEventRate(sensorEventQueue, accelerometerSensor, (1000L/60)*1000);
1271             }
1272             */
1273             break;
1274          case lostFocus:
1275             /*
1276             if(accelerometerSensor)
1277                ASensorEventQueue_disableSensor(sensorEventQueue, accelerometerSensor);
1278             */
1279             guiApp.SetAppFocus(false);
1280             guiApp.desktop.Update(null);
1281             break;
1282          case configChanged:
1283             if(window)
1284                guiApp.desktop.UpdateDisplay();
1285             break;
1286       }
1287    }
1288
1289    void main()
1290    {
1291       androidActivity = this;
1292       /* Let's have fun with sensors when we have an actual device to play with
1293       sensorManager = ASensorManager_getInstance();
1294       accelerometerSensor = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_ACCELEROMETER);
1295       sensorEventQueue = ASensorManager_createEventQueue(sensorManager, looper, LooperID::user, null, null);
1296       */
1297
1298       if(savedState)
1299          state = *(SavedState*)savedState;
1300
1301       {
1302          Module app;
1303
1304          // Evolve the Application class into a GuiApplication
1305          eInstance_Evolve((Instance *)&__androidCurrentModule, class(GuiApplication));
1306
1307          // Wait for the initWindow command:
1308          guiApp.interfaceDriver = class(AndroidInterface);
1309          while(!gotInit)
1310          {
1311             // Can't call the GuiApplication here, because GuiApplication::Initialize() has not been called yet
1312             guiApp.interfaceDriver.Wait();
1313             guiApp.interfaceDriver.ProcessInput(true);
1314          }
1315
1316          // Invoke __ecereDll_Load() in lib[our package name].so
1317          app = eModule_Load(__androidCurrentModule, moduleName, publicAccess);
1318          if(app)
1319          {
1320             Class c;
1321             // Find out if any GuiApplication class was defined in our module
1322             for(c = app.classes.first; c && !eClass_IsDerived(c, class(GuiApplication)); c = c.next);
1323             if(!c) c = class(GuiApplication);
1324
1325             guiApp.lockMutex.Release();   // TOCHECK: Seems the evolve is losing our mutex lock here ?
1326
1327             // Evolve the Application into it
1328             eInstance_Evolve((Instance *)&__androidCurrentModule, c);
1329             guiApp = (GuiApplication)__androidCurrentModule;
1330
1331             {
1332                const String skin = guiApp.skin;
1333                *&guiApp.currentSkin = null;
1334                guiApp.SelectSkin(skin);
1335             }
1336
1337             guiApp.lockMutex.Wait();
1338
1339             // Call Main()
1340             ((void (*)(void *))(void *)__androidCurrentModule._vTbl[12])(__androidCurrentModule);
1341          }
1342
1343          if(!destroyRequested)
1344             ANativeActivity_finish(activity);
1345          while(!destroyRequested)
1346          {
1347             guiApp.interfaceDriver.Wait();
1348             guiApp.interfaceDriver.ProcessInput(true);
1349          }
1350       }
1351    }
1352 }