private void runLinux(Control control) { this.mainControl = control; if (userCanvasSize.IsEmpty) { // Create physical canvas with actual terminal size winsize ws = Libc.GetTerminalSize(isDarwin); canvas = new PhysicalCanvas(ws.ws_col, ws.ws_row); } else { canvas = new PhysicalCanvas(userCanvasSize.Width, userCanvasSize.Height); } renderer.Canvas = canvas; renderer.RootElementRect = userRootElementRect.IsEmpty ? new Rect(canvas.Size) : userRootElementRect; renderer.RootElement = mainControl; // mainControl.Invalidate(); // Terminal initialization sequence // This is magic workaround to avoid messing up terminal after program finish // The bug is described at https://bugzilla.xamarin.com/show_bug.cgi?id=15118 bool ignored = Console.KeyAvailable; IntPtr stdscr = NCurses.initscr(); NCurses.cbreak(); NCurses.noecho(); NCurses.nonl(); NCurses.intrflush(stdscr, false); NCurses.keypad(stdscr, true); NCurses.start_color(); HideCursor(); try { renderer.UpdateLayout( ); renderer.FinallyApplyChangesToCanvas( ); termkeyHandle = LibTermKey.termkey_new(0, TermKeyFlag.TERMKEY_FLAG_SPACESYMBOL); // Setup the input mode Console.Write("\x1B[?1002h"); pollfd fd = new pollfd( ); fd.fd = 0; fd.events = POLL_EVENTS.POLLIN; pollfd[] fds = new pollfd[2]; fds[0] = fd; fds[1] = new pollfd( ); int pipeResult = Libc.pipe(pipeFds); if (pipeResult == -1) { throw new InvalidOperationException("Cannot create self-pipe."); } fds[1].fd = pipeFds[0]; fds[1].events = POLL_EVENTS.POLLIN; try { #if !WIN32 // Catch SIGWINCH to handle terminal resizing UnixSignal[] signals = new UnixSignal [] { new UnixSignal(Signum.SIGWINCH) }; Thread signal_thread = new Thread(delegate() { while (true) { // Wait for a signal to be delivered int index = UnixSignal.WaitAny(signals, -1); Signum signal = signals [index].Signum; Libc.writeInt64(pipeFds[1], 2); } } ); signal_thread.IsBackground = false; signal_thread.Start(); #endif TermKeyKey key = new TermKeyKey( ); // this.running = true; this.mainThreadId = Thread.CurrentThread.ManagedThreadId; // int nextwait = -1; while (true) { int pollRes = Libc.poll(fds, 2, nextwait); if (pollRes == 0) { if (nextwait == -1) { throw new InvalidOperationException("Assertion failed."); } if (TermKeyResult.TERMKEY_RES_KEY == LibTermKey.termkey_getkey_force(termkeyHandle, ref key)) { processLinuxInput(key); } } if (pollRes == -1) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != Libc.EINTR) { throw new InvalidOperationException(string.Format("poll() returned with error code {0}", errorCode)); } } if (fds[1].revents != POLL_EVENTS.NONE) { UInt64 u; Libc.readInt64(fds[1].fd, out u); if (u == 1) { // Exit from application #if !WIN32 signal_thread.Abort(); #endif break; } if (u == 2) { // Get new term size and process appropriate INPUT_RECORD event INPUT_RECORD inputRecord = new INPUT_RECORD( ); inputRecord.EventType = EventType.WINDOW_BUFFER_SIZE_EVENT; winsize ws = Libc.GetTerminalSize(isDarwin); inputRecord.WindowBufferSizeEvent.dwSize.X = ( short )ws.ws_col; inputRecord.WindowBufferSizeEvent.dwSize.Y = ( short )ws.ws_row; processInputEvent(inputRecord); } if (u == 3) { // It is signal from async actions invocation stuff } } if ((fds[0].revents & POLL_EVENTS.POLLIN) == POLL_EVENTS.POLLIN || (fds[0].revents & POLL_EVENTS.POLLHUP) == POLL_EVENTS.POLLHUP || (fds[0].revents & POLL_EVENTS.POLLERR) == POLL_EVENTS.POLLERR) { LibTermKey.termkey_advisereadable(termkeyHandle); } TermKeyResult result = (LibTermKey.termkey_getkey(termkeyHandle, ref key)); while (result == TermKeyResult.TERMKEY_RES_KEY) { processLinuxInput(key); result = (LibTermKey.termkey_getkey(termkeyHandle, ref key)); } if (result == TermKeyResult.TERMKEY_RES_AGAIN) { nextwait = LibTermKey.termkey_get_waittime(termkeyHandle); } else { nextwait = -1; } while (true) { bool anyInvokeActions = isAnyInvokeActions( ); bool anyRoutedEvent = !EventManager.IsQueueEmpty( ); bool anyLayoutToRevalidate = renderer.AnyControlInvalidated; if (!anyInvokeActions && !anyRoutedEvent && !anyLayoutToRevalidate) { break; } EventManager.ProcessEvents(); processInvokeActions( ); renderer.UpdateLayout( ); } renderer.FinallyApplyChangesToCanvas( ); } } finally { LibTermKey.termkey_destroy(termkeyHandle); Libc.close(pipeFds[0]); Libc.close(pipeFds[1]); Console.Write("\x1B[?1002l"); } } finally { // Restore cursor visibility before exit ShowCursor( ); NCurses.endwin( ); } renderer.RootElement = null; }
private void processLinuxInput(TermKeyKey key) { // If any special button has been pressed (Tab, Enter, etc) // we should convert its code to INPUT_RECORD.KeyEvent // Because INPUT_RECORD.KeyEvent depends on Windows' scan codes, // we convert codes retrieved from LibTermKey to Windows virtual scan codes // In the future, this logic may be changed (for example, both Windows and Linux // raw codes can be converted into ConsoleFramework's own abstract enum) if (key.type == TermKeyType.TERMKEY_TYPE_KEYSYM) { INPUT_RECORD inputRecord = new INPUT_RECORD(); inputRecord.EventType = EventType.KEY_EVENT; inputRecord.KeyEvent.bKeyDown = true; inputRecord.KeyEvent.wRepeatCount = 1; switch (key.code.sym) { case TermKeySym.TERMKEY_SYM_TAB: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Tab; break; case TermKeySym.TERMKEY_SYM_ENTER: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Return; break; // in gnome-terminal it is backspace by default // (see default compatibility settings in Profile's settings) case TermKeySym.TERMKEY_SYM_DEL: case TermKeySym.TERMKEY_SYM_BACKSPACE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Back; break; case TermKeySym.TERMKEY_SYM_DELETE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Delete; break; case TermKeySym.TERMKEY_SYM_HOME: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Home; break; case TermKeySym.TERMKEY_SYM_END: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.End; break; case TermKeySym.TERMKEY_SYM_PAGEUP: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Prior; break; case TermKeySym.TERMKEY_SYM_PAGEDOWN: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Next; break; case TermKeySym.TERMKEY_SYM_SPACE: inputRecord.KeyEvent.UnicodeChar = ' '; inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Space; break; case TermKeySym.TERMKEY_SYM_ESCAPE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Escape; break; case TermKeySym.TERMKEY_SYM_INSERT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Insert; break; case TermKeySym.TERMKEY_SYM_UP: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Up; break; case TermKeySym.TERMKEY_SYM_DOWN: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Down; break; case TermKeySym.TERMKEY_SYM_LEFT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Left; break; case TermKeySym.TERMKEY_SYM_RIGHT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Right; break; default: throw new NotSupportedException("Not supported keyboard code detected: " + key.code.sym); } inputRecord.KeyEvent.dwControlKeyState = 0; if ((key.modifiers & 4) == 4) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_CTRL_PRESSED; } if ((key.modifiers & 2) == 2) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_ALT_PRESSED; } processInputEvent(inputRecord); } else if (key.type == TermKeyType.TERMKEY_TYPE_UNICODE) { byte[] data = new byte[7]; data[0] = key.utf8_0; data[1] = key.utf8_1; data[2] = key.utf8_2; data[3] = key.utf8_3; data[4] = key.utf8_4; data[5] = key.utf8_5; data[6] = key.utf8_6; string d = System.Text.Encoding.UTF8.GetString(data); char unicodeCharacter = d[0]; INPUT_RECORD inputRecord = new INPUT_RECORD(); inputRecord.EventType = EventType.KEY_EVENT; inputRecord.KeyEvent.bKeyDown = true; inputRecord.KeyEvent.wRepeatCount = 1; inputRecord.KeyEvent.UnicodeChar = unicodeCharacter; inputRecord.KeyEvent.dwControlKeyState = 0; if (char.IsLetterOrDigit(unicodeCharacter)) { if (char.IsDigit(unicodeCharacter)) { inputRecord.KeyEvent.wVirtualKeyCode = (VirtualKeys)(unicodeCharacter - '0' + (int)VirtualKeys.N0); } else { char lowercased = char.ToLowerInvariant(unicodeCharacter); // Only english characters can be converted to VirtualKeys if (lowercased >= 'a' && lowercased <= 'z') { inputRecord.KeyEvent.wVirtualKeyCode = (VirtualKeys)(lowercased - 'a' + (int)VirtualKeys.A); } } } if ((key.modifiers & 4) == 4) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_CTRL_PRESSED; } if ((key.modifiers & 2) == 2) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_ALT_PRESSED; } // todo : remove hardcoded exit combo after testing if (unicodeCharacter == 'd' && key.modifiers == 4) { Exit(); } processInputEvent(inputRecord); // } else if (key.type == TermKeyType.TERMKEY_TYPE_MOUSE) { TermKeyMouseEvent ev; int button; int line, col; LibTermKey.termkey_interpret_mouse(termkeyHandle, ref key, out ev, out button, out line, out col); // INPUT_RECORD inputRecord = new INPUT_RECORD(); inputRecord.EventType = EventType.MOUSE_EVENT; if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_PRESS || ev == TermKeyMouseEvent.TERMKEY_MOUSE_RELEASE) { inputRecord.MouseEvent.dwEventFlags = MouseEventFlags.PRESSED_OR_RELEASED; } if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_DRAG) { inputRecord.MouseEvent.dwEventFlags = MouseEventFlags.MOUSE_MOVED; } inputRecord.MouseEvent.dwMousePosition = new COORD((short)(col - 1), (short)(line - 1)); if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_RELEASE) { inputRecord.MouseEvent.dwButtonState = 0; } else if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_DRAG || ev == TermKeyMouseEvent.TERMKEY_MOUSE_PRESS) { if (1 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.FROM_LEFT_1ST_BUTTON_PRESSED; } else if (2 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.FROM_LEFT_2ND_BUTTON_PRESSED; } else if (3 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.RIGHTMOST_BUTTON_PRESSED; } } // processInputEvent(inputRecord); } }
public static void Main(string[] args) { Thread thread = new Thread(new ThreadStart(() => { Thread.Sleep(TimeSpan.FromSeconds(5)); Console.WriteLine("Message from thread"); int res = LibTermKey.writeInt64(eventfd, 1); Console.WriteLine("write(1) returned {0}\n", res); if (res == -1) { int lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); Console.WriteLine("Last error is {0}\n", lastError); } })); thread.IsBackground = true; thread.Start(); IntPtr handle = LibTermKey.termkey_new(0, TermKeyFlag.TERMKEY_FLAG_SPACESYMBOL); Console.Write("\x1B[?1002h"); pollfd fd = new pollfd(); fd.fd = 0; fd.events = POLL_EVENTS.POLLIN; pollfd[] fds = new pollfd[2]; fds[0] = fd; fds[1] = new pollfd(); eventfd = LibTermKey.eventfd(0, EVENTFD_FLAGS.EFD_CLOEXEC); if (eventfd == -1) { Console.WriteLine("Cannot create eventfd\n"); int lastError = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); Console.WriteLine("Last error is {0}\n", lastError); } fds[1].fd = eventfd; fds[1].events = POLL_EVENTS.POLLIN; TermKeyKey key = new TermKeyKey(); while (true) { int pollRes = LibTermKey.poll(fds, 2, -1); if (0 == pollRes) { // timed out Console.WriteLine("Timed out"); if (LibTermKey.termkey_getkey_force(handle, ref key) == TermKeyResult.TERMKEY_RES_KEY) { Console.WriteLine("got TERMKEY_RES_KEY"); } } else if (-1 == pollRes) { int errorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); Console.WriteLine(string.Format("ErrorCode = {0}", errorCode)); } Console.WriteLine(string.Format("PollRes is {0}", pollRes)); for (int i = 0; i < 2; i++) { if (fds[i].revents != POLL_EVENTS.NONE) { if (i == 1) { UInt64 u; LibTermKey.readInt64(fds[i].fd, out u); Console.WriteLine("Readed eventfd counter : {0}\n", u); } } } if ((fds[0].revents & POLL_EVENTS.POLLIN) == POLL_EVENTS.POLLIN || (fds[0].revents & POLL_EVENTS.POLLHUP) == POLL_EVENTS.POLLHUP || (fds[0].revents & POLL_EVENTS.POLLERR) == POLL_EVENTS.POLLERR) { // todo : log return value LibTermKey.termkey_advisereadable(handle); } TermKeyResult result; while ((result = LibTermKey.termkey_getkey(handle, ref key)) == TermKeyResult.TERMKEY_RES_KEY) { Console.WriteLine("Received some key."); string descr = String.Format("Type : {0} Modifiers: {1} Utf8 bytes: {2}{3}{4}{5}{6}{7}{8}", key.type, key.modifiers, key.utf8_0, key.utf8_1, key.utf8_2, key.utf8_3, key.utf8_4, key.utf8_5, key.utf8_6); //dump the retrieved structure //byte[] buffer = new byte[30]; //IntPtr nativeBuffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(30); //System.Runtime.InteropServices.Marshal.StructureToPtr(key, nativeBuffer, false); //System.Runtime.InteropServices.Marshal.Copy(nativeBuffer, buffer, 0, 30); //for (int i = 0; i < 30; i++ ) { // Console.Write("{0} ", buffer[i]); // if ((i + 1) % 10 == 0) // Console.WriteLine(); //} //System.Runtime.InteropServices.Marshal.FreeHGlobal(nativeBuffer); Console.WriteLine(descr); if (key.type == TermKeyType.TERMKEY_TYPE_UNICODE) { byte[] data = new byte[7]; data[0] = key.utf8_0; data[1] = key.utf8_1; data[2] = key.utf8_2; data[3] = key.utf8_3; data[4] = key.utf8_4; data[5] = key.utf8_5; data[6] = key.utf8_6; string d = System.Text.Encoding.UTF8.GetString(data); Console.WriteLine(String.Format("Unicode symbol : {0}", d)); } else if (key.type == TermKeyType.TERMKEY_TYPE_MOUSE) { TermKeyMouseEvent ev; int button; int line, col; LibTermKey.termkey_interpret_mouse(handle, ref key, out ev, out button, out line, out col); Console.WriteLine("MouseEvent : {0} (button {1}) at {2}:{3}", ev, button, line, col); } } } LibTermKey.termkey_destroy(handle); LibTermKey.close(eventfd); }