mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add chvideomode(1) using the /dev/video interface.
This commit is contained in:
parent
78f0c6c094
commit
ce43f9c306
2 changed files with 383 additions and 0 deletions
|
@ -18,6 +18,7 @@ pwd \
|
|||
help \
|
||||
uptime \
|
||||
memstat \
|
||||
chvideomode \
|
||||
uname \
|
||||
kernelinfo \
|
||||
idle \
|
||||
|
|
382
utils/chvideomode.cpp
Normal file
382
utils/chvideomode.cpp
Normal file
|
@ -0,0 +1,382 @@
|
|||
#include <sys/keycodes.h>
|
||||
#include <sys/termmode.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <readparamstring.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Add table
Reference in a new issue