diff --git a/utils/Makefile b/utils/Makefile index 5c5ea2bd..6ab32fd1 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -18,6 +18,7 @@ pwd \ help \ uptime \ memstat \ +chvideomode \ uname \ kernelinfo \ idle \ diff --git a/utils/chvideomode.cpp b/utils/chvideomode.cpp new file mode 100644 index 00000000..c1aa0cd9 --- /dev/null +++ b/utils/chvideomode.cpp @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool SetCurrentMode(const char* mode) +{ + FILE* fp = fopen("/dev/video/mode", "w"); + if ( !fp ) { return false; } + if ( fprintf(fp, "%s\n", mode) < 0 ) { fclose(fp); return false; } + if ( fclose(fp) ) { return false; } + return true; +} + +char* GetCurrentMode() +{ + FILE* fp = fopen("/dev/video/mode", "r"); + if ( !fp ) { return NULL; } + char* mode = NULL; + size_t n = 0; + getline(&mode, &n, fp); + fclose(fp); + return mode; +} + +char** GetAvailableModes(size_t* nummodesptr) +{ + size_t modeslen = 0; + char** modes = new char*[modeslen]; + if ( !modes ) { return NULL; } + size_t nummodes = 0; + FILE* fp = fopen("/dev/video/modes", "r"); + if ( !fp ) { delete[] modes; return NULL; } + while ( true ) + { + char* mode = NULL; + size_t modesize = 0; + ssize_t modelen = getline(&mode, &modesize, fp); + if ( modelen <= 0 ) { break; } + if ( mode[modelen-1] == '\n' ) { mode[modelen-1] = 0; } + if ( nummodes == modeslen ) + { + size_t newmodeslen = modeslen ? 2 * modeslen : 16UL; + char** newmodes = new char*[newmodeslen]; + if ( !newmodes ) { free(mode); break; } + memcpy(newmodes, modes, nummodes * sizeof(char*)); + delete[] modes; modes = newmodes; + modeslen = newmodeslen; + } + modes[nummodes++] = mode; + } + fclose(fp); + *nummodesptr = nummodes; + return modes; +} + +void DrawMenu(size_t selection, char** modes, size_t nummodes) +{ + printf("\e[m\e[H\e[2K"); // Move to (0,0), clear screen. + printf("Please select one of these video modes or press ESC to abort.\n"); + size_t off = 1; // Above line + struct winsize ws; + if ( tcgetwinsize(1, &ws) != 0 ) + ws.ws_row = 25; + size_t amount = ws.ws_row-off; + size_t from = (selection / amount) * amount; + size_t howmanyavailable = nummodes - from; + size_t howmany = howmanyavailable < amount ? howmanyavailable : amount; + size_t to = from + howmany - 1; + for ( size_t i = from; i <= to; i++ ) + { + size_t screenline = 1 + off + i - from; + const char* color = i == selection ? "\e[31m" : "\e[m"; + printf("\e[%zuH%s\e[2K%zu\t%s", screenline, color, i, modes[i]); + } + printf("\e[m\e[J"); + fflush(stdout); +} + +int GetKey(int fd) +{ + unsigned int oldtermmode; + gettermmode(fd, &oldtermmode); + // Read the keyboard input from the user. + const unsigned termmode = TERMMODE_KBKEY + | TERMMODE_UNICODE + | TERMMODE_SIGNAL; + if ( settermmode(fd, termmode) ) { error(1, errno, "settermmode"); } + uint32_t codepoint; + ssize_t numbytes; + while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) + { + int kbkey = KBKEY_DECODE(codepoint); + if ( !kbkey ) { continue; } + return kbkey; + } + settermmode(fd, oldtermmode); + return 0; +} + +int GetKey(FILE* fp) { return GetKey(fileno(fp)); } + +struct Filter +{ + bool includeall; + bool includesupported; + bool includeunsupported; + bool includetext; + bool includegraphics; + size_t minbpp; + size_t maxbpp; + size_t minxres; + size_t maxxres; + size_t minyres; + size_t maxyres; + size_t minxchars; + size_t maxxchars; + size_t minychars; + size_t maxychars; +}; + +size_t ParseSizeT(const char* str, size_t def = 0) +{ + if ( !str || !*str ) + return def; + char* endptr; + size_t ret = strtoul(str, &endptr, 10); + if ( *endptr ) + return def; + return ret; +} + +bool ParseBool(const char* str, bool def = false) +{ + if ( !str || !*str ) + return def; + bool isfalse = !strcmp(str, "0") || !strcmp(str, "false"); + return !isfalse; +} + +bool PassesFilter(const char* modestr, Filter* filt) +{ + if ( filt->includeall ) { return true; } + char* widthstr = NULL; + char* heightstr = NULL; + char* bppstr = NULL; + char* unsupportedstr = NULL; + char* textstr = NULL; + if ( !ReadParamString(modestr, + "width", &widthstr, + "height", &heightstr, + "bpp", &bppstr, + "unsupported", &unsupportedstr, + "text", &textstr, + NULL) ) + error(1, errno, "Can't parse video mode: %s", modestr); + size_t width = ParseSizeT(widthstr, 0); delete[] widthstr; + size_t height = ParseSizeT(heightstr, 0); delete[] heightstr; + size_t bpp = ParseSizeT(bppstr, 0); delete[] bppstr; + bool unsupported = ParseBool(unsupportedstr); delete[] unsupportedstr; + bool supported = !unsupported; + bool text = ParseBool(textstr); delete[] textstr; + bool graphics = !text; + if ( unsupported && !filt->includeunsupported ) + return false; + if ( supported && !filt->includesupported ) + return false; + if ( text && !filt->includetext ) + return false; + if ( graphics && !filt->includegraphics ) + return false; + if ( graphics && (bpp < filt->minbpp || filt->maxbpp < bpp) ) + return false; + if ( graphics && (width < filt->minxres || filt->maxxres < width) ) + return false; + if ( graphics && (height < filt->minyres || filt->maxyres < height) ) + return false; + // TODO: Support filtering text modes according to columns/rows. + return true; +} + +void FilterModes(char** modes, size_t* nummodesptr, Filter* filt) +{ + size_t innum = *nummodesptr; + size_t outnum = 0; + for ( size_t i = 0; i < innum; i++ ) + { + if ( PassesFilter(modes[i], filt) ) + modes[outnum++] = modes[i]; + else + delete[] modes[i]; + } + *nummodesptr = outnum; +} + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION ...] [PROGRAM-TO-RUN [ARG ...]]\n", argv0); + fprintf(fp, "Changes the video mode and optionally runs a program\n"); + fprintf(fp, "\n"); + fprintf(fp, "Options supported by %s:\n", argv0); + fprintf(fp, " --help, --usage Display this help and exit\n"); + fprintf(fp, " --version Output version information and exit\n"); + fprintf(fp, "\n"); + fprintf(fp, "Options for filtering modes:\n"); + fprintf(fp, " --show-all BOOL\n"); + fprintf(fp, " --show-supported BOOL, --show-unsupported BOOL\n"); + fprintf(fp, " --show-text BOOL\n"); + fprintf(fp, " --show-graphics BOOL\n"); + fprintf(fp, " --bpp BPP, --min-bpp BPP, --max-bpp BPP\n"); + fprintf(fp, " --width NUM, --min-width NUM, --max-width NUM\n"); + fprintf(fp, " --height NUM, --min-height NUM, --max-height NUM\n"); + fprintf(fp, "\n"); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +bool SizeTParam(const char* name, const char* option, + const char* param, size_t* minvar, size_t* maxvar) +{ + char opt[64]; stpcpy(stpcpy(opt, "--"), name); + char minopt[64]; stpcpy(stpcpy(minopt, "--min-"), name); + char maxopt[64]; stpcpy(stpcpy(maxopt, "--max-"), name); + if ( !strcmp(option, opt) ) + *minvar = *maxvar = ParseSizeT(param); + else if ( !strcmp(option, minopt) ) + *minvar = ParseBool(param); + else if ( !strcmp(option, maxopt) ) + *maxvar = ParseBool(param); + else + return false; + return true; +} + +bool BoolParam(const char* name, const char* option, + const char* param, bool* var) +{ + char opt[64]; stpcpy(stpcpy(opt, "--"), name); + if ( !strcmp(option, opt) ) + *var = ParseBool(param); + else + return false; + return true; +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + + Filter filt; + filt.includeall = false; + filt.includesupported = true; + filt.includeunsupported = false; + filt.includetext = true; + filt.includegraphics = true; + // TODO: HACK: The kernel log printing requires either text mode or 32-bit + // graphics. For now, just filter away anything but 32-bit graphics. + filt.minbpp = 32; + filt.maxbpp = 32; + filt.minxres = 0; + filt.maxxres = SIZE_MAX; + filt.minyres = 0; + filt.maxyres = SIZE_MAX; + filt.minxchars = 0; + filt.maxxchars = SIZE_MAX; + filt.minychars = 0; + filt.maxychars = SIZE_MAX; + + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) { break; } + if ( !strcmp(arg, "--") ) { break; } + if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + if ( i == argc-1 ) + { + fprintf(stderr, "%s: missing parameter to %s\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + const char* p = argv[++i]; argv[i] = NULL; + bool handled = + BoolParam("show-all", arg, p, &filt.includeall) || + BoolParam("show-supported", arg, p, &filt.includesupported) || + BoolParam("show-unsupported", arg, p, &filt.includeunsupported) || + BoolParam("show-text", arg, p, &filt.includetext) || + BoolParam("show-graphics", arg, p, &filt.includegraphics) || + SizeTParam("bpp", arg, p, &filt.minbpp, &filt.maxbpp) || + SizeTParam("width", arg, p, &filt.minxres, &filt.maxxres) || + SizeTParam("height", arg, p, &filt.minyres, &filt.maxyres) || + false; + if ( !handled ) + { + fprintf(stderr, "%s: no such option: %s\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + char* prevmode = GetCurrentMode(); + if ( !prevmode ) { perror("Unable to detect current mode"); exit(1); } +#if 1 + size_t nummodes = 0; + char** modes = GetAvailableModes(&nummodes); + if ( !modes ) { perror("Unable to detect available video modes"); exit(1); } + + if ( !nummodes ) + { + fprintf(stderr, "No video modes are currently available.\n"); + fprintf(stderr, "Try make sure a driver exists and is activated.\n"); + exit(1); + } + + FilterModes(modes, &nummodes, &filt); + if ( !nummodes ) + { + fprintf(stderr, "\ +No video mode remains after filtering away unwanted modes.\n"); + fprintf(stderr, "\ +Try make sure the desired driver is loaded and is configured correctly.\n"); + exit(1); + } + + size_t selection = 0; + bool decided = false; + while ( !decided ) + { + DrawMenu(selection, modes, nummodes); + switch ( GetKey(stdin) ) + { + case KBKEY_ESC: printf("\n"); exit(10); break; + case KBKEY_UP: selection = (nummodes+selection-1) % nummodes; break; + case KBKEY_DOWN: selection = (selection+1) % nummodes; break; + case KBKEY_ENTER: decided = true; break; + } + } + + const char* mode = modes[selection]; + if ( !SetCurrentMode(mode) ) + { + error(1, errno, "Unable to set video mode: %s", mode); + } +#endif + if ( 1 < argc ) + { + pid_t childpid = fork(); + if ( childpid < 0 ) { perror("fork"); exit(1); } + if ( childpid ) + { + // TODO: Use the right WEXITSTATUS-ish macros here! + int status; + waitpid(childpid, &status, 0); + if ( !SetCurrentMode(prevmode) ) + { + error(1, errno, "Unable to restore video mode: %s", prevmode); + } + exit(status); + } + execvp(argv[1], argv + 1); + perror(argv[1]); + } + + return 0; +}