1 module scone.input.os.posix.posix_input;
2 
3 version (Posix)
4 {
5     import core.sys.posix.termios;
6     import core.sys.posix.unistd : STDOUT_FILENO;
7     import scone.input.keyboard_event : KeyboardEvent;
8     import scone.input.scone_control_key : SCK;
9     import scone.input.scone_key : SK;
10     import scone.input.os.standard_input : StandardInput;
11     import scone.input.os.posix.background_thread;
12     import scone.input.os.posix.locale.input_map : InputMap;
13     import scone.input.os.posix.locale.locale : Locale;
14     import std.concurrency : spawn, Tid, thisTid, send, receiveTimeout, ownerTid;
15     import std.conv : text;
16     import std.datetime : msecs;
17 
18     extern (C)
19     {
20         void cfmakeraw(termios* termios_p);
21     }
22 
23     class PosixInput : StandardInput
24     {
25         void initialize()
26         {
27             this.setInputMapping();
28             this.enableRawInput();
29             this.startPollingInput();
30         }
31 
32         void deinitialize()
33         {
34             this.resetTerminalState();
35         }
36 
37         uint[] retreiveInputSequence()
38         {
39             uint[] sequence;
40 
41             while (receiveTimeout(5.msecs, (uint code) { sequence ~= code; }))
42             {
43                 // continiously repeat until no code is recieved within 5 milliseconds
44             }
45 
46             return sequence;
47         }
48 
49         KeyboardEvent[] latestKeyboardEvents()
50         {
51             auto sequence = this.retreiveInputSequence();
52 
53             //todo: returns null here. should this logic be here or in `inputMap.keyboardEventsFromSequence(sequence)` instead?
54             if (sequence.length == 0)
55             {
56                 return null;
57             }
58 
59             // conversion to input events. refactor whole (winodws+posix) code to use keypresses instead of input events?
60             KeyboardEvent[] keyboardEvents = [];
61             foreach (KeyboardEvent keypress; inputMap.keyboardEventsFromSequence(sequence))
62             {
63                 keyboardEvents ~= KeyboardEvent(keypress.key, keypress.controlKey);
64             }
65 
66             return keyboardEvents;
67         }
68 
69         private void startPollingInput()
70         {
71             // begin polling
72             spawn(&pollKeyboardEvent);
73         }
74 
75         // unsure when to use this.
76         // on mac this shows a small key icon, used when entering passwords
77         private void setInputEcho(in bool echo)
78         {
79             termios termInfo;
80             tcgetattr(STDOUT_FILENO, &termInfo);
81 
82             if (echo)
83             {
84                 termInfo.c_lflag |= ECHO;
85             }
86             else
87             {
88                 termInfo.c_lflag &= ~ECHO;
89             }
90 
91             tcsetattr(STDOUT_FILENO, TCSADRAIN, &termInfo);
92         }
93 
94         private void setInputMapping()
95         {
96             Locale locale = new Locale();
97             this.inputMap = new InputMap(locale.systemLocaleSequences);
98         }
99 
100         private void enableRawInput()
101         {
102             // store the state of the terminal
103             tcgetattr(1, &originalTerminalState);
104 
105             // enable raw input
106             termios newTerminalState = originalTerminalState;
107             cfmakeraw(&newTerminalState);
108             tcsetattr(STDOUT_FILENO, TCSADRAIN, &newTerminalState);
109         }
110 
111         private void resetTerminalState()
112         {
113             tcsetattr(STDOUT_FILENO, TCSADRAIN, &originalTerminalState);
114         }
115 
116         private InputMap inputMap;
117         private termios originalTerminalState;
118     }
119 }