egra: don't use ENTER/LEAVE (because intel sux, and they are slower than the correspo...
[iv.d.git] / vt100 / vt100emu.d
blobd9518632094901aefe60c58a4335fc7d4eab375b
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module iv.vt100.vt100emu;
17 private:
19 import iv.alice;
20 import iv.strex;
21 import iv.utfutil;
22 import iv.x11;
24 import iv.vt100.scrbuf;
25 import iv.vt100.vt100buf;
28 // ////////////////////////////////////////////////////////////////////////// //
29 // VT-100 emulator with Pty
30 public class VT100Emu : VT100Buf {
31 private:
32 char* wrbuf;
33 uint wrbufpos, wrbufsize;
34 bool writeFucked;
35 char[4096] rdbuf;
36 int[2] readfds;
37 int[2] writefds;
39 public:
40 PtyInfo ptyi;
42 private:
43 void wrbufFree () nothrow @trusted @nogc {
44 if (wrbuf !is null) {
45 import core.stdc.stdlib : free;
46 free(wrbuf);
47 wrbuf = null;
49 wrbufpos = wrbufsize = 0;
52 // ////////////////////////////////////////////////////////////////////// //
53 public final @property bool canWriteData () nothrow @trusted @nogc {
54 import core.sys.posix.sys.select;
55 import core.sys.posix.sys.time : timeval;
56 if (!ptyi.valid) return false;
57 for (;;) {
58 fd_set wrs;
59 FD_ZERO(&wrs);
60 FD_SET(ptyi.masterfd, &wrs);
61 timeval tv;
62 tv.tv_sec = 0;
63 tv.tv_usec = 0;
64 int sres = select(ptyi.masterfd+1, &wrs, null, null, &tv);
65 if (sres < 0) {
66 import core.stdc.errno;
67 if (errno == EINTR) continue;
68 return false;
70 return (sres != 0);
74 final void writeBuf () nothrow @trusted @nogc {
75 if (writeFucked || wrbufpos == 0 || !ptyi.valid) return;
76 for (;;) {
77 import core.sys.posix.unistd : write;
78 auto wr = write(ptyi.masterfd, wrbuf, wrbufpos);
79 if (wr < 0) {
80 import core.stdc.errno;
81 if (errno == EINTR) continue;
82 writeFucked = true;
83 return;
85 if (wr > 0) {
86 if (wr == wrbufpos) {
87 wrbufpos = 0;
88 } else {
89 import core.stdc.string : memmove;
90 uint left = wrbufpos-cast(uint)wr;
91 memmove(wrbuf, wrbuf+wr, left);
92 wrbufpos = left;
95 break;
99 public final @property usize dataBufUsed () const pure nothrow @safe @nogc { pragma(inline, true); return wrbufpos; }
100 public final @property bool wasWriteError () const pure nothrow @safe @nogc { pragma(inline, true); return writeFucked; }
101 public final void resetWriteError () pure nothrow @safe @nogc { pragma(inline, true); writeFucked = false; }
103 public override void putData (const(void)[] buf) nothrow @trusted @nogc {
104 if (!ptyi.valid) return;
105 if (wrbufpos && wrbufpos == wrbufsize && canWriteData) writeBuf();
106 auto data = cast(const(ubyte)[])buf;
107 while (data.length > 0) {
108 import core.stdc.string : memcpy;
109 if (wrbufpos == wrbufsize) {
110 if (canWriteData) writeBuf();
111 if (wrbufpos == wrbufsize) {
112 import core.stdc.stdlib : realloc;
113 if (wrbufsize+buf.length <= wrbufsize) assert(0, "fuck!");
114 uint newsz = wrbufsize+cast(uint)data.length;
115 if (newsz >= int.max/1024) {
116 wrbufFree();
117 writeFucked = true;
118 return;
120 if (newsz&0x3ff) newsz = (newsz|0x3ff)+1;
121 auto nbuf = cast(char*)realloc(wrbuf, newsz);
122 if (nbuf is null) assert(0, "out of memory");
123 wrbuf = nbuf;
124 wrbufsize = newsz;
127 uint chunk = cast(uint)(data.length > wrbufsize-wrbufpos ? wrbufsize-wrbufpos : data.length);
128 memcpy(wrbuf+wrbufpos, data.ptr, chunk);
129 wrbufpos += chunk;
130 data = data[chunk..$];
134 final @property bool canReadData () nothrow @trusted @nogc {
135 import core.sys.posix.sys.select;
136 import core.sys.posix.sys.time : timeval;
137 if (!ptyi.valid) return false;
138 for (;;) {
139 fd_set rds;
140 FD_ZERO(&rds);
141 FD_SET(ptyi.masterfd, &rds);
142 timeval tv;
143 tv.tv_sec = 0;
144 tv.tv_usec = 0;
145 int sres = select(ptyi.masterfd+1, &rds, null, null, &tv);
146 if (sres < 0) {
147 import core.stdc.errno;
148 if (errno == EINTR) continue;
149 return false;
151 return (sres != 0);
155 final void readData () nothrow {
156 if (!ptyi.valid) return;
157 int total = 0;
158 while (canReadData) {
159 import core.sys.posix.unistd : read;
160 auto rd = read(ptyi.masterfd, rdbuf.ptr, rdbuf.length);
161 if (rd < 0) {
162 import core.stdc.errno;
163 if (errno == EINTR) continue;
164 return;
166 if (rd == 0) return;
167 version(dump_output) {
168 try {
169 import std.stdio : File;
170 auto fo = File("zdump.log", "a");
171 fo.rawWrite(rdbuf[0..rd]);
172 } catch (Exception) {}
174 putstr(rdbuf[0..rd]);
175 total += cast(int)rd;
176 if (total > 65535) break;
180 public:
181 this (int aw, int ah, bool mIsUtfuck=true) nothrow @safe {
182 super(aw, ah, mIsUtfuck);
185 ~this () { wrbufFree(); }
187 // DO NOT CALL!
188 override void intrClear () nothrow @trusted {
189 super.intrClear();
190 wrbufFree();
193 // for terminal emulator
194 final @property int masterFD () nothrow @trusted @nogc { return ptyi.masterfd; }
196 final const(int)[] getReadFDs () nothrow @trusted @nogc {
197 if (!ptyi.valid) return null;
198 readfds[0] = ptyi.masterfd;
199 return readfds[0..1];
202 final const(int)[] getWriteFDs () nothrow @trusted @nogc {
203 if (!ptyi.valid) return null;
204 if (wasWriteError) {
205 { import core.stdc.stdio; stderr.fprintf("WARNING! write error occured for masterfd %d!\n", ptyi.masterfd); }
206 if (onBell !is null) onBell(this);
207 resetWriteError();
209 if (!dataBufUsed) return null;
210 writefds[0] = ptyi.masterfd;
211 return writefds[0..1];
214 final void canWriteTo (int fd) {
215 if (fd < 0 || fd != ptyi.masterfd) return;
216 writeBuf();
219 final void canReadFrom (int fd) {
220 if (fd < 0 || fd != ptyi.masterfd) return;
221 readData();
224 final bool checkDeadChild (int pid, int exitcode) {
225 if (!ptyi.valid) return false;
226 if (ptyi.pid == pid) {
227 ptyi.close();
228 //putstr("\r\n\x1b[0;33;1;41mDEAD CHILD");
229 wrbufFree();
230 return true;
232 return false;
235 override void sendTTYResizeSignal () {
236 if (ptyi.valid) .sendTTYResizeSignal(ptyi.masterfd, mWidth, mHeight);
239 // ////////////////////////////////////////////////////////////////////// //
240 final @property bool isPtyActive () const pure nothrow @safe @nogc { pragma(inline, true); return ptyi.valid; }
242 final bool execPty (const(char[])[] args) nothrow @trusted @nogc {
243 if (isPtyActive) return false;
244 ptyi = executeInNewPty(args, mWidth, mHeight);
245 return ptyi.valid;
248 final char[] getProcessName(bool fullname=false) (char[] obuf) nothrow @trusted @nogc {
249 return .getProcessName(masterFD, obuf);
252 final char[] getProcessCwd (char[] obuf) nothrow @trusted @nogc {
253 return .getProcessCwd(masterFD, obuf);
256 final char[] getFullProcessName (char[] obuf) nothrow @trusted @nogc {
257 return .getFullProcessName(masterFD, obuf);
262 // ////////////////////////////////////////////////////////////////////////// //
263 private:
264 pragma(lib, "util");
266 import core.sys.posix.sys.ioctl : winsize;
267 import core.sys.posix.sys.types : pid_t;
268 import core.sys.posix.termios : termios;
270 extern(C) nothrow @nogc {
271 int openpty (int* amaster, int* aslave, char* name, const(termios)* termp, const(winsize)* winp);
272 pid_t forkpty (int* amaster, char* name, const(termios)* termp, const(winsize)* winp);
273 int login_tty (int fd);
275 // from unistd
276 //enum O_CLOEXEC = 0o2000000; /* set close_on_exec */
277 enum O_CLOEXEC = 524288; /* set close_on_exec */
279 int pipe2 (int* pipefd, int flags);
283 // ////////////////////////////////////////////////////////////////////////// //
284 private void closeAllFDs () nothrow @trusted @nogc {
285 // close FDs
286 import core.sys.posix.sys.resource : getrlimit, rlimit, rlim_t, RLIMIT_NOFILE;
287 import core.sys.posix.unistd : close;
288 rlimit r = void;
289 getrlimit(RLIMIT_NOFILE, &r);
290 foreach (rlim_t idx; 3..r.rlim_cur) close(cast(int)idx);
294 void exec (const(char[])[] args) nothrow @trusted @nogc {
295 import core.sys.posix.stdlib : getenv;
296 static char[65536] cmdline = void;
297 static const(char)*[32768] argv = void;
298 usize argc = 0;
299 usize cmpos = 0;
300 usize stidx = 0;
301 // if no args or first arg is empty, get default shell
302 if (args.length == 0 || args[0].length == 0) {
303 const(char)* envshell = getenv("SHELL");
304 if (envshell is null || !envshell[0]) envshell = "/bin/sh";
305 //setenv("TERM", "rxvt", 1); // should be done on program start
306 argv[0] = envshell;
307 if (args.length == 0) {
308 // interactive shell
309 argv[1] = "-i";
310 argc = 2;
311 } else {
312 argc = 1;
314 ++stidx;
316 foreach (immutable idx; stidx..args.length) {
317 import core.stdc.string : memcpy;
318 if (args[idx].length >= cmdline.length-cmpos) break;
319 if (argc+1 >= argv.length) break;
320 argv[argc++] = cmdline.ptr+cmpos;
321 if (args[idx].length) {
322 memcpy(cmdline.ptr+cmpos, args[idx].ptr, args[idx].length);
323 cmpos += args[idx].length;
325 cmdline[cmpos++] = 0;
327 argv[argc] = null;
328 closeAllFDs();
329 import core.sys.posix.unistd : execvp;
330 execvp(argv[0], argv.ptr);
332 import core.stdc.stdlib : exit, EXIT_FAILURE;
333 exit(EXIT_FAILURE);
338 // ////////////////////////////////////////////////////////////////////////// //
339 void sendTTYResizeSignal (int masterfd, int width, int height) nothrow @trusted @nogc {
340 if (masterfd < 0) return;
341 width = ScreenBuffer.max(1, ScreenBuffer.min(width, ushort.max-1));
342 height = ScreenBuffer.max(1, ScreenBuffer.min(height, ushort.max-1));
343 import core.sys.posix.sys.ioctl : winsize, ioctl, TIOCSWINSZ;
344 winsize w = void;
345 w.ws_row = cast(ushort)height;
346 w.ws_col = cast(ushort)width;
347 w.ws_xpixel = w.ws_ypixel = 0;
348 if (ioctl(masterfd, TIOCSWINSZ, &w) < 0) {
349 import core.stdc.errno;
350 import core.stdc.stdio;
351 import core.stdc.string;
352 fprintf(stderr, "Warning: couldn't set window size: %s\n", strerror(errno));
357 // ////////////////////////////////////////////////////////////////////////// //
358 struct PtyInfo {
359 int masterfd = -1;
360 int pid = -1;
362 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (masterfd >= 0 && pid >= 0); }
364 void close () {
365 if (masterfd >= 0) {
366 import core.sys.posix.unistd : close;
367 close(masterfd);
368 masterfd = -1;
370 pid = -1;
375 PtyInfo executeInNewPty (const(char[])[] args, int width, int height) nothrow @trusted @nogc {
376 import core.sys.posix.stdlib : setenv;
377 import core.sys.posix.sys.ioctl : winsize;
378 PtyInfo ptyi;
379 width = ScreenBuffer.max(1, ScreenBuffer.min(width, ushort.max-1));
380 height = ScreenBuffer.max(1, ScreenBuffer.min(height, ushort.max-1));
381 winsize w = void;
382 w.ws_col = cast(ushort)width;
383 w.ws_row = cast(ushort)height;
384 w.ws_xpixel = w.ws_ypixel = 0;
385 auto mPId = forkpty(&ptyi.masterfd, null, null, &w);
386 // failed?
387 if (mPId < 0) {
388 if (ptyi.masterfd >= 0) {
389 import core.sys.posix.unistd : close;
390 close(ptyi.masterfd);
391 ptyi.masterfd = -1;
393 ptyi.pid = -1;
394 return ptyi;
396 // child?
397 if (mPId == 0) {
398 import core.sys.posix.unistd : setsid;
399 setenv("TERM", "rxvt", 1);
400 setsid(); // create a new process group
401 exec(args);
402 // will never return
403 assert(0);
405 // master
406 ptyi.pid = mPId;
407 // no need to set terminal size here, as `forkpty()` did that for us
408 return ptyi;
412 // ////////////////////////////////////////////////////////////////////////// //
413 char[] getProcessName(bool fullname=false) (int masterfd, char[] obuf) nothrow @trusted @nogc {
414 import core.stdc.stdio : snprintf;
415 import core.sys.posix.fcntl : open, O_RDONLY;
416 import core.sys.posix.unistd : close, read, tcgetpgrp;
417 import core.sys.posix.sys.types : pid_t;
418 char[128] path = void;
419 char[4096] res = void;
420 //static char path[256], res[4097], *c;
421 if (masterfd < 0 || obuf.length < 1) return null;
422 pid_t pgrp = tcgetpgrp(masterfd);
423 if (pgrp == -1) return null;
424 snprintf(path.ptr, cast(uint)path.length, "/proc/%d/cmdline", pgrp);
425 int fd = open(path.ptr, O_RDONLY);
426 if (fd < 0) return null;
427 auto rd = read(fd, res.ptr, res.length);
428 close(fd);
429 if (rd <= 0) return null;
430 static if (fullname) {
431 usize pos = 0;
432 while (pos < rd && res[pos]) ++pos;
433 auto sb = res[0..pos];
434 //FIXME!
435 while (sb.length > obuf.length) sb = sb[1..$];
436 obuf[0..sb.length] = sb[];
437 return obuf[0..sb.length];
438 } else {
439 usize pos = rd;
440 while (pos > 0 && res[pos-1] != '/') --pos;
441 if (pos >= rd) return null;
442 if (rd-pos > obuf.length) pos = rd-obuf.length;
443 obuf[0..rd-pos] = res[pos..rd];
444 return obuf[0..rd-pos];
449 char[] getFullProcessName (int masterfd, char[] obuf) nothrow @trusted @nogc {
450 import core.stdc.stdio : snprintf;
451 import core.sys.posix.fcntl : open, O_RDONLY;
452 import core.sys.posix.unistd : close, read, tcgetpgrp, readlink;
453 import core.sys.posix.sys.types : pid_t;
454 char[128] path = void;
455 char[4096] res = void;
456 if (masterfd < 0 || obuf.length < 1) return null;
457 pid_t pgrp = tcgetpgrp(masterfd);
458 if (pgrp == -1) return null;
459 snprintf(path.ptr, cast(uint)path.length, "/proc/%d/exe", pgrp);
460 auto rd = readlink(path.ptr, res.ptr, res.length);
461 if (rd <= 0) return null;
462 usize pos = 0;
463 while (pos < rd && res[pos]) ++pos;
464 auto sb = res[0..pos];
465 //FIXME!
466 while (sb.length > obuf.length) sb = sb[1..$];
467 obuf[0..sb.length] = sb[];
468 return obuf[0..sb.length];
472 char[] getProcessCwd (int masterfd, char[] obuf) nothrow @trusted @nogc {
473 import core.stdc.stdio : snprintf;
474 import core.sys.posix.fcntl : open, O_RDONLY;
475 import core.sys.posix.unistd : close, read, tcgetpgrp, readlink;
476 import core.sys.posix.sys.types : pid_t;
477 char[128] path = void;
478 char[4096] res = void;
479 if (masterfd < 0 || obuf.length < 1) return null;
480 pid_t pgrp = tcgetpgrp(masterfd);
481 if (pgrp == -1) return null;
482 snprintf(path.ptr, cast(uint)path.length, "/proc/%d/cwd", pgrp);
483 auto rd = readlink(path.ptr, res.ptr, res.length);
484 if (rd <= 0) return null;
485 usize pos = 0;
486 while (pos < rd && res[pos]) ++pos;
487 auto sb = res[0..pos];
488 //FIXME!
489 while (sb.length > obuf.length) sb = sb[1..$];
490 obuf[0..sb.length] = sb[];
491 return obuf[0..sb.length];