ecere: Initial Emscripten support
[sdk] / ecere / src / gui / drivers / NCursesInterface.ec
1 #define _Noreturn
2
3 namespace gui::drivers;
4
5 import "instance"
6
7 #if (defined(__unix__) || defined(__APPLE__)) && !defined(__DOS__)
8
9 #undef __BLOCKS__
10 #define DBLCLICK_DELAY  0.3  // seconds
11 #define DBLCLICK_DELTA  1
12
13 // #define DEBUG_THREADS
14
15 #include <curses.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <sys/time.h>
19 #include <unistd.h>
20
21 import "Interface"
22
23 static class Message : struct
24 {
25    Message prev, next;
26    uint vmid;
27    int x, y;
28    Key key;
29 };
30
31 static OldList messages;
32
33 static Window nCursesDesktop;
34 static Point mousePosition;
35 static Box mouseRange;
36 static int caretX, caretY, caretVisible;
37
38 #define TIMEOUT (1000 / 18.2)
39
40 static Key characters2Key[256] =
41 {
42 // K_PERIOD - DEL
43    0,0,0,0,0,0,0,0,backSpace,tab,0,0,0,enter,0,0,
44    leftShift,leftControl,0,0,capsLock,0,0,0,0,0,0,escape,0,0,0,0,
45 //   space,pageUp,pageDown,end,home,left,up,right,down,keyPad5,0,0,0,insert,period,0,
46    space,k1,quote,k3,k4,k5,k7,quote,k9,k0,k8,equal,comma,minus,period,slash,
47    k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,semicolon,semicolon,comma,equal,period,slash,
48    k2,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,
49    p,q,r,s,t,u,v,w,x,y,z,leftBracket,slash,rightBracket,k6,minus,
50    quote,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,
51    p,q,r,s,t,u,v,w,x,y,z,leftBracket,backSlash,rightBracket,quote,backSpace
52 };
53
54 static char * clipBoardData = null;
55
56 void ClearClipboard();
57
58 static Thread ncursesThread;
59 static Mutex ncursesMutex;
60 static bool ncursesTerminate;
61
62 /****************************************************************************
63    /// PRIVATE UTILITIES /////////////
64 ****************************************************************************/
65
66 static void AddMsg(uint vmid, int x, int y, Key key)
67 {
68    messages.Add(Message { vmid = vmid, x = x, y = y, key = key });
69 }
70
71 default:
72 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
73 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp;
74 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown;
75 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
76 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove;
77 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick;
78 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown;
79 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp;
80 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick;
81 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown;
82 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp;
83 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick;
84 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown;
85 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp;
86 private:
87
88 static void AddMessagesToQueue(int ch)
89 {
90    Key key = 0;
91    Key keyFlags = 0;
92    int ch1;
93 #ifdef NCURSES_VERSION
94    MEVENT event;
95 #endif
96
97    //timeout(0);
98    for(; ch != ERR; ch = getch())
99    {
100       if(ch == 0x1B) //Escaped = true;
101       {
102          ch = getch();
103 #ifdef NCDEBUG
104          fprintf(f, "  \\e: %d\n",ch);
105 #endif
106          if((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
107          {
108             key = characters2Key[ch];
109             keyFlags.alt = true;
110          }
111          else if(ch == '[')
112          {
113             ch = getch();
114 #ifdef NCDEBUG
115             fprintf(f, "   [: %d\n",ch);
116 #endif
117             ch1 = getch();
118 #ifdef NCDEBUG
119             fprintf(f, "   MOD: %c\n",ch1);
120 #endif
121             if(ch == '[')
122             {
123                if(ch1 == 'A') key = f1;
124                else if(ch1 == 'B') key = f2;
125                else if(ch1 == 'C') key = f3;
126                else if(ch1 == 'D') key = f4;
127                else if(ch1 == 'E') key = f5;
128             }
129             else
130             {
131                if(ch1 == '$') keyFlags.shift = true;
132                else if(ch1 == '^') keyFlags.ctrl = true;
133                else if(ch1 == '@') { keyFlags.shift = true; keyFlags.ctrl = true; }
134                else if(ch1 != ERR)
135                   ungetch(ch1);
136
137                switch(ch)
138                {
139                   case '1': case '7': key = home; break;
140                   case '2': key = insert; break;
141                   case '3': key = del; break;
142                   case '4': case '8': key = end; break;
143                   case '5': key = pageUp; break;
144                   case '6': key = pageDown; break;
145                   case 'a': keyFlags.shift = true; case 'A': key = up; break;
146                   case 'b': keyFlags.shift = true; case 'B': key = down; break;
147                   case 'c': keyFlags.shift = true; case 'C': key = right; break;
148                   case 'd': keyFlags.shift = true; case 'D': key = left; break;
149                   case 'k': keyFlags.shift = true; key = tab; break;
150                }
151             }
152             ch = 0;
153          }
154          else if(ch == 'O')
155          {
156             ch = getch();
157 #ifdef NCDEBUG
158             fprintf(f, "   O: %d\n",ch);
159 #endif
160             keyFlags.ctrl = true;
161             switch(ch)
162             {
163                case 'a': key = up; break;
164                case 'b': key = down; break;
165                case 'c': key = right; break;
166                case 'd': key = left; break;
167             }
168             ch = 0;
169          }
170          else
171          {
172             if(ch == ERR)
173             {
174                key = escape;
175                ch = 27;
176             }
177             else
178                ch = 0;
179             /*else
180             {
181                ungetch(ch);
182             }*/
183          }
184       }
185       else if(ch<256)
186       {
187          key = characters2Key[ch];
188          if(!key && ch <= 26)
189          {
190             keyFlags.ctrl = true;
191             key = characters2Key[ch + 'a' - 1];
192             ch += 'a' - 1;
193          }
194       }
195       else
196       {
197          switch(ch)
198          {
199             case KEY_ENTER: key = enter; break;
200             case KEY_BACKSPACE: key = backSpace; break;
201             case KEY_LEFT: key = left; break;
202             case KEY_RIGHT: key = right; break;
203             case KEY_UP: key = up; break;
204             case KEY_DOWN: key = down; break;
205             case KEY_SLEFT: key = left; keyFlags.shift = true; break;
206             case KEY_SRIGHT: key = right; keyFlags.shift = true; break;
207 //               case KEY_SUP: key = up; keyFlags.shift = true; break;
208 //               case KEY_SDOWN: key = down; keyFlags.shift = true; break;
209             case 353: key = tab; keyFlags.shift = true; break;
210             case 383:
211                keyFlags.shift = true;
212             case KEY_DC: key = del; break;
213             case KEY_IC: case KEY_EIC: key = insert; break;
214             case 362:
215             case KEY_HOME: key = home; break;
216             case 335:
217                keyFlags.ctrl = true;
218             case 385:
219             case KEY_END: key = end; break;
220             case KEY_SHOME: key = home; keyFlags.shift = true; break;
221             case KEY_SEND: key = end; keyFlags.shift = true; break;
222             case KEY_NPAGE: key = pageDown; break;
223             case KEY_PPAGE: key = pageUp; break;
224
225             // NCurses Extensions
226 #ifdef NCURSES_VERSION
227             case KEY_RESIZE:
228                resizeterm(LINES, COLS);
229                guiApp.SetDesktopPosition(0,0, COLS * textCellW, LINES * textCellH, true);
230                break;
231             case KEY_MOUSE:
232             {
233                static double lastTime[3];
234                static Point lastPos[3];
235                int x, y;
236                getmouse(&event);
237                x = Min(Max(event.x * textCellW, mouseRange.left), mouseRange.right);
238                y = Min(Max(event.y * textCellH, mouseRange.top), mouseRange.bottom);
239                if(mousePosition.x != x || mousePosition.y != y)
240                {
241                   mousePosition.x = x;
242                   mousePosition.y = y;
243
244                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove, x, y, key);
245                }
246                if(event.bstate & BUTTON1_PRESSED)
247                {
248                   Time time = GetTime();
249                   if(time - lastTime[0] < DBLCLICK_DELAY &&
250                      Abs(mousePosition.x - lastPos[0].x) < DBLCLICK_DELTA &&
251                      Abs(mousePosition.y - lastPos[0].y) < DBLCLICK_DELTA)
252                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick, x, y, keyFlags);
253                   else
254                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, x, y, keyFlags);
255                   lastTime[0] = time;
256                   lastPos[0] = mousePosition;
257                }
258                if(event.bstate & BUTTON1_RELEASED)
259                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp, x, y, keyFlags);
260                if(event.bstate & BUTTON3_PRESSED)
261                {
262                   Time time = GetTime();
263                   if(time - lastTime[2] < DBLCLICK_DELAY &&
264                      Abs(mousePosition.x - lastPos[2].x) < DBLCLICK_DELTA &&
265                      Abs(mousePosition.y - lastPos[2].y) < DBLCLICK_DELTA)
266                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick, x, y, keyFlags);
267                   else
268                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown, x, y, keyFlags);
269                   lastTime[2] = time;
270                   lastPos[2] = mousePosition;
271                }
272                if(event.bstate & BUTTON3_RELEASED)
273                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp, x, y, keyFlags);
274                if(event.bstate & BUTTON2_PRESSED)
275                {
276                   Time time = GetTime();
277                   if(time - lastTime[1] < DBLCLICK_DELAY &&
278                      Abs(mousePosition.x - lastPos[1].x) < DBLCLICK_DELTA &&
279                      Abs(mousePosition.y - lastPos[1].y) < DBLCLICK_DELTA)
280                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick, x, y, keyFlags);
281                   else
282                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown, x, y, keyFlags);
283                   lastTime[1] = time;
284                   lastPos[1] = mousePosition;
285                }
286                if(event.bstate & BUTTON2_RELEASED)
287                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp,  x, y, keyFlags);
288                break;
289             }
290 #endif
291          }
292          if(ch >= KEY_F(1) && ch <= KEY_F(12))
293             key = (Key)f1 + ch-KEY_F(1);
294          if(ch >= KEY_F(13) && ch <= KEY_F(24))
295          {
296             key = (Key)f1 + ch-KEY_F(13);
297             keyFlags.shift = true;
298          }
299          ch = 0;
300       }
301       if(key)
302       {
303          key |= keyFlags;
304          AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown, ch, 1, key);
305          AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp, ch, 0, key);
306       }
307    }
308 }
309
310 static uint NCursesThread(Thread thread)
311 {
312    while(!ncursesTerminate)
313    {
314       int ch;
315       ncursesMutex.Wait();
316       ch = getch();
317       if(ch != ERR)
318       {
319          AddMessagesToQueue(ch);
320          guiApp.SignalEvent();
321       }
322       ncursesMutex.Release();
323       if(ch == ERR)
324          Sleep(TIMEOUT / 1000.0);
325    }
326    return 0;
327 }
328
329 /****************************************************************************
330    /// DRIVER IMPLEMENTATION /////////////
331 ****************************************************************************/
332 class NCursesInterface : Interface
333 {
334    class_property(name) = "NCurses";
335
336    // --- User Interface System ---
337    bool Initialize()
338    {
339       static byte colorMap[8] = {0,4,2,6,1,5,3,7};
340
341       byte c = 0,f,b;
342       initscr();
343
344       SetLoggingMode(buffer, null);
345       if((bool)has_colors())
346       {
347          start_color();
348          for(b=0; b<8; b++)
349             for(f=0; f<8; f++)
350                init_pair(c++,colorMap[f],colorMap[b]);
351       }
352
353       printf( "\033(U\017");
354       fflush(stdout);
355       intrflush(stdscr, false);
356       nonl();
357       curs_set(false);
358       keypad(stdscr, true);
359    #ifdef NCURSES_VERSION
360       ESCDELAY = 0;
361       mousemask(REPORT_MOUSE_POSITION |
362                 BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON1_DOUBLE_CLICKED |
363                 BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON2_DOUBLE_CLICKED |
364                 BUTTON3_PRESSED | BUTTON3_RELEASED | BUTTON3_DOUBLE_CLICKED, null);
365       mouseinterval(0);
366    #endif
367       idlok(stdscr, false);
368       idcok(stdscr, false);
369       clearok(stdscr, false);
370       scrollok(stdscr, false);
371       cbreak();
372       caretVisible = false;
373       leaveok(stdscr, true);
374       timeout(0);
375       noecho();
376
377         ncursesMutex = Mutex { };
378
379       ncursesTerminate = false;
380
381       ncursesThread = Thread { };
382       incref ncursesThread;
383       ncursesThread.Main = NCursesThread;
384       ncursesThread.Create();
385
386       ncursesMutex.Wait();
387       return true;
388    }
389
390    void Terminate()
391    {
392       ncursesTerminate = true;
393       ncursesMutex.Release();
394       ncursesThread.Wait();
395       delete ncursesThread;
396       endwin();
397       delete ncursesMutex;
398       ClearClipboard();
399    }
400
401    bool ProcessInput(bool processAll)
402    {
403       Message msg;
404       bool result = false;
405
406       // Process messages
407       while(!ncursesTerminate && (msg = messages.first))
408       {
409          result = true;
410          messages.Remove(msg);
411          if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown || msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp)
412             nCursesDesktop.KeyMessage(msg.vmid, msg.key, (char)msg.x);
413          else
414          {
415             if(nCursesDesktop.MouseMessage(msg.vmid, msg.x, msg.y, (Modifiers *)&msg.key, false, true))
416             {
417                if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick)
418                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
419                else if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick)
420                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
421                else if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick)
422                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
423             }
424          }
425          delete msg;
426
427          if(processAll && ncursesMutex)
428          {
429             ncursesMutex.Release();
430             ncursesMutex.Wait();
431          }
432          result = true;
433       }
434       if(ncursesTerminate)
435          messages.Free(null);
436       else if(caretVisible)
437       {
438          leaveok(stdscr, false);
439          move(caretY,caretX);
440          refresh();
441          curs_set(true);
442       }
443       return result;
444    }
445
446    void Wait()
447    {
448       ncursesMutex.Release();
449       guiApp.WaitEvent();
450       ncursesMutex.Wait();
451    }
452
453    void Lock(Window window)
454    {
455
456    }
457
458    void Unlock(Window window)
459    {
460
461    }
462
463    const char ** GraphicsDrivers(int * numDrivers)
464    {
465       static const char *graphicsDrivers[] = { "NCurses" };
466       *numDrivers = sizeof(graphicsDrivers) / sizeof(char *);
467       return (const char **)graphicsDrivers;
468    }
469
470    void GetCurrentMode(bool * fullScreen, Resolution * resolution, PixelFormat * colorDepth, int * refreshRate)
471    {
472       *fullScreen = true;
473       *colorDepth = pixelFormatText;
474    }
475
476    void EnsureFullScreen(bool *fullScreen)
477    {
478       *fullScreen = true;
479    }
480
481    void OffsetWindow(Window window, int * x, int * y)
482    {
483
484    }
485
486    bool ScreenMode(bool fullScreen, int resolution, int colorDepth, int refreshRate, bool * textMode)
487    {
488       if(fullScreen)
489       {
490          guiApp.SetDesktopPosition(0,0, COLS * textCellW, LINES * textCellH, false);
491          *textMode = true;
492          mouseRange.right = MAXINT;
493          mouseRange.bottom = MAXINT;
494            return true;
495       }
496       return false;
497    }
498
499    // --- Window Creation ---
500    void * CreateRootWindow(Window window)
501    {
502       nCursesDesktop = window;
503       return (void *) stdscr;
504    }
505
506    void DestroyRootWindow(void * windowHandle)
507    {
508    }
509
510    // -- Window manipulation ---
511
512    void SetRootWindowCaption(void * windowHandle, const char * name)
513    {
514    }
515
516    void PositionRootWindow(void * windowHandle, int x, int y, int w, int h, bool move, bool resize)
517    {
518    }
519
520    void OrderRootWindow(void * windowHandle, bool topMost)
521    {
522    }
523
524    void SetRootWindowColor(Window window)
525    {
526    }
527
528    void UpdateRootWindow(void * windowHandle)
529    {
530    }
531
532    void SetRootWindowState(Window window, int state, bool visible)
533    {
534
535    }
536
537    void ActivateRootWindow(void * windowHandle)
538    {
539    }
540
541    // --- Mouse-based window movement ---
542
543    void StartMoving(void * windowHandle, int x, int y, bool fromKeyBoard)
544    {
545    }
546
547    void StopMoving(void * windowHandle)
548    {
549    }
550
551    // -- Mouse manipulation ---
552
553    void GetMousePosition(int *x, int *y)
554    {
555       *x = mousePosition.x;
556       *y = mousePosition.y;
557    }
558
559    void SetMousePosition(int x, int y)
560    {
561       mousePosition.x = x;
562       mousePosition.y = y;
563    }
564
565    void SetMouseRange(Window window, Box box)
566    {
567       if(box != null)
568       {
569          mouseRange = box;
570       }
571       else
572       {
573          mouseRange.left = mouseRange.top = 0;
574          mouseRange.right = mouseRange.bottom = MAXINT;
575       }
576    }
577
578    void SetMouseCapture(void * windowHandle)
579    {
580
581    }
582
583    // -- Mouse cursor ---
584
585    void SetMouseCursor(Window window, int cursor)
586    {
587
588    }
589
590    // --- Caret ---
591
592    void SetCaret(int x, int y, int size)
593    {
594       if(size)
595       {
596          caretX = x / textCellW;
597          caretY = y / textCellH;
598          caretVisible = true;
599       }
600       else
601          caretVisible = false;
602    }
603
604    // --- Clipboard manipulation ---
605
606    void ClearClipboard()
607    {
608       delete clipBoardData;
609    }
610
611    bool AllocateClipboard(ClipBoard clipBoard, uint size)
612    {
613       bool result = false;
614       if((clipBoard.text = new char[size]))
615          result = true;
616       return result;
617    }
618
619    bool SaveClipboard(ClipBoard clipBoard)
620    {
621       bool result = false;
622       if(clipBoard.text)
623       {
624          delete clipBoardData;
625          clipBoardData = clipBoard.text;
626          clipBoard.text = null;
627          result = true;
628       }
629       return result;
630    }
631
632    bool LoadClipboard(ClipBoard clipBoard)
633    {
634       bool result = false;
635       if(clipBoardData)
636       {
637          clipBoard.text = clipBoardData;
638          result = true;
639       }
640       return result;
641    }
642
643    void UnloadClipboard(ClipBoard clipBoard)
644    {
645
646    }
647
648    // --- State based input ---
649
650    bool AcquireInput(void * windowHandle, bool state)
651    {
652       return false;
653    }
654
655    bool GetMouseState(MouseButtons * buttons, int * x, int * y)
656    {
657       bool result = false;
658
659       return result;
660    }
661
662    bool GetJoystickState(int device, Joystick joystick)
663    {
664       bool result = false;
665
666       return result;
667    }
668
669    bool GetKeyState(Key key)
670    {
671       return false;
672    }
673 }
674
675 #endif