Cześć,
Najpierw trochę kontekstu. Eksploruję sobie obsługę terminala z poziomu C, bo chciałbym bardziej elastycznie używać procesów. Np. skryptowanie Vima to udręka, a gdybym mógł po prostu wcisnąć mu po prostu klawisze, byłoby lżej. Chodzi o to, żeby mieć otwartych kilka procesów (np. vim, gdb, shell) i swobodnie przełączać je między sobą. Tak więc, mówiąc obrazowo coś jak centrala telefoniczna dla strumieni. One shell to rule them all, że tak powiem ;)
Doszedłem, że w tym celu powinienem sobie tworzyć PTY dla każdego procesu, żeby móc w pełni obsłużyć każdy program i potem wewnątrz mojego programu tworzyć połączenia między poszczególnymi procesami. Jestem w trakcie implementowania funkcji podłączania wejcia i wyjścia procesu prosto na konsolę. W tym celu używam termiosa, żeby wyłączyć echo i buforowanie i wtedy mogę po prostu sobie po prostu przepisywać, a po zamknięciu procesu przywracam poprzednie ustawienia termianala zapisane wcześniej.
Kawałek kodu poglądowego:
int thp_writer(void *ctx) {
int fd = *((int*)ctx);
char buff[BUFF_SIZE+1];
buff[BUFF_SIZE] = 0;
int n;
while((n = read(fd, buff, BUFF_SIZE)) > 0) {
//Kawałek używany do debugowania: czyt dalej
//for(int i = 0; i < n; i++)
// if(buff[i] < 0x20) buff[i] = '?';
write(STDOUT_FILENO, buff, n);
}
thrd_exit(0);
}
void ch_fg(char *const args[], PTY *ptys) {
PTY *pty = args_to_pty(args, ptys);
if(pty != NULL) {
thrd_t thrd_writer;
if(thrd_create(&thrd_writer, thp_writer, (void*)&(pty->master)) == thrd_error) {
fprintf(stderr, "Can't create writer thread. :(");
return;
}
struct termios termopt, old_ito, old_oto, old_eto;
tcgetattr(STDIN_FILENO, &termopt);
tcgetattr(STDOUT_FILENO, &old_oto);
tcgetattr(STDERR_FILENO, &old_eto);
old_ito = termopt;
termopt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &termopt);
fd_set rds;
int n;
char c;
struct timeval tv = { .tv_sec = 2, .tv_usec= 0 };
while(1) {
FD_ZERO(&rds);
FD_SET(STDIN_FILENO, &rds);
if(select(STDIN_FILENO+1, &rds, NULL, NULL, &tv) < 0) {
break;
}
if(!(FD_ISSET(STDIN_FILENO, &rds))) continue;
if((n = read(STDIN_FILENO, &c, 1)) > 0) {
write(pty->master, &c, 1);
}
int status;
pid_t pid;
if((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("pid #%d finished: %d\n", (int)pid, status);
thrd_join(thrd_writer, &status);
fsync(STDIN_FILENO);
fsync(STDOUT_FILENO);
fsync(STDERR_FILENO);
tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_ito);
tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old_oto);
tcsetattr(STDERR_FILENO, TCSAFLUSH, &old_eto);
break;
}
}
}
}
I to prawie działa, bo uruchamiam sobie vima i podłączam do terminala, wszystko działa super dopóki nie zamknę go. Pomimo, że po jego zamknięciu przywracam ustawienia termiosa po zsynchronizowaniu strumieni standardowych, wraca mi do mojego kodu, ale ignoruje mi wszystkie znaki nowej linii i powrotu karetki, czyli praktycznie nie do użytku jest. Używałem zakomentowanego kawałka kodu, żeby zweryfikować, że vim używa jakiegoś specjalnego kodu ucieczki (escape code), żeby zmienić ustawienia terminala i tak się rzeczywiście dzieje.
Teraz pytanie, jak to właściwie zrobić? Powinienem otworzyć /dev/tty
i na nim robić termios czy może użyć jakiegoś specjalnego kodu do resetowania terminala? Może jakieś komentarze jak to lepiej zrobić, co jest potrzebne, a co nie?