1.10
[platform/upstream/kbd.git] / src / resizecons.c
1 /*
2  * resizecons.c - change console video mode
3  *
4  * Version 1.00
5  *
6  * How to use this:
7  *
8  * 1. Get svgalib, make restoretextmode, put it somewhere in your path.
9  * 2. Put vga=ask in /etc/lilo/config, boot your machine a number of times,
10  *    each time with a different vga mode, and run the command
11  *        "restoretextmode -w COLSxROWS".
12  *    For me this resulted in the files 80x25, 80x28, 80x50, 80x60, 100x40,
13  *    132x25, 132x28, 132x44. Put these files in /usr/lib/kbd/videomodes
14  *    (or in your current dir).
15  * 3. Now "resizecons COLSxROWS" will change your video mode. (Assuming you
16  *    have an appropriate kernel, and svgalib works for your video card.)
17  *
18  * Note: this is experimental, but it works for me. Comments are welcome.
19  * You may have to be root to get the appropriate ioperm permissions.
20  * It is not safe to make this program suid root.
21  *
22  * aeb@cwi.nl - 940924
23  *
24  * Harm Hanemaaijer added the -lines option, which reprograms the
25  * number of scanlines. He writes:
26  *
27  * Added -lines option, which reprograms the number of scanlines and
28  * the font height of the VGA hardware with register I/O, so that
29  * switching is possible between textmodes with different numbers
30  * of lines, in a VGA compatible way. It should work for 132 column
31  * modes also, except that number of columns cannot be changed.
32  *
33  * Standard VGA textmode uses a 400 scanline screen which is refreshed
34  * at 70 Hz. The following modes are supported that use this vertical
35  * resolution (C is the number of columns, usually 80 or 132).
36  *
37  *      mode            font height
38  *      C x 25          16
39  *      C x 28          14
40  *      C x 36          11      (non-standard height)
41  *      C x 44          9       (8-line fonts are a good match)
42  *      C x 50          8
43  *
44  * The following modes are supported with a 480 scanline resolution,
45  * refresh at 60 Hz. Some not quite VGA compatible displays may not
46  * support this (it uses the same vertical timing as standard VGA
47  * 640x480x16 graphics mode).
48  *
49  *      mode            font height
50  *      C x 30          16
51  *      C x 34          14
52  *      C x 40          12      (non-standard height)
53  *      C x 60          8
54  *
55  * Two 12-line fonts are already in the consolefonts directory,
56  * namely lat1-12.psfu.gz and lat2-12.psfu.gz.
57  * For the 36 lines mode (11 line font), lat1-10.psfu.gz and lat2-10.psfu.gz
58  * can be used.
59  * 
60  * hhanemaa@cs.ruu.nl - 941028
61  * 
62  * Notes:
63  *
64  * In the consolefonts directory there is 'default8x9' font file but
65  * no 'default8x8'. Why is this? The standard VGA BIOS has an 8-line
66  * font, and they are much more common in SVGA modes (e.g. 50 and 60
67  * row modes). It is true that standard VGA textmode uses effectively
68  * 9 pixel wide characters, but that has nothing to do with the font
69  * data.
70  */
71
72 #include <stdlib.h>
73 #include <unistd.h>
74 #include <string.h>
75 #include <fcntl.h>
76 #include <stdio.h>
77 #include <errno.h>
78 #include <signal.h>
79 #include <sys/ioctl.h>
80 #if (__GNU_LIBRARY__ >= 6)
81 #include <sys/perm.h>
82 #else
83 #include <linux/types.h>
84 #include <linux/termios.h>
85 #endif
86 #include <linux/vt.h>
87 #include "paths.h"
88 #include "getfd.h"
89 #include "findfile.h"
90 #include "nls.h"
91 #include "version.h"
92
93 #define MODE_RESTORETEXTMODE    0
94 #define MODE_VGALINES           1
95
96 static void usage(void);
97
98 /* VGA textmode register tweaking. */
99 static void vga_init_io(void);
100 static void vga_400_scanlines(void);
101 static void vga_480_scanlines(void);
102 static void vga_set_fontheight(int);
103 static int vga_get_fontheight(void);
104 static void vga_set_cursor(int, int);
105 static void vga_set_verticaldisplayend_lowbyte(int);
106
107 char *dirpath[] = { "", DATADIR "/" VIDEOMODEDIR "/", 0};
108 char *suffixes[] = { "", 0 };
109
110 int
111 main(int argc, char **argv) {
112     int rr, cc, fd, i, mode;
113     struct vt_sizes vtsizes;
114     struct vt_stat vtstat;
115     struct winsize winsize;
116     char *p;
117     char tty[12], cmd[80], infile[1024];
118     FILE *fin;
119     char *defaultfont;
120
121     set_progname(argv[0]);
122
123     setlocale(LC_ALL, "");
124     bindtextdomain(PACKAGE, LOCALEDIR);
125     textdomain(PACKAGE);
126
127     if (argc < 2)
128       usage();
129
130     if (argc == 2 && !strcmp(argv[1], "-V"))
131       print_version_and_exit();
132
133     rr = 0;                     /* make gcc happy */
134     cc = atoi(argv[1]);
135     mode = MODE_RESTORETEXTMODE;
136     if (argc == 3 && strcmp(argv[1], "-lines") == 0) {
137         mode = MODE_VGALINES;
138         rr = atoi(argv[2]);
139     }
140     else
141     if (argc == 2 && (p = index(argv[1], 'x')) != 0)
142       rr = atoi(p+1);
143     else if(argc == 3)
144       rr = atoi(argv[2]);
145     else
146       usage();
147
148     if (mode == MODE_RESTORETEXTMODE) {
149         /* prepare for: restoretextmode -r 80x25 */
150         sprintf(infile, "%dx%d", cc, rr);
151         fin = findfile(infile, dirpath, suffixes);
152         if (!fin) {
153             fprintf(stderr, _("resizecons: cannot find videomode file %s\n"),
154                     infile);
155             exit(1);
156         }
157         fpclose(fin);
158     }
159
160     fd = getfd(NULL);
161
162     if(ioctl(fd, TIOCGWINSZ, &winsize)) {
163         perror("TIOCGWINSZ");
164         exit(1);
165     }
166
167     if (mode == MODE_VGALINES) {
168         /* Get the number of columns. */
169         cc = winsize.ws_col;
170         if (rr != 25 && rr != 28 && rr !=30 && rr != 34 && rr != 36
171         && rr != 40 && rr != 44 && rr != 50 && rr != 60) {
172             fprintf(stderr, _("Invalid number of lines\n"));
173             exit(1);
174         }
175     }
176
177     if(ioctl(fd, VT_GETSTATE, &vtstat)) {
178         perror("VT_GETSTATE");
179         exit(1);
180     }
181
182     vtsizes.v_rows = rr;
183     vtsizes.v_cols = cc;
184     vtsizes.v_scrollsize = 0;
185
186     vga_init_io();              /* maybe only if (mode == MODE_VGALINES) */
187
188     if(ioctl(fd, VT_RESIZE, &vtsizes)) {
189         perror("VT_RESIZE");
190         exit(1);
191     }
192
193     if (mode == MODE_VGALINES) {
194         /* Program the VGA registers. */
195         int scanlines_old;
196         int scanlines_new;
197         int fontheight;
198         if (winsize.ws_row == 25 || winsize.ws_row == 28 ||
199         winsize.ws_row == 36 || winsize.ws_row == 44 ||
200         winsize.ws_row == 50)
201             scanlines_old = 400;
202         else
203             scanlines_old = 480;
204         if (rr == 25 || rr == 28 || rr == 36 || rr == 44 || rr == 50)
205             scanlines_new = 400;
206         else
207             scanlines_new = 480;
208         /* Switch to 400 or 480 scanline vertical timing if required. */
209         if (scanlines_old != 400 && scanlines_new == 400)
210             vga_400_scanlines();
211         if (scanlines_old != 480 && scanlines_new == 480)
212             vga_480_scanlines();
213         switch (rr) {
214         case 25 : fontheight = 16; break;
215         case 28 : fontheight = 14; break;
216         case 30 : fontheight = 16; break;
217         case 34 : fontheight = 14; break;
218         case 36 : fontheight = 12; break;
219         case 40 : fontheight = 12; break;
220         case 44 : fontheight = 9; break;
221         case 50 : fontheight = 8; break;
222         case 60 : fontheight = 8; break;
223         default : fontheight = 8; break;
224         }
225         /* Set the VGA character height. */
226         vga_set_fontheight(fontheight);
227         /* Set the line offsets within a character cell of the cursor. */
228         if (fontheight >= 10)
229             vga_set_cursor(fontheight - 3, fontheight - 2);
230         else
231             vga_set_cursor(fontheight - 2, fontheight - 1);
232         /*
233          * If there are a few unused scanlines at the bottom of the
234          * screen, make sure they are not displayed (otherwise
235          * there is a annoying changing partial line at the bottom).
236          */
237         vga_set_verticaldisplayend_lowbyte((fontheight * rr - 1) & 0xff);
238         printf(_("Old mode: %dx%d  New mode: %dx%d\n"), winsize.ws_col,
239                 winsize.ws_row, cc, rr);
240         printf(_("Old #scanlines: %d  New #scanlines: %d  Character height: %d\n"),
241                 scanlines_old, scanlines_new, fontheight);
242     }
243
244     if (mode == MODE_RESTORETEXTMODE) {
245         /* do: restoretextmode -r 25x80 */
246         sprintf(cmd, "restoretextmode -r %s\n", pathname);
247         errno = 0;
248         if(system(cmd)) {
249             if(errno)
250                 perror("restoretextmode");
251             fprintf(stderr, _("resizecons: the command `%s' failed\n"), cmd);
252             exit(1);
253         }
254     }
255
256     /*
257      * for i in /dev/tty[0-9] /dev/tty[0-9][0-9]
258      * do
259      *     stty rows $rr cols $cc < $i
260      * done
261      * kill -SIGWINCH `cat /tmp/selection.pid`
262      */
263     winsize.ws_row = rr;
264     winsize.ws_col = cc;
265     for (i=0; i<16; i++)
266       if (vtstat.v_state & (1<<i)) {
267           sprintf(tty, "/dev/tty%d", i);
268           fd = open(tty, O_RDONLY);
269           if (fd < 0 && errno == ENOENT) {
270               sprintf(tty, "/dev/vc/%d", i);
271               fd = open(tty, O_RDONLY);
272           }
273           if (fd >= 0) {
274               if(ioctl(fd, TIOCSWINSZ, &winsize))
275                 perror("TIOCSWINSZ");
276               close(fd);
277           }
278       }
279
280 #if 0
281     /* Try to tell selection about the change */
282     /* [may be a security risk?] */
283     if ((fd = open("/tmp/selection.pid", O_RDONLY)) >= 0) {
284         char buf[64];
285         int n = read(fd, buf, sizeof(buf));
286         if (n > 0) {
287             int pid;
288
289             buf[n-1] = 0;
290             pid = atoi(buf);
291             kill(pid, SIGWINCH);
292         }
293         close(fd);
294     }
295 #endif
296
297     /* do: setfont default8x16 */
298     /* (other people might wish other fonts - this should be settable) */
299
300     /* We read the VGA font height register to be sure. */
301     /* There isn't much consistency in this. */
302     switch (vga_get_fontheight()) {
303     case 8 :
304     case 9 : defaultfont = "default8x9"; break;
305     case 10 : defaultfont = "lat1-10"; break;
306     case 11 :
307     case 12 : defaultfont = "lat1-12"; break;
308     case 13 :
309     case 14 : defaultfont = "iso01.14"; break;
310     case 15 :
311     case 16 :
312     default : defaultfont = "default8x16"; break;
313     }
314
315     sprintf(cmd, "setfont %s", defaultfont);
316     errno = 0;
317     if(system(cmd)) {
318         if(errno)
319             perror("setfont");
320         fprintf(stderr, "resizecons: the command `%s' failed\n", cmd);
321         exit(1);
322     }
323
324     fprintf(stderr, _("resizecons: don't forget to change TERM "
325                       "(maybe to con%dx%d or linux-%dx%d)\n"),
326             cc, rr, cc, rr);
327     if (getenv("LINES") || getenv("COLUMNS"))
328       fprintf(stderr,
329               "Also the variables LINES and COLUMNS may need adjusting.\n");
330
331     return 0;
332 }
333
334 static void
335 usage() {
336     fprintf(stderr,
337             _("resizecons:\n"
338               "call is:  resizecons COLSxROWS  or:  resizecons COLS ROWS\n"
339               "or: resizecons -lines ROWS, with ROWS one of 25, 28, 30, 34,"
340               " 36, 40, 44, 50, 60\n"));
341     exit(1);
342 }
343
344 /*
345  * The code below is used only with the option `-lines ROWS', and is
346  * very hardware dependent, and requires root privileges.
347  */
348
349 /* Port I/O macros. Note that these are not compatible with the ones */
350 /* defined in the kernel header files. */
351
352 static inline void outb( int port, int value )
353 {
354         __asm__ volatile ("outb %0,%1"
355         : : "a" ((unsigned char)value), "d" ((unsigned short)port));
356 }
357
358 static inline int inb( int port )
359 {
360         unsigned char value;
361         __asm__ volatile ("inb %1,%0"
362                 : "=a" (value)
363                 : "d" ((unsigned short)port));
364         return value;
365 }
366
367
368 /* VGA textmode register tweaking functions. */
369
370 static int crtcport;
371
372 static void vga_init_io() {
373         if (iopl(3) < 0) {
374                 fprintf(stderr,
375                         _("resizecons: cannot get I/O permissions.\n"));
376                 exit(1);
377         }
378         crtcport = 0x3d4;
379         if ((inb(0x3cc) & 0x01) == 0)
380                 crtcport = 0x3b4;
381 }
382
383 static void vga_set_fontheight( int h ) {
384         outb(crtcport, 0x09);
385         outb(crtcport + 1, (inb(crtcport + 1) & 0xe0) | (h - 1));
386 }
387
388 static int vga_get_fontheight() {
389         outb(crtcport, 0x09);
390         return (inb(crtcport + 1) & 0x1f) + 1;
391 }
392
393 static void vga_set_cursor( int top, int bottom ) {
394         outb(crtcport, 0x0a);
395         outb(crtcport + 1, (inb(crtcport + 1) & 0xc0) | top);
396         outb(crtcport, 0x0b);
397         outb(crtcport + 1, (inb(crtcport + 1) & 0xe0) | bottom);
398 }
399
400 static void vga_set_verticaldisplayend_lowbyte( int byte ) {
401         /* CRTC register 0x12 */
402         /* vertical display end */
403         outb(crtcport, 0x12);
404         outb(crtcport + 1, byte);
405 }
406
407 static void vga_480_scanlines() {
408         /* CRTC register 0x11 */
409         /* vertical sync end (also unlocks CR0-7) */
410         outb(crtcport, 0x11);
411         outb(crtcport + 1, 0x0c);
412
413         /* CRTC register 0x06 */
414         /* vertical total */
415         outb(crtcport, 0x06);
416         outb(crtcport + 1, 0x0b);
417
418         /* CRTC register 0x07 */
419         /* (vertical) overflow */
420         outb(crtcport, 0x07);
421         outb(crtcport + 1, 0x3e);
422
423         /* CRTC register 0x10 */
424         /* vertical sync start */
425         outb(crtcport, 0x10);
426         outb(crtcport + 1, 0xea);
427
428         /* CRTC register 0x12 */
429         /* vertical display end */
430         outb(crtcport, 0x12);
431         outb(crtcport + 1, 0xdf);
432
433         /* CRTC register 0x15 */
434         /* vertical blank start */
435         outb(crtcport, 0x15);
436         outb(crtcport + 1, 0xe7);
437
438         /* CRTC register 0x16 */
439         /* vertical blank end */
440         outb(crtcport, 0x16);
441         outb(crtcport + 1, 0x04);
442
443         /* Misc Output register */
444         /* Preserver clock select bits and set correct sync polarity */
445         outb(0x3c2, (inb(0x3cc) & 0x0d) | 0xe2);
446 }
447
448 static void vga_400_scanlines() {
449         /* CRTC register 0x11 */
450         /* vertical sync end (also unlocks CR0-7) */
451         outb(crtcport, 0x11);
452         outb(crtcport + 1, 0x0e);
453
454         /* CRTC register 0x06 */
455         /* vertical total */
456         outb(crtcport, 0x06);
457         outb(crtcport + 1, 0xbf);
458
459         /* CRTC register 0x07 */
460         /* (vertical) overflow */
461         outb(crtcport, 0x07);
462         outb(crtcport + 1, 0x1f);
463
464         /* CRTC register 0x10 */
465         /* vertical sync start */
466         outb(crtcport, 0x10);
467         outb(crtcport + 1, 0x9c);
468
469         /* CRTC register 0x12 */
470         /* vertical display end */
471         outb(crtcport, 0x12);
472         outb(crtcport + 1, 0x8f);
473
474         /* CRTC register 0x15 */
475         /* vertical blank start */
476         outb(crtcport, 0x15);
477         outb(crtcport + 1, 0x96);
478
479         /* CRTC register 0x16 */
480         /* vertical blank end */
481         outb(crtcport, 0x16);
482         outb(crtcport + 1, 0xb9);
483
484         /* Misc Output register */
485         /* Preserver clock select bits and set correct sync polarity */
486         outb(0x3c2, (inb(0x3cc) & 0x0d) | 0x62);
487 }