Made more cross-platform by changing ->_fileno to fileno().
[wine] / console / xterm.c
1 /* xterm.c */
2
3 /* This "driver" is designed to go on top of an existing driver
4    to provide support for features only present if using an
5    xterm or compatible program for your console output. It should
6    inlcude such features as resizing, separation of output from the
7    standard wine console, and a configurable title bar for
8    Win32 console. */
9 /* Right now, it doesn't have any "special" features */
10
11 #include <signal.h>
12 #include <sys/ioctl.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <termios.h>
16 #include <errno.h>
17
18 #include "windows.h"
19 #include "console.h"
20 #include "debug.h"
21
22 static BOOL32 wine_create_console(FILE **master, FILE **slave, int *pid);
23 static FILE *wine_openpty(FILE **master, FILE **slave, char *name,
24                         struct termios *term, struct winsize *winsize);
25
26 /* The console -- I chose to keep the master and slave
27  * (UNIX) file descriptors around in case they are needed for
28  * ioctls later.  The pid is needed to destroy the xterm on close
29  */
30 typedef struct _XTERM_CONSOLE {
31         FILE    *master;                 /* xterm side of pty */
32         FILE    *slave;                  /* wine side of pty */
33         int     pid;                    /* xterm's pid, -1 if no xterm */
34 } XTERM_CONSOLE;
35
36 static XTERM_CONSOLE xterm_console;
37
38 CONSOLE_device chain;
39 FILE *old_in, *old_out;
40
41 void XTERM_Start()
42 {
43    /* Here, this is a supplementary driver so we should remember to call
44       the chain. */
45    chain.init = driver.init;
46    driver.init = XTERM_Init;
47
48    chain.close = driver.close;
49    driver.close = XTERM_Close;
50 }
51
52 void XTERM_Init()
53 {
54    wine_create_console(&xterm_console.master, &xterm_console.slave,
55       &xterm_console.pid);
56
57    old_in = driver.console_in;
58    driver.console_in = xterm_console.slave;
59
60    old_out = driver.console_out;
61    driver.console_out = xterm_console.slave;
62
63    /* Then call the chain... */
64    if (chain.init)
65       chain.init();
66 }
67
68 void XTERM_Close()
69 {
70    /* Call the chain first... */
71    if (chain.close)
72       chain.close();
73
74    driver.console_in = old_in;
75    driver.console_out = old_out;
76
77    /* make sure a xterm exists to kill */
78    if (xterm_console.pid != -1) {
79       kill(xterm_console.pid, SIGTERM);
80    }
81 }
82
83 /**
84  *  It looks like the openpty that comes with glibc in RedHat 5.0
85  *  is buggy (second call returns what looks like a dup of 0 and 1
86  *  instead of a new pty), this is a generic replacement.
87  */
88 /** Can't we determine this using autoconf?
89  */
90
91 static FILE *wine_openpty(FILE **master, FILE **slave, char *name,
92                         struct termios *term, struct winsize *winsize)
93 {
94         FILE *fdm, *fds;
95         char *ptr1, *ptr2;
96         char pts_name[512];
97
98         strcpy (pts_name, "/dev/ptyXY");
99         for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++) {
100                 pts_name[8] = *ptr1;
101                 for (ptr2 = "0123456789abcdef"; *ptr2 != 0; ptr2++) {
102                         pts_name[9] = *ptr2;
103
104                         if ((fdm = fopen(pts_name, "r+")) == NULL) {
105                                 if (errno == ENOENT)
106                                         return (FILE *) -1;
107                                 else
108                                         continue;
109                         }
110                         pts_name[5] = 't';
111                         if ((fds = fopen(pts_name, "r+")) == NULL) {
112                                 pts_name[5] = 'p';
113                                 continue;
114                         }
115                         *master = fdm;
116                         *slave = fds;
117
118                         if (term != NULL)
119                                 tcsetattr(fileno(*slave), TCSANOW, term);
120                         if (winsize != NULL)
121                                 ioctl(fileno(*slave), TIOCSWINSZ, winsize);
122
123                         if (name != NULL)
124                                 strcpy(name, pts_name);
125                         return fds;
126                 }
127         }
128         return (FILE *) -1;
129 }
130
131 static BOOL32 wine_create_console(FILE **master, FILE **slave, int *pid)
132 {
133         struct termios term;
134         char buf[1024];
135         char c = '\0';
136         int status = 0;
137         int i;
138
139         if (tcgetattr(0, &term) < 0) return FALSE;
140         term.c_lflag |= ICANON;
141         term.c_lflag &= ~ECHO;
142         if (wine_openpty(master, slave, NULL, &term, NULL) < 0)
143            return FALSE;
144
145         if ((*pid=fork()) == 0) {
146                 tcsetattr(fileno(*slave), TCSADRAIN, &term);
147                 sprintf(buf, "-Sxx%d", fileno(*master));
148                 execlp("xterm", "xterm", buf, NULL);
149                 ERR(console, "error creating xterm\n");
150                 exit(1);
151         }
152
153         /* most xterms like to print their window ID when used with -S;
154          * read it and continue before the user has a chance...
155          * NOTE: this is the reason we started xterm with ECHO off,
156          * we'll turn it back on below
157          */
158
159         for (i=0; c!='\n'; (status=fread(&c, 1, 1, *slave)), i++) {
160                 if (status == -1 && c == '\0') {
161                                 /* wait for xterm to be created */
162                         usleep(100);
163                 }
164                 if (i > 10000) {
165                         WARN(console, "can't read xterm WID\n");
166                         kill(*pid, SIGKILL);
167                         return FALSE;
168                 }
169         }
170         term.c_lflag |= ECHO;
171         tcsetattr(fileno(*master), TCSADRAIN, &term);
172
173         return TRUE;
174 }