Skip to content

Instantly share code, notes, and snippets.

@mulle-nat
Created October 29, 2023 13:42
Show Gist options
  • Save mulle-nat/54115d479a99591e8b3cef29e5023c34 to your computer and use it in GitHub Desktop.
Save mulle-nat/54115d479a99591e8b3cef29e5023c34 to your computer and use it in GitHub Desktop.
Run a curses app like btop from another unix process in a pty for educational purposes
//
// terminal.c
//
// Copyright (C) 2023 Nat!, Mulle kybernetiK.
// All rights reserved.
//
// Coded by Nat!
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// Neither the name of Mulle kybernetiK nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <termios.h>
#include <fcntl.h>
#ifdef __linux__
# include <pty.h>
#else
# ifdef __APPLE__
# include <util.h>
# else
# include <libutil.h> // need -lutil
# endif
#endif
static struct pty
{
volatile int winsize_changed;
struct termios original;
} global_info;
static void restore_terminal( struct termios *p_original)
{
// Reset the terminal to its previous state
tcsetattr( STDIN_FILENO, TCSANOW, p_original);
}
static void reset_terminal( struct termios *p_original)
{
if( ! getenv( "NO_TERMINAL_RESET"))
{
// Reset the terminal to its previous state
tcsetattr( STDIN_FILENO, TCSANOW, p_original);
// Send the ANSI escape sequence for clear screen and reset
write( 1, "\033[2J\033c", 6);
// Exit the program with the appropriate exit code
}
}
static void handle_resize( int sig)
{
global_info.winsize_changed = 1;
}
static void handle_signal(int signum)
{
reset_terminal( &global_info.original);
exit( 128 + signum); // 128 + SIGTERM
}
static void pty_install_signals( void)
{
// signal handling, so we can react to window changes
// and make a nice reset if btop gets CTRL-C
signal( SIGINT, handle_signal);
signal( SIGTERM, handle_signal);
signal( SIGWINCH, handle_resize);
}
static int pty_io_loop( pid_t pid, int master)
{
char buf[ 256];
fd_set readfds, writefds;
sigset_t sigmask;
ssize_t n;
struct termios attr;
struct winsize w;
struct winsize ws = { 0 };
// int change;
// Set some more terminal attributes
tcgetattr( 0, &global_info.original);
attr = global_info.original;
attr.c_lflag &= ~ECHO; // no half-duplex
attr.c_lflag &= ~ICANON; // When ICANON is set, the terminal buffers a line at a time, and input is only available to the program when one of the line delimiters (usually newline or Enter) is typed.
tcsetattr( 0, TCSANOW, &attr);
global_info.winsize_changed = 1;
for(;;)
{
if( global_info.winsize_changed) // get current winsize and copy
{
ioctl( 0, TIOCGWINSZ, &w);
// only do this if there is a real change
if( ws.ws_row != w.ws_row || ws.ws_col != w.ws_col)
{
// Set the window size of the pseudo-terminal
ws.ws_row = w.ws_row;
ws.ws_col = w.ws_col;
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
if( ioctl( master, TIOCSWINSZ, &ws) == -1)
{
perror("ioctl");
return( 1);
}
// forward signal to child
kill( pid, SIGWINCH);
}
global_info.winsize_changed = 0;
}
FD_ZERO( &readfds);
FD_ZERO( &writefds);
FD_SET( master, &readfds);
FD_SET( 0, &readfds);
sigemptyset( &sigmask);
sigaddset( &sigmask, SIGWINCH);
if( pselect( master + 1, &readfds, &writefds, NULL, NULL, &sigmask) == -1)
{
perror( "select");
return( 1);
}
// change = 0;
if( FD_ISSET( master, &readfds))
{
n = read( master, buf, sizeof(buf));
if( n <= 0)
break;
write( 1, buf, n);
// ++change;
}
if( FD_ISSET( 0, &readfds))
{
n = read( 0, buf, sizeof(buf));
if( n <= 0)
break;
write( master, buf, n);
// ++change;
}
// assert( change); // doesn't get hit when window size changes
}
return( 0);
}
extern char **environ;
pid_t pty_fork_exec( char **argv, char **env, int *p_master)
{
int master, slave;
pid_t pid;
assert( argv[ 0][ 0] == '/');
assert( p_master);
// Open the pseudo-terminal
if( openpty( &master, &slave, NULL, NULL, NULL) == -1)
{
perror( "openpty");
return( -1);
}
// Fork the child process
pid = fork();
if( pid == -1)
{
perror( "fork");
return( -1);
}
if( pid == 0)
{
// Child process
close( master);
// Set the slave as the controlling terminal
if( setsid() == -1)
{
perror("setsid");
exit( 1);
}
if( ioctl( slave, TIOCSCTTY, 0) == -1)
{
perror( "ioctl");
exit( 1);
}
// Redirect the slave to the child's stdin, stdout, and stderr
dup2( slave, 0);
dup2( slave, 1);
dup2( slave, 2);
if( env)
environ = env;
execv( argv[ 0], argv);
perror( "execv");
exit( 1);
}
// Parent process
close( slave);
*p_master = master;
return( pid);
}
int main( int argc, char *argv[])
{
char *child_argv[ 2];
int master;
pid_t pid;
int rc;
// Fork the child process
child_argv[ 0] = "/home/nat/bin/btop";
child_argv[ 1] = NULL;
pid = pty_fork_exec( argc == 1 ? child_argv : &argv[ 1], NULL, &master);
if( pid == -1)
return( 1);
pty_install_signals();
rc = pty_io_loop( pid, master);
restore_terminal( &global_info.original);
return( rc);
}
//
// ,╓≤æKª╨╙╙▓█D"╙╙╙*╨¥W▄╓,
// ╓▄ª╙└╧▓ █ ⁿ▓▓ª╦w
// ╓#╜^*█⌐ `╕ █ ╓▌ `╙%w
// ╓ΦΓ █ ╙⌐ ,█,,,Φ ╓▌ ╙¥▄
// ,▄▀ █▄ █µ,╓╝,╓≤╗≡≡ª╨╨╨¥≡∞╗w╓,ª█, , ]▄▀V,
// ,Φ└ ║"▀▄ ,▄▀╨" "╙¥▓▓ ▄▀ º▄¥,
// ,#▌Φ▓▄╓ Φ `█Q▄M╙ ╙¥▄ ,▄▀ ╙▄⌐` `Φ
// ▄▀ `V,"▀▀¼╓╩└ ╙¥▓╗ , ╙▄
// Φ `¥═▄▀ ╒ ▀▄ ▓
// .▓ ╓▓]V▄H ▓ ,Å ▀, ▀
// █ ,-ª▓╛ `██▀½ "▄ ,╓╓╓╓;╓▄▄▄▄╗▄█, ╙▄ ▀
// █ `██▄ ▓. - Φ█═.~ ,≤█▄▀- ` █└"▀▀▀▀▄╓╓╓╓ ▌ ██▄, █
// ║⌐ " ▀ ▓╨⌐ }▀` ,█ ` █ .█▀▄▄███▄ ▌ ` ▀ ▌
// ▌ ▐M A '┴ ▌ ▀ ╙█▌████▌ ╙▄ ║⌐
// ║⌐ █⌐'║▄ Γr2« ║▀ ▄▄▄ ╙▌▄███ █ ▌
// █] ,▄▐H ██████═ª ╓` ▄█▀▀▀▀█▄ ████ ╓███` `╙╙║█ ▐H █
// █ "% ,▄▀ ║░╠K█═╣███^ ╛╓█▀ ▐█ ,╓╓═ª╙└ █ ╙█▄▐▌▓######▌╫
// █║▄▄▄█▄▄▄║H: "╙▀███▄╓▄,█▌╓▄,,▄∩ ██p$ΦΦ▀▀▀▀▀ΦΦΦR≡≥▄╓ ║▄ ╙j▌` ▄▀╙╗ `╫
// █" ╙▐H-═M ▄███████¥█H "^╙ '█, ,«^`` ` ║▌ ▐M▄▀ "Φ█
// ║ !█▄▄████≤═^`╓█ █▌╓▀╦▄▀█ ╙█⌐ ╓█▌ ▓ ` █
// ▌ ,▄██╫██ ▄█═╜" █▌▐█╣Hj⌐ █ █▌╙¥w, ██▄ ╓▌ ▐M
// ╢"▄▄#▀▀╙╟█Ü███▀`▀█ ║██J█▄▌jH █ ██ ╙▀W▄; ,██ `╙w, █▄ ,╓ █
// █╙ ▐███▌▀█w ▀█ ╠██████j▌║█▌ ▀██▄ `╙▓██▀w, ╙╗█'`"▀█▀╙"`║`
// ╙▌ Φ████████▄██ ███████████ ═██▀W▄, ╓█▀└ ╓▀ " ╙▄ ╓▀
// ╙▄ ╙▀███╙█▄▐██▄,╙▀█████▄▀▄▀▄ ,█M╗ ▀▄██▀╙""*ⁿ═─≈-,,▄▌, ▄▀
// `▌╙µ,▄Φ▀x███▄██████████▀V███▀╙└ ,▄#▌████▀`"*«, .█▀▄▀▄, ▄╛
// ▀Ö█ ;▓████████▄▄█▀▄╙W "V▄██▀▀│▄███▀└ ▀╦ "*═╥▄▀ ║, ║▀▄ ,▓
// ╙W▀ '█████╨▀▀█████████ "▀▀████└ ╙▄ ,▄▀' `▀▀╣H ▐▀▄▀
// ╙▄ ▀▀Γ╓▌ ▄▀ⁿ`╙▀███ ██▌ ╓█R▀▄A█ ▀▀▄▀
// ╙W,Φ╙ ║█ ▄╙╙ª%≡╦██≤≤╗╗≡≡╜▀▀ ╣▌ █╕ ,▄▀
// `▀▄M█┘ ║▀▌ ╫" *Φ▄ █ Φ` ▀▌, ▄M╙
// "▀╦, M ╙▌ ▀ `█W▐▌ wæQ▄▀╙
// ╙▀W╥,╓╣ ╙█║ ╫M █ ,╓▄╝╙`
// ╙╙▀%▄▄▄▌,≤▄_▄≡,▐▓▓▄▄#ª▀"`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment