1 /* 2 * MiniTerminal - A basic windowed terminal to allow 3 * command-line interaction from within app_server. 4 * Based on consoled and MuTerminal. 5 * 6 * Copyright 2005 Michael Lotz. All rights reserved. 7 * Distributed under the MIT License. 8 * 9 * Copyright 2004-2005, Haiku. All rights reserved. 10 * Distributed under the terms of the MIT License. 11 * 12 * Copyright 2002, Travis Geiselbrecht. All rights reserved. 13 * Distributed under the terms of the NewOS License. 14 */ 15 16 17 #include <OS.h> 18 #include <image.h> 19 #include <Message.h> 20 #include <Window.h> 21 22 #include <termios.h> 23 #include <unistd.h> 24 #include <dirent.h> 25 #include <string.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <signal.h> 29 30 #include "Arguments.h" 31 #include "Console.h" 32 #include "MiniView.h" 33 34 #include "VTkeymap.h" 35 36 //#define TRACE_MINI_TERMINAL 37 #ifdef TRACE_MINI_TERMINAL 38 #ifdef __HAIKU__ 39 #define TRACE(x) debug_printf x 40 #else 41 #define TRACE(x) printf x 42 #endif 43 #else 44 #define TRACE(x) 45 #endif 46 47 void 48 Setenv(const char *var, const char *value) 49 { 50 int envindex = 0; 51 const int len = strlen(var); 52 const int val_len = strlen (value); 53 54 while (environ [envindex] != NULL) { 55 if (strncmp (environ [envindex], var, len) == 0) { 56 /* found it */ 57 // TODO: shouldn't we free the old variable first? 58 environ[envindex] = (char *)malloc ((unsigned)len + val_len + 2); 59 sprintf (environ [envindex], "%s=%s", var, value); 60 return; 61 } 62 envindex ++; 63 } 64 65 environ [envindex] = (char *) malloc ((unsigned)len + val_len + 2); 66 sprintf (environ [envindex], "%s=%s", var, value); 67 environ [++envindex] = NULL; 68 } 69 70 MiniView::MiniView(const Arguments &args) 71 : ViewBuffer(args.Bounds().OffsetToCopy(0, 0)), 72 fArguments(args) 73 { 74 // we need a message filter so that we get B_TAB keydowns 75 AddFilter(new BMessageFilter(B_KEY_DOWN, &MiniView::MessageFilter)); 76 } 77 78 MiniView::~MiniView() 79 { 80 kill_thread(fConsoleWriter); 81 kill_thread(fShellExecutor); 82 kill_thread(fShellProcess); 83 } 84 85 void 86 MiniView::Start() 87 { 88 fConsole = new Console(this); 89 90 if (OpenTTY() != B_OK) { 91 TRACE(("error in OpenTTY\n")); 92 return; 93 } 94 95 // we're a session leader 96 setsid(); 97 98 if (SpawnThreads() != B_OK) 99 TRACE(("error in SpawnThreads\n")); 100 } 101 102 status_t 103 MiniView::OpenTTY() 104 { 105 DIR *dir; 106 107 dir = opendir("/dev/pt"); 108 if (dir != NULL) { 109 struct dirent *entry; 110 char name[64]; 111 112 while ((entry = readdir(dir)) != NULL) { 113 if (entry->d_name[0] == '.') // filter out . and .. 114 continue; 115 116 sprintf(name, "/dev/pt/%s", entry->d_name); 117 118 fMasterFD = open(name, O_RDWR); 119 if (fMasterFD >= 0) { 120 sprintf(name, "/dev/tt/%s", entry->d_name); 121 122 fSlaveFD = open(name, O_RDWR); 123 if (fSlaveFD < 0) { 124 TRACE(("cannot open tty\n")); 125 close(fMasterFD); 126 } else { 127 struct termios tio; 128 tcgetattr(fSlaveFD, &tio); 129 130 // set signal default 131 signal(SIGCHLD, SIG_DFL); 132 signal(SIGHUP, SIG_DFL); 133 signal(SIGQUIT, SIG_DFL); 134 signal(SIGTERM, SIG_DFL); 135 signal(SIGINT, SIG_DFL); 136 signal(SIGTTOU, SIG_DFL); 137 138 // set terminal interface 139 tio.c_line = 0; 140 tio.c_lflag |= ECHOE; 141 142 // input: nl->nl, cr->nl 143 tio.c_iflag &= ~(INLCR|IGNCR); 144 tio.c_iflag |= ICRNL; 145 tio.c_iflag &= ~ISTRIP; 146 147 // output: cr->cr, nl in not retrun, no delays, ln->cr/ln 148 tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); 149 tio.c_oflag |= ONLCR; 150 tio.c_oflag |= OPOST; 151 152 // baud rate is 19200 (equal to beterm) 153 tio.c_cflag &= ~(CBAUD); 154 tio.c_cflag |= B19200; 155 156 tio.c_cflag &= ~CSIZE; 157 tio.c_cflag |= CS8; 158 tio.c_cflag |= CREAD; 159 160 tio.c_cflag |= HUPCL; 161 tio.c_iflag &= ~(IGNBRK|BRKINT); 162 163 // enable signals, canonical processing (erase, kill, etc), echo 164 tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL; 165 tio.c_lflag &= ~ECHOK; 166 167 tio.c_lflag &= ~IEXTEN; 168 169 // set control charactors 170 tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */ 171 tio.c_cc[VQUIT] = '\\'& 0x1f; /* '^\' */ 172 tio.c_cc[VERASE] = 0x08; /* '^H' */ 173 tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */ 174 tio.c_cc[VEOF] = 'D' & 0x1f; /* '^D' */ 175 tio.c_cc[VEOL] = 0; /* '^@' */ 176 tio.c_cc[VMIN] = 4; 177 tio.c_cc[VTIME] = 0; 178 tio.c_cc[VEOL2] = 0; /* '^@' */ 179 tio.c_cc[VSWTCH] = 0; /* '^@' */ 180 tio.c_cc[VSTART] = 'S' & 0x1f; /* '^S' */ 181 tio.c_cc[VSTOP] = 'Q' & 0x1f; /* '^Q' */ 182 tio.c_cc[VSUSP] = '@' & 0x1f; /* '^@' */ 183 184 // set terminal interface 185 tcsetattr(fSlaveFD, TCSANOW, &tio); 186 187 // set window size 188 winsize ws; 189 int32 rows, cols; 190 GetSize(&cols, &rows); 191 ws.ws_row = rows; 192 ws.ws_col = cols; 193 if (LockLooper()) { 194 ws.ws_xpixel = Bounds().IntegerWidth(); 195 ws.ws_ypixel = Bounds().IntegerHeight(); 196 UnlockLooper(); 197 } 198 ioctl(fSlaveFD, TIOCSWINSZ, &ws); 199 } 200 break; 201 } 202 } 203 204 205 Setenv("TTY", name); 206 } 207 208 if (fMasterFD < 0 || fSlaveFD < 0) { 209 TRACE(("could not open master or slave fd\n")); 210 return B_ERROR; 211 } 212 213 Setenv("TERM", "beterm"); 214 return B_OK; 215 } 216 217 void 218 MiniView::FrameResized(float width, float height) 219 { 220 ViewBuffer::FrameResized(width, height); 221 222 winsize ws; 223 int32 rows, cols; 224 GetSize(&cols, &rows); 225 ws.ws_row = rows; 226 ws.ws_col = cols; 227 ws.ws_xpixel = (uint16)width; 228 ws.ws_ypixel = (uint16)height; 229 ioctl(fSlaveFD, TIOCSWINSZ, &ws); 230 } 231 232 233 void 234 MiniView::KeyDown(const char *bytes, int32 numBytes) 235 { 236 // TODO: add interrupt char handling 237 uint32 mod = modifiers(); 238 if (numBytes == 1) { 239 if (mod & B_OPTION_KEY) { 240 char c = bytes[0] | 0x80; 241 write(fMasterFD, &c, 1); 242 } else { 243 switch (bytes[0]) { 244 case B_LEFT_ARROW: 245 write(fMasterFD, LEFT_ARROW_KEY_CODE, sizeof(LEFT_ARROW_KEY_CODE) - 1); 246 break; 247 case B_RIGHT_ARROW: 248 write(fMasterFD, RIGHT_ARROW_KEY_CODE, sizeof(RIGHT_ARROW_KEY_CODE) - 1); 249 break; 250 case B_UP_ARROW: 251 write(fMasterFD, UP_ARROW_KEY_CODE, sizeof(UP_ARROW_KEY_CODE) - 1); 252 break; 253 case B_DOWN_ARROW: 254 write(fMasterFD, DOWN_ARROW_KEY_CODE, sizeof(DOWN_ARROW_KEY_CODE) - 1); 255 break; 256 default: 257 write(fMasterFD, bytes, numBytes); 258 } 259 } 260 } else 261 write(fMasterFD, bytes, numBytes); 262 } 263 264 status_t 265 MiniView::SpawnThreads() 266 { 267 fConsoleWriter = spawn_thread(&MiniView::ConsoleWriter, "console writer", B_URGENT_DISPLAY_PRIORITY, this); 268 if (fConsoleWriter < 0) 269 return B_ERROR; 270 TRACE(("console writer thread is: %ld\n", fConsoleWriter)); 271 272 fShellExecutor = spawn_thread(&MiniView::ExecuteShell, "shell process", B_URGENT_DISPLAY_PRIORITY, this); 273 if (fShellExecutor < 0) 274 return B_ERROR; 275 TRACE(("shell executor thread is: %ld\n", fShellExecutor)); 276 277 resume_thread(fConsoleWriter); 278 resume_thread(fShellExecutor); 279 return B_OK; 280 } 281 282 int32 283 MiniView::ConsoleWriter(void *arg) 284 { 285 char buf[1024]; 286 ssize_t len; 287 MiniView *view = (MiniView *)arg; 288 289 for (;;) { 290 len = read(view->fMasterFD, buf, sizeof(buf)); 291 if (len < 0) 292 break; 293 294 view->fConsole->Write(buf, len); 295 } 296 297 return 0; 298 } 299 300 int32 301 MiniView::ExecuteShell(void *arg) 302 { 303 MiniView *view = (MiniView *)arg; 304 305 for (;;) { 306 int argc; 307 const char *const *argv; 308 view->fArguments.GetShellArguments(argc, argv); 309 310 int saved_stdin = dup(0); 311 int saved_stdout = dup(1); 312 int saved_stderr = dup(2); 313 314 dup2(view->fSlaveFD, 0); 315 dup2(view->fSlaveFD, 1); 316 dup2(view->fSlaveFD, 2); 317 318 view->fShellProcess = load_image(argc, (const char **)argv, 319 (const char **)environ); 320 setpgid(view->fShellProcess, 0); 321 tcsetpgrp(view->fSlaveFD, view->fShellProcess); 322 323 dup2(saved_stdin, 0); 324 dup2(saved_stdout, 1); 325 dup2(saved_stderr, 2); 326 close(saved_stdin); 327 close(saved_stdout); 328 close(saved_stderr); 329 330 status_t return_code; 331 wait_for_thread(view->fShellProcess, &return_code); 332 333 if (!view->fArguments.StandardShell()) { 334 view->Window()->PostMessage(B_QUIT_REQUESTED); 335 break; 336 } 337 } 338 339 return B_OK; 340 } 341 342 filter_result 343 MiniView::MessageFilter(BMessage *message, BHandler **target, BMessageFilter *filter) 344 { 345 MiniView *view = (MiniView *)(*target); 346 347 int32 raw_char; 348 message->FindInt32("raw_char", &raw_char); 349 if (raw_char == B_TAB) { 350 char bytes[2] = { B_TAB, 0 }; 351 view->KeyDown(bytes, 1); 352 return B_SKIP_MESSAGE; 353 } 354 355 return B_DISPATCH_MESSAGE; 356 } 357