diff --git a/include/tvterm/pty.h b/include/tvterm/pty.h index b0d2497..6ba5b58 100644 --- a/include/tvterm/pty.h +++ b/include/tvterm/pty.h @@ -3,6 +3,8 @@ #include +#include + template class TSpan; class TPoint; @@ -12,11 +14,12 @@ namespace tvterm struct PtyDescriptor { - int fd; + int masterFd; + pid_t clientPid; bool valid() const { - return fd != -1; + return masterFd != -1; } }; @@ -31,7 +34,8 @@ PtyDescriptor createPty( TPoint size, TSpan environment, class PtyMaster { - int fd; + int masterFd; + pid_t clientPid; public: @@ -44,7 +48,8 @@ class PtyMaster }; inline PtyMaster::PtyMaster(PtyDescriptor ptyDescriptor) noexcept : - fd(ptyDescriptor.fd) + masterFd(ptyDescriptor.masterFd), + clientPid(ptyDescriptor.clientPid) { } diff --git a/source/tvterm-core/pty.cc b/source/tvterm-core/pty.cc index c1f34b0..602a2d5 100644 --- a/source/tvterm-core/pty.cc +++ b/source/tvterm-core/pty.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #if __has_include() @@ -29,9 +30,9 @@ PtyDescriptor createPty( TPoint size, TSpan customEnvironm { auto termios = createTermios(); auto winsize = createWinsize(size); - int master_fd; - pid_t child_pid = forkpty(&master_fd, nullptr, &termios, &winsize); - if (child_pid == 0) + int masterFd; + pid_t clientPid = forkpty(&masterFd, nullptr, &termios, &winsize); + if (clientPid == 0) { // Use the default ISIG signal handlers. signal(SIGINT, SIG_DFL); @@ -59,14 +60,14 @@ PtyDescriptor createPty( TPoint size, TSpan customEnvironm // Exit the subprocess without cleaning up resources. _Exit(EXIT_FAILURE); } - else if (child_pid == -1) + else if (clientPid == -1) { char *str = fmtStr("forkpty failed: %s", strerror(errno)); onError(str); delete[] str; return {-1}; } - return {master_fd}; + return {masterFd, clientPid}; } bool PtyMaster::readFromClient(TSpan data, size_t &bytesRead) noexcept @@ -74,17 +75,17 @@ bool PtyMaster::readFromClient(TSpan data, size_t &bytesRead) noexcept bytesRead = 0; if (data.size() > 1) { - ssize_t r = read(fd, &data[0], 1); + ssize_t r = read(masterFd, &data[0], 1); if (r < 0) return false; else if (r > 0) { bytesRead += r; int availableBytes = 0; - if ( ioctl(fd, FIONREAD, &availableBytes) != -1 && + if ( ioctl(masterFd, FIONREAD, &availableBytes) != -1 && availableBytes > 0 ) { - r = read(fd, &data[1], min(availableBytes, data.size() - 1)); + r = read(masterFd, &data[1], min(availableBytes, data.size() - 1)); if (r < 0) return false; bytesRead += r; @@ -99,7 +100,7 @@ bool PtyMaster::writeToClient(TSpan data) noexcept size_t written = 0; while (written < data.size()) { - ssize_t r = write(fd, &data[written], data.size() - written); + ssize_t r = write(masterFd, &data[written], data.size() - written); if (r < 0) return false; written += r; @@ -112,13 +113,23 @@ void PtyMaster::resizeClient(TPoint size) noexcept struct winsize w = {}; w.ws_row = size.y; w.ws_col = size.x; - int rr = ioctl(fd, TIOCSWINSZ, &w); + int rr = ioctl(masterFd, TIOCSWINSZ, &w); (void) rr; } void PtyMaster::disconnect() noexcept { - close(fd); + close(masterFd); + // Send a SIGHUP, then a SIGKILL after a while if the process is not yet + // terminated, like most terminal emulators do. + kill(clientPid, SIGHUP); + sleep(1); + if (waitpid(clientPid, nullptr, WNOHANG) != clientPid) + { + kill(clientPid, SIGKILL); + while( waitpid(clientPid, nullptr, 0) != clientPid && + errno == EINTR ); + } } static struct winsize createWinsize(TPoint size) noexcept diff --git a/source/tvterm-core/termctrl.cc b/source/tvterm-core/termctrl.cc index 56484de..1aaf0c0 100644 --- a/source/tvterm-core/termctrl.cc +++ b/source/tvterm-core/termctrl.cc @@ -112,7 +112,6 @@ TerminalController *TerminalController::create( TPoint size, void TerminalController::shutDown() noexcept { - ptyMaster.disconnect(); { std::lock_guard lock(eventLoop.mutex); eventLoop.terminated = true; @@ -155,8 +154,10 @@ void TerminalController::TerminalEventLoop::runWriterLoop() noexcept condVar.wait_until(lock, currentTimeout); if (terminated) - // The PTY master has already been closed, there's nothing to write. + { + ctrl.ptyMaster.disconnect(); break; + } processEvents(); updateState(updated);