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