public void SetSize(int width, int height) { var ws = new winsize { ws_col = (ushort)width, ws_row = (ushort)height }; ioctl(_masterPtyFd, 0x5414, ref ws); }
private void SetWindowSizeCore(int height, int width) { var size = new winsize { ws_col = (ushort)width, ws_row = (ushort)height, }; // TODO: add some error handling, or find a better way to key a reference to the master fd var stream = (UnixFileDescriptorStream)Stdin.BaseStream; int retVal; do { retVal = Libc.ioctl((int)stream.Fd, Libc.TIOCSWINSZ, ref size); } while (Error.ShouldRetrySyscall(retVal)); Error.ThrowExceptionForLastErrorIf(retVal); }
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 static extern int ioctl(int fd, uint cmd, ref winsize ws);
// TODO: restructure this function // For example posix_spawn from chromium seems to have a // maintainable flow: https://chromium.googlesource.com/native_client/nacl-newlib/+/bf66148d14c7fca26b9198dd5dc81e743893bb66/newlib/libc/posix/posix_spawn.c private static unsafe int ForkAndExec( string filename, string[] argv, string[] envp, string cwd, bool useTty, bool redirectStdin, bool redirectStdout, bool redirectStderr, out int stdin, out int stdout, out int stderr ) { byte **argvPtr = null; byte **envpPtr = null; bool success = true; ArrayHelpers.AllocNullTerminatedArray(argv, ref argvPtr); ArrayHelpers.AllocNullTerminatedArray(envp, ref envpPtr); int[] stdInFds = new[] { -1, -1 }; int[] stdOutFds = new[] { -1, -1 }; int[] stdErrFds = new[] { -1, -1 }; int masterFd = -1; int slaveFd = -1; try { int inFd, outFd, errFd; if (filename == null || argv == null || envp == null) { success = false; throw new ArgumentException("Provide the correct arguments"); } if (Libc.access(filename, Libc.X_OK) != 0) { success = false; throw new Exception("The given file is not accessible"); } if (useTty) { var size = new winsize { ws_col = DEFAULT_WIDTH, ws_row = DEFAULT_HEIGHT }; if (Libc.openpty(out masterFd, out slaveFd, IntPtr.Zero, IntPtr.Zero, ref size) == -1) { success = false; throw new Exception("Could not open a new pty"); } } else { try { if (redirectStdin) { CreateCloseOnExecPipe(stdInFds); } if (redirectStdout) { CreateCloseOnExecPipe(stdOutFds); } if (redirectStderr) { CreateCloseOnExecPipe(stdErrFds); } } catch (Exception ex) { success = false; throw ex; } } var pid = Libc.fork(); if (pid < 0) { success = false; Error.ThrowExceptionForLastError(); } if (pid == 0) { if (useTty) { Libc.close(masterFd); Libc.setsid(); if (Libc.ioctl(slaveFd, Libc.TIOCSCTTY) == -1) { success = false; Error.ThrowExceptionForLastError(); } inFd = outFd = errFd = slaveFd; } else { inFd = stdInFds[READ_END_OF_PIPE]; outFd = stdOutFds[WRITE_END_OF_PIPE]; errFd = stdErrFds[WRITE_END_OF_PIPE]; } // TODO: this code is just horrible and the likely hood introducing bugs here is quite high. // I should refactor this asap. But first I would love to get some more tests in place that // could catch any regressions.. if (redirectStdin) { while (Error.ShouldRetrySyscall(Libc.dup2(inFd, Libc.STDIN_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (redirectStdout) { while (Error.ShouldRetrySyscall(Libc.dup2(outFd, Libc.STDOUT_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (redirectStderr) { while (Error.ShouldRetrySyscall(Libc.dup2(errFd, Libc.STDERR_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (!string.IsNullOrEmpty(cwd)) { var ret = Libc.chdir(cwd); if (ret == -1) { success = false; Error.ThrowExceptionForLastError(); } } Libc.execve(filename, argvPtr, envpPtr); // Exec syscall should never return, and thus we should never get here, if we do then exit immediately Libc._exit(Libc.errno != 0 ? Libc.errno : -1); } Libc.close(slaveFd); if (useTty) { stdin = masterFd; stdout = masterFd; stderr = masterFd; } else { stdin = stdInFds[WRITE_END_OF_PIPE]; stdout = stdOutFds[READ_END_OF_PIPE]; stderr = stdErrFds[READ_END_OF_PIPE]; } return(pid); } finally { // Regardless of success or failure, close the parent's copy of the child's end of // any opened pipes. The parent doesn't need them anymore. CloseIfOpen(stdInFds[READ_END_OF_PIPE]); CloseIfOpen(stdOutFds[WRITE_END_OF_PIPE]); CloseIfOpen(stdErrFds[WRITE_END_OF_PIPE]); if (!success) { // Cleanup all open fd CloseIfOpen(masterFd); CloseIfOpen(slaveFd); CloseIfOpen(stdInFds[WRITE_END_OF_PIPE]); CloseIfOpen(stdOutFds[READ_END_OF_PIPE]); CloseIfOpen(stdErrFds[READ_END_OF_PIPE]); } ArrayHelpers.FreeArray(argvPtr, argv.Length); ArrayHelpers.FreeArray(envpPtr, envp.Length); } }
public extern static int ioctl(int fd, int cmd, out winsize argp);