private void runWindows(Control control) { this.mainControl = control; // stdInputHandle = Win32.GetStdHandle(StdHandleType.STD_INPUT_HANDLE); stdOutputHandle = Win32.GetStdHandle(StdHandleType.STD_OUTPUT_HANDLE); IntPtr[] handles = new[] { exitWaitHandle.SafeWaitHandle.DangerousGetHandle(), stdInputHandle, invokeWaitHandle.SafeWaitHandle.DangerousGetHandle( ) }; // Set console mode to enable mouse and window resizing events const uint ENABLE_WINDOW_INPUT = 0x0008; const uint ENABLE_MOUSE_INPUT = 0x0010; uint consoleMode; Win32.GetConsoleMode(stdInputHandle, out consoleMode); Win32.SetConsoleMode(stdInputHandle, consoleMode | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT); // Get console screen buffer size CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo; Win32.GetConsoleScreenBufferInfo(stdOutputHandle, out screenBufferInfo); // Set Canvas size to current console window size (not to whole buffer size) savedWindowRect = new Rect(new Point(Console.WindowLeft, Console.WindowTop), new Size(Console.WindowWidth, Console.WindowHeight)); CanvasSize = new Size(savedWindowRect.Width, savedWindowRect.Height); canvas = userCanvasSize.IsEmpty ? new PhysicalCanvas(screenBufferInfo.dwSize.X, screenBufferInfo.dwSize.Y, stdOutputHandle) : new PhysicalCanvas(userCanvasSize.Width, userCanvasSize.Height, stdOutputHandle); renderer.Canvas = canvas; // Fill the canvas by default renderer.RootElementRect = userRootElementRect.IsEmpty ? new Rect(new Point(0, 0), canvas.Size) : userRootElementRect; renderer.RootElement = mainControl; // mainControl.Invalidate(); renderer.UpdateLayout(); renderer.FinallyApplyChangesToCanvas( ); // Initially hide the console cursor HideCursor(); this.running = true; this.mainThreadId = Thread.CurrentThread.ManagedThreadId; // while (true) { // 100 ms instead of Win32.INFINITE to check console window Zoomed and Iconic // state periodically (because if user presses Maximize/Restore button // there are no input event generated). uint waitResult = Win32.WaitForMultipleObjects(3, handles, false, 100); if (waitResult == 0) { break; } if (waitResult == 1) { processInput(); } if (waitResult == 2) { // Do nothing special - because invokeActions will be invoked in loop anyway } // If we received WAIT_TIMEOUT - check window Zoomed and Iconic state // and correct buffer size and console window size if (waitResult == 0x00000102) { IntPtr consoleWindow = getConsoleWindowHwnd( ); bool isZoomed = Win32.IsZoomed(consoleWindow); bool isIconic = Win32.IsIconic(consoleWindow); if (maximized != isZoomed && !isIconic) { if (isZoomed) { Maximize(); } else { Restore(); } } if (!maximized) { savedWindowRect = new Rect(new Point(Console.WindowLeft, Console.WindowTop), new Size(Console.WindowWidth, Console.WindowHeight)); } } // WAIT_FAILED if (waitResult == 0xFFFFFFFF) { throw new InvalidOperationException("Invalid wait result of WaitForMultipleObjects."); } 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( ); } // Restore cursor visibility before exit ShowCursor(); // Restore console mode before exit Win32.SetConsoleMode(stdInputHandle, consoleMode); renderer.RootElement = null; // todo : restore attributes of console output }
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; }