mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
382 lines
10 KiB
C++
382 lines
10 KiB
C++
#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;
|
|
}
|