Backspace sends DEL instead of ^H.
[spft.git] / Terminal.cpp
bloba7edb4d2a5bb5bdd5dd7665bea44dee03d85ea0c
1 #include "Terminal.h"
2 #include "History.h"
3 #include "Settings.h"
4 #include <pty.h>
5 #include <unistd.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <pwd.h>
9 #include <time.h>
10 #include <stdexcept>
13 // Because we can't pass a parameter to the signal handler, we can't really
14 // handle more than one terminal in our process without adding extra mechanism.
15 bool Terminal::child_died = false;
18 Terminal::Terminal(History* history_in)
19 : history(history_in), child_pid(0)
21 buffer = (char*) malloc(BUFSIZ);
22 prebuffered_bytes = 0;
24 int child_fd;
25 int result = openpty(&terminal_fd, &child_fd, NULL, NULL, NULL);
26 if (result < 0)
27 throw std::runtime_error("openpty() failed");
29 pid_t pid = fork();
30 if (pid < 0)
31 throw std::runtime_error("fork() failed");
32 else if (pid == 0) {
33 // We're in the child.
34 setsid(); // Create a new process group.
35 dup2(child_fd, 0);
36 dup2(child_fd, 1);
37 dup2(child_fd, 2);
38 if (ioctl(child_fd, TIOCSCTTY, NULL) < 0)
39 throw std::runtime_error("ioctl(TIOCSCTTY) failed");
40 close(child_fd);
41 close(terminal_fd);
42 exec_shell();
44 else {
45 child_pid = pid;
46 close(child_fd);
47 struct sigaction sigchld_action;
48 memset(&sigchld_action, 0, sizeof(sigchld_action));
49 sigchld_action.sa_handler = sigchld_received;
50 sigaction(SIGCHLD, &sigchld_action, NULL);
55 Terminal::~Terminal()
57 free(buffer);
61 bool Terminal::is_done()
63 return child_died;
67 void Terminal::tick()
69 // We assume this won't be called unless select() has indicated there is
70 // data to read.
72 if (child_died)
73 return;
75 // Read.
76 int result =
77 read(terminal_fd, buffer + prebuffered_bytes, BUFSIZ - prebuffered_bytes);
78 if (result == 0) {
79 child_died = true;
80 return;
82 else if (result < 0) {
83 // Wait a moment before checking child_died again.
84 struct timespec sleep_spec = { 0, 1000000 };
85 nanosleep(&sleep_spec, nullptr);
86 if (child_died) {
87 // This is a race condition; the child died *while* we were reading.
88 return;
90 throw std::runtime_error("read() failed");
93 // Give it to the History.
94 int length = prebuffered_bytes + result;
95 int bytes_consumed = history->add_input(buffer, length);
96 length -= bytes_consumed;
97 if (length > 0) {
98 if (length == BUFSIZ) {
99 // The history didn't consume *anything* -- probably it hit an
100 // unterminated escape sequence. Just discard the whole buffer. It's
101 // not a great solution, but it'll keep us from getting wedged.
102 length = 0;
104 else
105 memmove(buffer, buffer + bytes_consumed, length);
107 prebuffered_bytes = length;
111 void Terminal::send(const char* data, int length)
113 if (length == -1)
114 length = strlen(data);
115 // st makes sure not to send more than 256 bytes at a time, because it might
116 // be connected to a modem. We don't bother with that (currently).
117 while (length > 0) {
118 ssize_t bytes_written = write(terminal_fd, data, length);
119 if (bytes_written < 0)
120 throw std::runtime_error("write() failed");
121 data += bytes_written;
122 length -= bytes_written;
127 void Terminal::hang_up()
129 if (child_pid > 0)
130 kill(child_pid, SIGHUP);
134 void Terminal::notify_resize(int columns, int rows, int pixel_width, int pixel_height)
136 struct winsize window_size;
137 window_size.ws_row = rows;
138 window_size.ws_col = columns;
139 window_size.ws_xpixel = pixel_width;
140 window_size.ws_ypixel = pixel_height;
141 ioctl(terminal_fd, TIOCSWINSZ, &window_size);
145 static void its_ok()
147 // Alles cool, nothing to do.
150 void Terminal::exec_shell()
152 const struct passwd* user_info = getpwuid(getuid());
153 if (user_info == NULL)
154 throw std::runtime_error("Couldn't get user info");
156 // Which shell to use.
157 const char* shell_path = getenv("SHELL");
158 if (shell_path == NULL) {
159 if (user_info->pw_shell[0])
160 shell_path = user_info->pw_shell;
161 else
162 shell_path = "/bin/bash";
165 // Environment.
166 unsetenv("COLUMNS");
167 unsetenv("LINES");
168 unsetenv("TERMCAP");
169 setenv("LOGNAME", user_info->pw_name, true);
170 setenv("USER", user_info->pw_name, true);
171 setenv("SHELL", shell_path, true);
172 setenv("HOME", user_info->pw_dir, true);
173 setenv("TERM", settings.term_name.c_str(), true);
174 setenv("SPFT", "true", true);
176 // Signals.
177 signal(SIGCHLD, SIG_DFL);
178 signal(SIGHUP, SIG_DFL);
179 signal(SIGINT, SIG_DFL);
180 signal(SIGQUIT, SIG_DFL);
181 signal(SIGTERM, SIG_DFL);
182 signal(SIGALRM, SIG_DFL);
184 // Working directory.
185 if (!settings.working_directory.empty()) {
186 std::string path = settings.working_directory;
187 if (path[0] == '~')
188 path = settings.home_path() + path.substr(1);
189 int result = chdir(path.c_str());
190 if (result < 0)
191 its_ok();
194 // Start the shell.
195 char* args[] = { (char*) shell_path, 0 };
196 execvp(shell_path, args);
200 void Terminal::sigchld_received(int signal_number)
202 child_died = true;