blob: 38b548fbde63ec2e381bf5b12506558c5bb171e6 [file] [log] [blame]
/*
* resizecons.c - change console video mode
*
* Version 1.00
*
* How to use this:
*
* 1. Get svgalib, make restoretextmode, put it somewhere in your path.
* 2. Put vga=ask in /etc/lilo/config, boot your machine a number of times,
* each time with a different vga mode, and run the command
* "restoretextmode -w COLSxROWS".
* For me this resulted in the files 80x25, 80x28, 80x50, 80x60, 100x40,
* 132x25, 132x28, 132x44. Put these files in /usr/lib/kbd/videomodes
* (or in your current dir).
* 3. Now "resizecons COLSxROWS" will change your video mode. (Assuming you
* have an appropriate kernel, and svgalib works for your video card.)
*
* Note: this is experimental, but it works for me. Comments are welcome.
* You may have to be root to get the appropriate ioperm permissions.
* It is not safe to make this program suid root.
*
* aeb@cwi.nl - 940924
*
* Harm Hanemaaijer added the -lines option, which reprograms the
* number of scanlines. He writes:
*
* Added -lines option, which reprograms the number of scanlines and
* the font height of the VGA hardware with register I/O, so that
* switching is possible between textmodes with different numbers
* of lines, in a VGA compatible way. It should work for 132 column
* modes also, except that number of columns cannot be changed.
*
* Standard VGA textmode uses a 400 scanline screen which is refreshed
* at 70 Hz. The following modes are supported that use this vertical
* resolution (C is the number of columns, usually 80 or 132).
*
* mode font height
* C x 25 16
* C x 28 14
* C x 36 11 (non-standard height)
* C x 44 9 (8-line fonts are a good match)
* C x 50 8
*
* The following modes are supported with a 480 scanline resolution,
* refresh at 60 Hz. Some not quite VGA compatible displays may not
* support this (it uses the same vertical timing as standard VGA
* 640x480x16 graphics mode).
*
* mode font height
* C x 30 16
* C x 34 14
* C x 40 12 (non-standard height)
* C x 60 8
*
* Two 12-line fonts are already in the consolefonts directory,
* namely lat1-12.psfu.gz and lat2-12.psfu.gz.
* For the 36 lines mode (11 line font), lat1-10.psfu.gz and lat2-10.psfu.gz
* can be used.
*
* hhanemaa@cs.ruu.nl - 941028
*
* Notes:
*
* In the consolefonts directory there is 'default8x9' font file but
* no 'default8x8'. Why is this? The standard VGA BIOS has an 8-line
* font, and they are much more common in SVGA modes (e.g. 50 and 60
* row modes). It is true that standard VGA textmode uses effectively
* 9 pixel wide characters, but that has nothing to do with the font
* data.
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/io.h>
#include <sys/ioctl.h>
#if (__GNU_LIBRARY__ >= 6)
#include <sys/perm.h>
#else
#include <linux/types.h>
#include <linux/termios.h>
#endif
#include <linux/vt.h>
#include "paths.h"
#include "getfd.h"
#include "findfile.h"
#include "nls.h"
#include "version.h"
#define MODE_RESTORETEXTMODE 0
#define MODE_VGALINES 1
static void usage(void);
/* VGA textmode register tweaking. */
static void vga_init_io(void);
static void vga_400_scanlines(void);
static void vga_480_scanlines(void);
static void vga_set_fontheight(int);
static int vga_get_fontheight(void);
static void vga_set_cursor(int, int);
static void vga_set_verticaldisplayend_lowbyte(int);
char *dirpath[] = { "", DATADIR "/" VIDEOMODEDIR "/", 0};
char *suffixes[] = { "", 0 };
int
main(int argc, char **argv) {
int rr, cc, fd, i, mode;
struct vt_sizes vtsizes;
struct vt_stat vtstat;
struct winsize winsize;
char *p;
char tty[12], cmd[80], infile[1024];
FILE *fin;
char *defaultfont;
set_progname(argv[0]);
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE_NAME, LOCALEDIR);
textdomain(PACKAGE_NAME);
if (argc < 2)
usage();
if (argc == 2 && !strcmp(argv[1], "-V"))
print_version_and_exit();
rr = 0; /* make gcc happy */
cc = atoi(argv[1]);
mode = MODE_RESTORETEXTMODE;
if (argc == 3 && strcmp(argv[1], "-lines") == 0) {
mode = MODE_VGALINES;
rr = atoi(argv[2]);
}
else
if (argc == 2 && (p = strchr(argv[1], 'x')) != 0)
rr = atoi(p+1);
else if(argc == 3)
rr = atoi(argv[2]);
else
usage();
if (mode == MODE_RESTORETEXTMODE) {
/* prepare for: restoretextmode -r 80x25 */
sprintf(infile, "%dx%d", cc, rr);
fin = findfile(infile, dirpath, suffixes);
if (!fin) {
fprintf(stderr, _("resizecons: cannot find videomode file %s\n"),
infile);
exit(1);
}
fpclose(fin);
}
fd = getfd(NULL);
if(ioctl(fd, TIOCGWINSZ, &winsize)) {
perror("TIOCGWINSZ");
exit(1);
}
if (mode == MODE_VGALINES) {
/* Get the number of columns. */
cc = winsize.ws_col;
if (rr != 25 && rr != 28 && rr !=30 && rr != 34 && rr != 36
&& rr != 40 && rr != 44 && rr != 50 && rr != 60) {
fprintf(stderr, _("Invalid number of lines\n"));
exit(1);
}
}
if(ioctl(fd, VT_GETSTATE, &vtstat)) {
perror("VT_GETSTATE");
exit(1);
}
vtsizes.v_rows = rr;
vtsizes.v_cols = cc;
vtsizes.v_scrollsize = 0;
vga_init_io(); /* maybe only if (mode == MODE_VGALINES) */
if(ioctl(fd, VT_RESIZE, &vtsizes)) {
perror("VT_RESIZE");
exit(1);
}
if (mode == MODE_VGALINES) {
/* Program the VGA registers. */
int scanlines_old;
int scanlines_new;
int fontheight;
if (winsize.ws_row == 25 || winsize.ws_row == 28 ||
winsize.ws_row == 36 || winsize.ws_row == 44 ||
winsize.ws_row == 50)
scanlines_old = 400;
else
scanlines_old = 480;
if (rr == 25 || rr == 28 || rr == 36 || rr == 44 || rr == 50)
scanlines_new = 400;
else
scanlines_new = 480;
/* Switch to 400 or 480 scanline vertical timing if required. */
if (scanlines_old != 400 && scanlines_new == 400)
vga_400_scanlines();
if (scanlines_old != 480 && scanlines_new == 480)
vga_480_scanlines();
switch (rr) {
case 25 : fontheight = 16; break;
case 28 : fontheight = 14; break;
case 30 : fontheight = 16; break;
case 34 : fontheight = 14; break;
case 36 : fontheight = 12; break;
case 40 : fontheight = 12; break;
case 44 : fontheight = 9; break;
case 50 : fontheight = 8; break;
case 60 : fontheight = 8; break;
default : fontheight = 8; break;
}
/* Set the VGA character height. */
vga_set_fontheight(fontheight);
/* Set the line offsets within a character cell of the cursor. */
if (fontheight >= 10)
vga_set_cursor(fontheight - 3, fontheight - 2);
else
vga_set_cursor(fontheight - 2, fontheight - 1);
/*
* If there are a few unused scanlines at the bottom of the
* screen, make sure they are not displayed (otherwise
* there is a annoying changing partial line at the bottom).
*/
vga_set_verticaldisplayend_lowbyte((fontheight * rr - 1) & 0xff);
printf(_("Old mode: %dx%d New mode: %dx%d\n"), winsize.ws_col,
winsize.ws_row, cc, rr);
printf(_("Old #scanlines: %d New #scanlines: %d Character height: %d\n"),
scanlines_old, scanlines_new, fontheight);
}
if (mode == MODE_RESTORETEXTMODE) {
/* do: restoretextmode -r 25x80 */
sprintf(cmd, "restoretextmode -r %s\n", pathname);
errno = 0;
if(system(cmd)) {
if(errno)
perror("restoretextmode");
fprintf(stderr, _("resizecons: the command `%s' failed\n"), cmd);
exit(1);
}
}
/*
* for i in /dev/tty[0-9] /dev/tty[0-9][0-9]
* do
* stty rows $rr cols $cc < $i
* done
* kill -SIGWINCH `cat /tmp/selection.pid`
*/
winsize.ws_row = rr;
winsize.ws_col = cc;
for (i=0; i<16; i++)
if (vtstat.v_state & (1<<i)) {
sprintf(tty, "/dev/tty%d", i);
fd = open(tty, O_RDONLY);
if (fd < 0 && errno == ENOENT) {
sprintf(tty, "/dev/vc/%d", i);
fd = open(tty, O_RDONLY);
}
if (fd >= 0) {
if(ioctl(fd, TIOCSWINSZ, &winsize))
perror("TIOCSWINSZ");
close(fd);
}
}
#if 0
/* Try to tell selection about the change */
/* [may be a security risk?] */
if ((fd = open("/tmp/selection.pid", O_RDONLY)) >= 0) {
char buf[64];
int n = read(fd, buf, sizeof(buf));
if (n > 0) {
int pid;
buf[n-1] = 0;
pid = atoi(buf);
kill(pid, SIGWINCH);
}
close(fd);
}
#endif
/* do: setfont default8x16 */
/* (other people might wish other fonts - this should be settable) */
/* We read the VGA font height register to be sure. */
/* There isn't much consistency in this. */
switch (vga_get_fontheight()) {
case 8 :
case 9 : defaultfont = "default8x9"; break;
case 10 : defaultfont = "lat1-10"; break;
case 11 :
case 12 : defaultfont = "lat1-12"; break;
case 13 :
case 14 : defaultfont = "iso01.14"; break;
case 15 :
case 16 :
default : defaultfont = "default8x16"; break;
}
sprintf(cmd, "setfont %s", defaultfont);
errno = 0;
if(system(cmd)) {
if(errno)
perror("setfont");
fprintf(stderr, "resizecons: the command `%s' failed\n", cmd);
exit(1);
}
fprintf(stderr, _("resizecons: don't forget to change TERM "
"(maybe to con%dx%d or linux-%dx%d)\n"),
cc, rr, cc, rr);
if (getenv("LINES") || getenv("COLUMNS"))
fprintf(stderr,
"Also the variables LINES and COLUMNS may need adjusting.\n");
return 0;
}
static void attr_noreturn
usage() {
fprintf(stderr,
_("resizecons:\n"
"call is: resizecons COLSxROWS or: resizecons COLS ROWS\n"
"or: resizecons -lines ROWS, with ROWS one of 25, 28, 30, 34,"
" 36, 40, 44, 50, 60\n"));
exit(1);
}
/*
* The code below is used only with the option `-lines ROWS', and is
* very hardware dependent, and requires root privileges.
*/
/* Port I/O macros. Note that these are not compatible with the ones */
/* defined in the kernel header files. */
static inline void my_outb( int port, int value )
{
__asm__ volatile ("outb %0,%1"
: : "a" ((unsigned char)value), "d" ((unsigned short)port));
}
static inline int my_inb( int port )
{
unsigned char value;
__asm__ volatile ("inb %1,%0"
: "=a" (value)
: "d" ((unsigned short)port));
return value;
}
/* VGA textmode register tweaking functions. */
static int crtcport;
static void vga_init_io() {
if (iopl(3) < 0) {
fprintf(stderr,
_("resizecons: cannot get I/O permissions.\n"));
exit(1);
}
crtcport = 0x3d4;
if ((my_inb(0x3cc) & 0x01) == 0)
crtcport = 0x3b4;
}
static void vga_set_fontheight( int h ) {
my_outb(crtcport, 0x09);
my_outb(crtcport + 1, (my_inb(crtcport + 1) & 0xe0) | (h - 1));
}
static int vga_get_fontheight() {
my_outb(crtcport, 0x09);
return (my_inb(crtcport + 1) & 0x1f) + 1;
}
static void vga_set_cursor( int top, int bottom ) {
my_outb(crtcport, 0x0a);
my_outb(crtcport + 1, (my_inb(crtcport + 1) & 0xc0) | top);
my_outb(crtcport, 0x0b);
my_outb(crtcport + 1, (my_inb(crtcport + 1) & 0xe0) | bottom);
}
static void vga_set_verticaldisplayend_lowbyte( int byte ) {
/* CRTC register 0x12 */
/* vertical display end */
my_outb(crtcport, 0x12);
my_outb(crtcport + 1, byte);
}
static void vga_480_scanlines() {
/* CRTC register 0x11 */
/* vertical sync end (also unlocks CR0-7) */
my_outb(crtcport, 0x11);
my_outb(crtcport + 1, 0x0c);
/* CRTC register 0x06 */
/* vertical total */
my_outb(crtcport, 0x06);
my_outb(crtcport + 1, 0x0b);
/* CRTC register 0x07 */
/* (vertical) overflow */
my_outb(crtcport, 0x07);
my_outb(crtcport + 1, 0x3e);
/* CRTC register 0x10 */
/* vertical sync start */
my_outb(crtcport, 0x10);
my_outb(crtcport + 1, 0xea);
/* CRTC register 0x12 */
/* vertical display end */
my_outb(crtcport, 0x12);
my_outb(crtcport + 1, 0xdf);
/* CRTC register 0x15 */
/* vertical blank start */
my_outb(crtcport, 0x15);
my_outb(crtcport + 1, 0xe7);
/* CRTC register 0x16 */
/* vertical blank end */
my_outb(crtcport, 0x16);
my_outb(crtcport + 1, 0x04);
/* Misc Output register */
/* Preserver clock select bits and set correct sync polarity */
my_outb(0x3c2, (my_inb(0x3cc) & 0x0d) | 0xe2);
}
static void vga_400_scanlines() {
/* CRTC register 0x11 */
/* vertical sync end (also unlocks CR0-7) */
my_outb(crtcport, 0x11);
my_outb(crtcport + 1, 0x0e);
/* CRTC register 0x06 */
/* vertical total */
my_outb(crtcport, 0x06);
my_outb(crtcport + 1, 0xbf);
/* CRTC register 0x07 */
/* (vertical) overflow */
my_outb(crtcport, 0x07);
my_outb(crtcport + 1, 0x1f);
/* CRTC register 0x10 */
/* vertical sync start */
my_outb(crtcport, 0x10);
my_outb(crtcport + 1, 0x9c);
/* CRTC register 0x12 */
/* vertical display end */
my_outb(crtcport, 0x12);
my_outb(crtcport + 1, 0x8f);
/* CRTC register 0x15 */
/* vertical blank start */
my_outb(crtcport, 0x15);
my_outb(crtcport + 1, 0x96);
/* CRTC register 0x16 */
/* vertical blank end */
my_outb(crtcport, 0x16);
my_outb(crtcport + 1, 0xb9);
/* Misc Output register */
/* Preserver clock select bits and set correct sync polarity */
my_outb(0x3c2, (my_inb(0x3cc) & 0x0d) | 0x62);
}