init
This commit is contained in:
commit
5abe469c43
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.out
|
||||
object/*
|
||||
.gdb_history
|
||||
rc-tui
|
40
Makefile
Executable file
40
Makefile
Executable file
@ -0,0 +1,40 @@
|
||||
.PHONY: clean run gdb
|
||||
|
||||
ifeq (${DEBUG}, 1)
|
||||
LFLAGS += --debug --trace
|
||||
CXXFLAGS += -Wall -Wextra -Wpedantic
|
||||
CXXFLAGS += -DDEBUG -O0 -ggdb -fno-inline
|
||||
WRAP := valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all
|
||||
else
|
||||
CXXFLAGS += -O3 -fno-stack-protector -fno-exceptions -fno-rtti
|
||||
endif
|
||||
|
||||
LDLIBS += -lfl $$(pkgconf --cflags --libs menu) $$(pkgconf --cflags --libs ncurses) $$(pkgconf --cflags --libs readline)
|
||||
CXXFLAGS += -std=gnu++20 -I./source/ -I./object/ -I./
|
||||
|
||||
OBJD:=object/
|
||||
SRCD:=source/
|
||||
SRC:=main.cpp tui.cpp
|
||||
SRC:=$(addprefix ${SRCD},${SRC}) ${OBJD}/lex.cpp
|
||||
OBJ:=$(subst .cpp,.o,$(subst ${SRCD},${OBJD},${SRC}))
|
||||
|
||||
OUTPUT:=rc-tui
|
||||
|
||||
main: lexer ${OBJ}
|
||||
${LINK.cpp} ${OBJ} -o ${OUTPUT} ${LDLIBS}
|
||||
|
||||
object/%.o: source/%.cpp
|
||||
${COMPILE.cpp} $< -o $@
|
||||
|
||||
lexer: source/rc_lexer.l
|
||||
${LEX} ${LFLAGS} --header-file=${OBJD}/rc_lexer.h -o ${OBJD}/lex.cpp ${SRCD}/rc_lexer.l
|
||||
|
||||
clean:
|
||||
-rm ${OBJD}/*
|
||||
-rm ./${OUTPUT}
|
||||
|
||||
run:
|
||||
./${OUTPUT}
|
||||
|
||||
gdb:
|
||||
sudo gdb --directory=./source -p $(shell pgrep ${OUTPUT})
|
44
config/colors.h
Normal file
44
config/colors.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef COLORS_H
|
||||
#define COLORS_H
|
||||
|
||||
#include <curses.h>
|
||||
|
||||
/* List of color options:
|
||||
COLOR_BLACK
|
||||
COLOR_RED
|
||||
COLOR_GREEN
|
||||
COLOR_YELLOW
|
||||
COLOR_BLUE
|
||||
COLOR_MAGENTA
|
||||
COLOR_CYAN
|
||||
COLOR_WHITE
|
||||
-1 // for transparent (only works if that is your default terminal background)
|
||||
*/
|
||||
|
||||
#define COLOR_STD_FG COLOR_WHITE
|
||||
#define COLOR_STD_BG COLOR_BLACK
|
||||
#define COLOR_SELECTION_FG COLOR_WHITE
|
||||
#define COLOR_SELECTION_BG COLOR_RED
|
||||
#define COLOR_CURSOR_FG COLOR_WHITE
|
||||
#define COLOR_CURSOR_BG COLOR_BLUE
|
||||
#define COLOR_HELP_FG COLOR_BLACK
|
||||
#define COLOR_HELP_BG COLOR_WHITE
|
||||
#define COLOR_WARNING_FG COLOR_BLACK
|
||||
#define COLOR_WARNING_BG COLOR_YELLOW
|
||||
#define COLOR_ERROR_FG COLOR_YELLOW
|
||||
#define COLOR_ERROR_BG COLOR_WHITE
|
||||
|
||||
|
||||
// ### DO NOT TOUCH !!! ###
|
||||
enum {
|
||||
COLOR_PAIR_STD = 1,
|
||||
COLOR_PAIR_SELECTION,
|
||||
COLOR_PAIR_CURSOR,
|
||||
COLOR_PAIR_HELP,
|
||||
COLOR_PAIR_WARNING,
|
||||
COLOR_PAIR_ERROR,
|
||||
};
|
||||
|
||||
#define easy_init_pair(x) init_pair(COLOR_PAIR_ ## x, COLOR_ ## x ## _FG, COLOR_ ## x ## _BG)
|
||||
|
||||
#endif
|
0
debug/.gitkeep
Normal file
0
debug/.gitkeep
Normal file
2
documentation/TODO.md
Normal file
2
documentation/TODO.md
Normal file
@ -0,0 +1,2 @@
|
||||
# TODO
|
||||
+ on every second call new\_nemu returns NULL with E\_NOT\_CONNECTED, which is bullshit `items` is passed correctly
|
100
source/Service.hpp
Normal file
100
source/Service.hpp
Normal file
@ -0,0 +1,100 @@
|
||||
#ifndef SERVICE_HPP
|
||||
#define SERVICE_HPP
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "tui.hpp"
|
||||
|
||||
#define SERVICE_MAX_STATUS_LEN 10
|
||||
#define SERVICE_MAX_RUNLEVEL_LEN 23
|
||||
extern size_t SERVICE_MAX_NAME_LEN;
|
||||
|
||||
struct Service {
|
||||
static constexpr const
|
||||
char** cmd[] = {
|
||||
(const char *[]){"restart", "stop", NULL},
|
||||
(const char *[]){"start", NULL}
|
||||
};
|
||||
static char **runlevels;
|
||||
|
||||
static std::map<Service*, const int> chld_table;
|
||||
|
||||
static void chld([[ maybe_unused ]] int ignore){
|
||||
pid_t pid;
|
||||
while((pid = waitpid(-1, NULL, 0)) > 0){
|
||||
auto i = Service::chld_table.begin();
|
||||
while(i != Service::chld_table.end()){
|
||||
if(i->second == pid){
|
||||
(*i).first->locked = false;
|
||||
Service::chld_table.erase(i);
|
||||
tui_draw();
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string name;
|
||||
std::string runlevel;
|
||||
std::string status;
|
||||
bool locked = false;
|
||||
|
||||
void change_status(const int st){
|
||||
if(this->locked){ return; }
|
||||
const char * const to_cmd = strdup(((char**)cmd[(bool)strcmp(this->status.c_str(), "[started]")])[st]);
|
||||
const char * const argv[] = {"rc-service", this->name.c_str(), to_cmd, NULL};
|
||||
|
||||
const int cp = fork();
|
||||
if(cp == -1){ return; }
|
||||
this->locked = true;
|
||||
if(cp == 0){
|
||||
// XXX: do a pause to avoid inter process race conditions
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
#if DEBUG == 1
|
||||
[[ maybe_unused ]] int iw = open("debug/debug.log", O_WRONLY | O_CREAT, 0644);
|
||||
[[ maybe_unused ]] int iv = open("debug/debug.log", O_WRONLY | O_CREAT, 0644);
|
||||
#endif
|
||||
execvpe(argv[0], (char**)argv, NULL);
|
||||
}else{
|
||||
Service::chld_table.emplace(this, cp);
|
||||
}
|
||||
}
|
||||
|
||||
void change_runlevel(const int rl) {
|
||||
const char * const argv[] = {"rc-update", "add", (char*)this->name.c_str(), Service::runlevels[rl], NULL};
|
||||
|
||||
const int cp = fork();
|
||||
if(cp == -1){ return; }
|
||||
if(cp == 0){
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
#if DEBUG == 1
|
||||
[[ maybe_unused ]] int iw = open("debug/debug.log", O_WRONLY | O_CREAT, 0644);
|
||||
[[ maybe_unused ]] int iv = open("debug/debug.log", O_WRONLY | O_CREAT, 0644);
|
||||
#endif
|
||||
execvpe(argv[0], (char**)argv, NULL);
|
||||
}else{
|
||||
Service::chld_table.emplace(this, cp);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void print(){
|
||||
printf("%p: %s is %s on %s\n", this, name.c_str(), status.c_str(), runlevel.c_str());
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
extern std::vector<Service*> services;
|
||||
extern std::vector<Service*> service_results;
|
||||
|
||||
typedef void (Service::*menu_callback_t)(int);
|
||||
|
||||
#endif
|
136
source/main.cpp
Normal file
136
source/main.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <ncurses.h>
|
||||
|
||||
#include "tui.hpp"
|
||||
#include "rc_lexer.h"
|
||||
|
||||
#include "opts.hpp"
|
||||
#include "Service.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline const char * const version =
|
||||
# include "version.inc"
|
||||
;
|
||||
|
||||
inline std::vector<Service*> services;
|
||||
inline std::vector<Service*> service_results;
|
||||
|
||||
std::map<Service*, const int> Service::chld_table;
|
||||
char **Service::runlevels;
|
||||
size_t SERVICE_MAX_NAME_LEN = 0;
|
||||
|
||||
bool is_root;
|
||||
|
||||
bool init();
|
||||
[[ noreturn ]] void quit([[ maybe_unused ]] int ignore);
|
||||
|
||||
// ###
|
||||
|
||||
signed main(const int argc, const char* argv[]){
|
||||
opts(argc, argv);
|
||||
|
||||
bool running = init();
|
||||
if(not running){ return 1; }
|
||||
|
||||
tui_redraw();
|
||||
while(true){
|
||||
if(tui_control(wgetch(stdscr))){
|
||||
tui_draw();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool init(){
|
||||
// ### Get root status
|
||||
is_root = (getuid() == 0);
|
||||
|
||||
// ### Read available runlevels ###
|
||||
/* XXX: This operation could be safer by allocating the first time enties are read,
|
||||
* because now if the directory is modified between the 2 loops the application
|
||||
* may not pick up all run leves / crash.
|
||||
* However, its such an edge case that i cant be bothered. DIY.
|
||||
*/
|
||||
#define runleveld "/etc/runlevels/"
|
||||
DIR * d = opendir(runleveld);
|
||||
if (not d) {
|
||||
fputs("Error reading runlevel list from '" runleveld "'.\n", stderr);
|
||||
}
|
||||
dirent * entry;
|
||||
int rlc = -2; // NOTE: "." and ".." will always be present
|
||||
while ((entry = readdir(d)) != NULL) {
|
||||
++rlc;
|
||||
}
|
||||
Service::runlevels = (char**)malloc((sizeof(char*) * rlc) + 1);
|
||||
rewinddir(d);
|
||||
for (int i = 0; (entry = readdir(d)) != NULL;) {
|
||||
if (not strcmp(entry->d_name, ".")
|
||||
or not strcmp(entry->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
Service::runlevels[i] = strdup(entry->d_name);
|
||||
++i;
|
||||
}
|
||||
Service::runlevels[rlc] = NULL;
|
||||
closedir(d);
|
||||
|
||||
// ### Read rc-status ###
|
||||
int fd[2];
|
||||
int cp;
|
||||
char buf;
|
||||
const char * const argv[] = {"rc-status", "--all", "-f", "ini", NULL};
|
||||
string rcoutput;
|
||||
|
||||
if(pipe(fd) == -1){ return false; }
|
||||
cp = fork();
|
||||
if(cp == -1){ return false; }
|
||||
if(cp == 0){
|
||||
dup2(fd[1], STDOUT_FILENO);
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
execvpe(argv[0], (char**)argv, NULL);
|
||||
}else{
|
||||
close(fd[1]);
|
||||
while(read(fd[0], &buf, 1)){
|
||||
rcoutput += buf;
|
||||
}
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
// ### Lex rc-status output ###
|
||||
auto buffer = yy_scan_string(rcoutput.c_str());
|
||||
yylex();
|
||||
yy_delete_buffer(buffer);
|
||||
|
||||
service_results.resize(services.size());
|
||||
copy(services.begin(), services.end(), service_results.begin());
|
||||
|
||||
// ### Handle signals ###
|
||||
signal(SIGABRT, quit);
|
||||
signal(SIGINT, quit);
|
||||
signal(SIGSEGV, quit);
|
||||
signal(SIGTERM, quit);
|
||||
signal(SIGCHLD, Service::chld);
|
||||
//signal(SIGWINCH, tui_redraw);
|
||||
|
||||
// ### Init ncurses ###
|
||||
if(not tui_init()){ return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[ noreturn ]] void yyerror(char* str){
|
||||
printf("\nFlex scanner jammed on: %s\n", str);
|
||||
raise(SIGABRT);
|
||||
}
|
||||
|
||||
[[ noreturn ]] void quit([[ maybe_unused ]]int ignore){
|
||||
tui_quit();
|
||||
exit(1);
|
||||
}
|
40
source/opts.hpp
Normal file
40
source/opts.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef OPTS_HPP
|
||||
#define OPTS_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern const char * const version;
|
||||
|
||||
void usage() {
|
||||
printf(
|
||||
"%s\n"
|
||||
"Interactive terminal interface for managing open-rc services.\n"
|
||||
"rc-rui <options>\n"
|
||||
"\t-v --version : print verion and quit\n"
|
||||
"\t-h --help : print help and quit\n",
|
||||
version
|
||||
);
|
||||
}
|
||||
|
||||
void opts(int argc, const char * * argv) {
|
||||
(void)argc;
|
||||
for (const char * * opt = argv + 1; *opt != NULL; opt++) {
|
||||
if (not strcmp(*opt, "-h")
|
||||
or not strcmp(*opt, "--help")) {
|
||||
usage();
|
||||
exit(0);
|
||||
}
|
||||
if (not strcmp(*opt, "-v")
|
||||
or not strcmp(*opt, "--version")) {
|
||||
puts(version);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printf("Unknown option: '%s'.\n", *opt);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
43
source/rc_lexer.l
Normal file
43
source/rc_lexer.l
Normal file
@ -0,0 +1,43 @@
|
||||
%{
|
||||
/* This lexer parses INI format output by rc-status
|
||||
( rc-status -f ini --all )
|
||||
*/
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Service.hpp"
|
||||
static std::string runlevel;
|
||||
extern std::vector<Service*> services;
|
||||
void yyerror(char* str);
|
||||
%}
|
||||
%option noyywrap
|
||||
%option nodefault
|
||||
%x STATUS SWALLOW
|
||||
|
||||
identifier [[:alnum:]]([[:alnum:]]|[-_ /])*[[:alnum:]]
|
||||
|
||||
%%
|
||||
|
||||
"["{identifier}"]" {
|
||||
//printf("new runlevel: %s\n", yytext);
|
||||
runlevel = yytext;
|
||||
}
|
||||
^{identifier} {
|
||||
//printf("service found: %s", yytext);
|
||||
services.push_back(new Service);
|
||||
services.back()->name = yytext;
|
||||
services.back()->runlevel = runlevel;
|
||||
}
|
||||
= { BEGIN STATUS;
|
||||
}
|
||||
\n { ; }
|
||||
<STATUS>[[:alpha:]]+ { BEGIN SWALLOW;
|
||||
//printf(" whichs status is: %s\n", yytext);
|
||||
services.back()->status = yytext;
|
||||
services.back()->status = '[' + services.back()->status + ']';
|
||||
}
|
||||
<SWALLOW>. { ; }
|
||||
<SWALLOW>\n { BEGIN INITIAL; }
|
||||
<INITIAL,STATUS>[ \t]* { ; }
|
||||
<INITIAL,STATUS>. { yyerror(yytext); }
|
||||
|
||||
%%
|
84
source/search.hpp
Normal file
84
source/search.hpp
Normal file
@ -0,0 +1,84 @@
|
||||
#ifndef SEARCH_HPP
|
||||
#define SEARCH_HPP
|
||||
|
||||
#include "Service.hpp"
|
||||
#include <vector>
|
||||
|
||||
void search(std::vector<Service*> &list, const char * const &query) {
|
||||
for (int i = list.size()-1; i > -1; i--) {
|
||||
if (list[i]->name.find(query) == std::string::npos) {
|
||||
list.erase(list.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// https://github.com/Meteorix/pylcs
|
||||
|
||||
vector<string> utf8_split(const string &str){
|
||||
vector<string> split;
|
||||
int len = str.length();
|
||||
int left = 0;
|
||||
int right = 1;
|
||||
|
||||
for (int i = 0; i < len; i++){
|
||||
if (right >= len || ((str[right] & 0xc0) != 0x80)){
|
||||
string s = str.substr(left, right - left);
|
||||
split.push_back(s);
|
||||
// printf("%s %d %d\n", s.c_str(), left, right);
|
||||
left = right;
|
||||
}
|
||||
right ++;
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
// https://github.com/schiffma/distlib
|
||||
int mini(int a, int b, int c){
|
||||
return(min(a, min(b,c)));
|
||||
}
|
||||
|
||||
int levenshtein_dist(const string &word1, const string &word2){
|
||||
///
|
||||
/// Please use lower-case strings
|
||||
/// word1 : first word
|
||||
/// word2 : second word
|
||||
///
|
||||
|
||||
//int size1 = word1.size(), size2 = word2.size();
|
||||
|
||||
vector<string> word1_ = utf8_split(word1);
|
||||
vector<string> word2_ = utf8_split(word2);
|
||||
int size1 = word1_.size();
|
||||
int size2 = word2_.size();
|
||||
|
||||
int suppr_dist, insert_dist, subs_dist;
|
||||
int* dist = new int[(size1+1)*(size2+1)];
|
||||
|
||||
for(int i=0; i<size1+1; ++i)
|
||||
dist[(size2+1)*i] = i;
|
||||
for(int j=0; j<size2+1; ++j)
|
||||
dist[j] = j;
|
||||
for(int i=1; i<size1+1; ++i){
|
||||
for(int j=1; j<size2+1; ++j){
|
||||
suppr_dist = dist[(size2+1)*(i-1)+j] + 1;
|
||||
insert_dist = dist[(size2+1)*i+j-1] + 1;
|
||||
subs_dist = dist[(size2+1)*(i-1)+j-1];
|
||||
if(word1_[i-1]!=word2_[j-1]){ // word indexes are implemented differently.
|
||||
subs_dist += 1;
|
||||
}
|
||||
dist[(size2+1)*i+j] = mini(suppr_dist, insert_dist, subs_dist);
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
int res = dist[(size1+1)*(size2+1) - 1];
|
||||
delete dist;
|
||||
return(res);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
505
source/tui.cpp
Normal file
505
source/tui.cpp
Normal file
@ -0,0 +1,505 @@
|
||||
#include "tui.hpp"
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <ncurses.h>
|
||||
#include <menu.h>
|
||||
#include <readline/readline.h>
|
||||
|
||||
#include "Service.hpp"
|
||||
#include "search.hpp"
|
||||
|
||||
#include "config/colors.h"
|
||||
|
||||
#ifndef ESC
|
||||
# define ESC '\033'
|
||||
#endif
|
||||
#ifndef ENTER
|
||||
# define ENTER '\r'
|
||||
#endif
|
||||
|
||||
#define TUI_BANNER "| Open-rc TUI |"
|
||||
|
||||
extern bool is_root;
|
||||
|
||||
template<typename T>
|
||||
struct array {
|
||||
T* elements;
|
||||
int size;
|
||||
|
||||
array() {
|
||||
}
|
||||
|
||||
array(int n) : size(n) {
|
||||
elements = new T[n];
|
||||
}
|
||||
|
||||
~array() {
|
||||
delete[] this->elements;
|
||||
}
|
||||
|
||||
T& operator[](int i) {
|
||||
return elements[i];
|
||||
}
|
||||
};
|
||||
|
||||
static bool tui_running;
|
||||
|
||||
enum {
|
||||
CUR_RUNLEVEL,
|
||||
CUR_STATUS,
|
||||
CUR_ENUM_END,
|
||||
};
|
||||
static size_t cursor = CUR_STATUS;
|
||||
static size_t selection = 0;
|
||||
|
||||
enum {
|
||||
STATE_INITIAL,
|
||||
STATE_SEARCH,
|
||||
STATE_CMD_MENU,
|
||||
};
|
||||
static size_t state = STATE_INITIAL;
|
||||
|
||||
static WINDOW * wmain;
|
||||
static WINDOW * wmaind;
|
||||
static WINDOW * wmenu;
|
||||
static WINDOW * wmenud;
|
||||
static WINDOW * whelpbar;
|
||||
static WINDOW * wsearchbar;
|
||||
static MENU * cmd_menu;
|
||||
|
||||
static array<ITEM*> runlevel_options;
|
||||
static array<ITEM*> status_options[2];
|
||||
|
||||
static menu_callback_t menu_callback;
|
||||
|
||||
|
||||
static inline size_t windoww(WINDOW* w){
|
||||
return (w->_maxx+1 - w->_begx)+1;
|
||||
}
|
||||
static inline size_t windowh(WINDOW* w){
|
||||
return (w->_maxy+1 - w->_begy+w->_yoffset)+1;
|
||||
}
|
||||
|
||||
static size_t top = 0;
|
||||
|
||||
static int input_available = false;
|
||||
static char input;
|
||||
|
||||
inline int option_list_to_items(ITEM** &items, const char * const * const &options) {
|
||||
int n = 0;
|
||||
while(options[n] != NULL) {
|
||||
++n;
|
||||
}
|
||||
items = (ITEM**)malloc((n+1) * sizeof(ITEM*));
|
||||
for (int i = 0; i < n; i++) {
|
||||
items[i] = new_item(options[i], "");
|
||||
}
|
||||
items[n] = NULL;
|
||||
return n;
|
||||
}
|
||||
|
||||
bool tui_init(){
|
||||
// Prerequizet info
|
||||
for(auto i : services){
|
||||
if(i->name.size() > SERVICE_MAX_NAME_LEN){
|
||||
SERVICE_MAX_NAME_LEN = i->name.size();
|
||||
}
|
||||
}
|
||||
|
||||
runlevel_options.size = option_list_to_items(runlevel_options.elements, Service::runlevels);
|
||||
status_options[0].size = option_list_to_items(status_options[0].elements, Service::cmd[0]);
|
||||
status_options[1].size = option_list_to_items(status_options[1].elements, Service::cmd[1]);
|
||||
|
||||
// NCurses
|
||||
initscr();
|
||||
|
||||
nonl();
|
||||
noecho();
|
||||
curs_set(0);
|
||||
|
||||
start_color();
|
||||
easy_init_pair(STD);
|
||||
easy_init_pair(SELECTION);
|
||||
easy_init_pair(CURSOR);
|
||||
easy_init_pair(HELP);
|
||||
easy_init_pair(WARNING);
|
||||
easy_init_pair(ERROR);
|
||||
|
||||
wmain = newwin(LINES-2, COLS, 0, 0);
|
||||
wmaind = derwin(wmain, wmain->_maxy-1, wmain->_maxx-1, 1, 1);
|
||||
whelpbar = newwin(1, COLS, LINES-1, 0);
|
||||
wsearchbar = newwin(1, COLS, LINES-2, 0);
|
||||
|
||||
refresh();
|
||||
|
||||
wbkgd(wmain, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
wbkgd(wmaind, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
wbkgd(whelpbar, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
wbkgd(wsearchbar, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
|
||||
// ReadLine
|
||||
rl_bind_key('\t', rl_insert); // make tab insert itself
|
||||
rl_catch_signals = 0; // do not install signal handlers
|
||||
rl_catch_sigwinch = 0; // do not care about window change signals
|
||||
rl_prep_term_function = NULL; // do not initialize the ternimal
|
||||
rl_deprep_term_function = NULL; // do not clean up
|
||||
rl_change_environment = 0; // ?!
|
||||
rl_getc_function = +[]([[ maybe_unused ]] FILE* ignore){
|
||||
input_available = false;
|
||||
return (int)input;
|
||||
};
|
||||
rl_input_available_hook = +[]{
|
||||
return input_available;
|
||||
};
|
||||
rl_redisplay_function = +[]{
|
||||
wmove(wsearchbar, 0, 1);
|
||||
wclrtoeol(wsearchbar);
|
||||
waddstr(wsearchbar, rl_line_buffer);
|
||||
wrefresh(wsearchbar);
|
||||
return;
|
||||
};
|
||||
rl_callback_handler_install("", +[]([[ maybe_unused ]] char *line){
|
||||
return;
|
||||
});
|
||||
|
||||
tui_running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void tui_quit(){
|
||||
if(not tui_running){ return; }
|
||||
|
||||
delwin(wsearchbar);
|
||||
delwin(whelpbar);
|
||||
delwin(wmenud);
|
||||
delwin(wmenu);
|
||||
delwin(wmaind);
|
||||
delwin(wmain);
|
||||
endwin();
|
||||
|
||||
tui_running = false;
|
||||
}
|
||||
|
||||
static char* tui_render_service(const Service* const s, const size_t &width){
|
||||
char* r;
|
||||
|
||||
static const char l[] = " Locked";
|
||||
auto l_spaceout = []() constexpr {
|
||||
char* const r = (char*)malloc(sizeof(l));
|
||||
memset(r, ' ', sizeof(l)-1);
|
||||
r[sizeof(l)-1] = '\00';
|
||||
return r;
|
||||
};
|
||||
const char* const lstr = (s->locked ? l : l_spaceout());
|
||||
|
||||
int err = asprintf(
|
||||
&r,
|
||||
"%-*s%s%*s%*s%*s", /* name,<locked>,<space_padding>,runlevel,status */
|
||||
(int)SERVICE_MAX_NAME_LEN,
|
||||
s->name.c_str(),
|
||||
lstr,
|
||||
(int)(width-
|
||||
(SERVICE_MAX_NAME_LEN
|
||||
+ (sizeof(l)-1)
|
||||
+ SERVICE_MAX_RUNLEVEL_LEN
|
||||
+ SERVICE_MAX_STATUS_LEN+1)
|
||||
),
|
||||
" ",
|
||||
SERVICE_MAX_RUNLEVEL_LEN,
|
||||
s->runlevel.c_str(),
|
||||
SERVICE_MAX_STATUS_LEN+1,
|
||||
s->status.c_str()
|
||||
);
|
||||
|
||||
if (err == -1) {
|
||||
abort();
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static size_t tui_rendered_service_button_pos(const Service* const s, size_t width){
|
||||
size_t starts[] = {
|
||||
width-(s->status.size()+1 + s->runlevel.size()+1),
|
||||
width - (s->status.size())
|
||||
};
|
||||
return starts[cursor];
|
||||
}
|
||||
|
||||
static void tui_render_services(){
|
||||
wmove(wmaind, 0, 0);
|
||||
int winw = windoww(wmaind);
|
||||
const size_t t = std::min(service_results.size() - top, (size_t)windowh(wmaind));
|
||||
for(size_t i = top; i < (top + t); i++){
|
||||
char* buf = tui_render_service(service_results[i], winw);
|
||||
if(i == selection) [[ unlikely ]] {
|
||||
size_t cur_start,
|
||||
cur_len;
|
||||
cur_start = tui_rendered_service_button_pos(service_results[i], winw);
|
||||
switch(cursor){
|
||||
case CUR_RUNLEVEL:
|
||||
cur_len = service_results[i]->runlevel.size();
|
||||
break;
|
||||
case CUR_STATUS:
|
||||
cur_len = service_results[i]->status.size();
|
||||
break;
|
||||
}
|
||||
// Print until cursor
|
||||
wattron(wmaind, A_BOLD | COLOR_PAIR(COLOR_PAIR_SELECTION));
|
||||
waddnstr(wmaind, buf, cur_start);
|
||||
wattroff(wmaind, COLOR_PAIR(COLOR_PAIR_SELECTION));
|
||||
// Print cursor
|
||||
wattron(wmaind, COLOR_PAIR(COLOR_PAIR_CURSOR));
|
||||
waddnstr(wmaind, buf+cur_start, cur_len);
|
||||
wattroff(wmaind, COLOR_PAIR(COLOR_PAIR_CURSOR));
|
||||
// Print remainder
|
||||
wattron(wmaind, COLOR_PAIR(COLOR_PAIR_SELECTION));
|
||||
waddstr(wmaind, buf+cur_start+cur_len);
|
||||
wattroff(wmaind, A_BOLD | COLOR_PAIR(COLOR_PAIR_SELECTION));
|
||||
}else [[ likely ]] {
|
||||
waddstr(wmaind, buf);
|
||||
}
|
||||
delete buf;
|
||||
}
|
||||
if ((service_results.size() - top) < windowh(wmaind)) {
|
||||
wclrtobot(wmaind);
|
||||
}
|
||||
}
|
||||
|
||||
static const char NOT_ROOT_MSG[] = " WARNING: NOT RUNNING AS ROOT! ";
|
||||
static const char * const * const HELP_MSG[] = {
|
||||
[STATE_INITIAL] = (char const *[]){"Down [j]", "Up [k]", "Next [h]", "Previous [l]", "Modify [\\n]", "Search [/]", "Quit [q]", NULL},
|
||||
[STATE_SEARCH] = (char const *[]){"Browse [\\n]", "Cancel [ESC]", NULL},
|
||||
[STATE_CMD_MENU] = (char const *[]){"Down [j]", "Up [k]", "Select [\\n]", "Cancel [ESC]", NULL},
|
||||
};
|
||||
static void tui_render_help(){
|
||||
werase(whelpbar);
|
||||
for(int i = 0; HELP_MSG[state][i] != NULL; i++){
|
||||
waddch(whelpbar, ' ');
|
||||
wattron(whelpbar, COLOR_PAIR(COLOR_PAIR_HELP));
|
||||
waddstr(whelpbar, HELP_MSG[state][i]);
|
||||
wattroff(whelpbar, COLOR_PAIR(COLOR_PAIR_HELP));
|
||||
}
|
||||
|
||||
if (not is_root) {
|
||||
wattron(whelpbar, COLOR_PAIR(COLOR_PAIR_WARNING));
|
||||
mvwaddstr(whelpbar, 0, COLS - sizeof(NOT_ROOT_MSG), NOT_ROOT_MSG);
|
||||
wattroff(whelpbar, COLOR_PAIR(COLOR_PAIR_WARNING));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void make_menu(const int w, int y, const int x, const int flip_above, array<ITEM*> &items, menu_callback_t callback) {
|
||||
menu_callback = callback;
|
||||
|
||||
if (flip_above < items.size+2) {
|
||||
y = y - (items.size+2) - 1;
|
||||
}
|
||||
|
||||
wmenu = newwin(items.size+2, w, y, x);
|
||||
wmenud = derwin(wmenu, items.size, w-2, 1, 1);
|
||||
|
||||
wbkgd(wmenu, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
wbkgd(wmenud, COLOR_PAIR(COLOR_PAIR_STD));
|
||||
|
||||
cmd_menu = new_menu(items.elements);
|
||||
|
||||
set_menu_win(cmd_menu, wmenu);
|
||||
set_menu_sub(cmd_menu, wmenud);
|
||||
post_menu(cmd_menu);
|
||||
}
|
||||
|
||||
static inline bool tui_control_search(const char &c){
|
||||
switch(c) {
|
||||
case ESC: {
|
||||
state = STATE_INITIAL;
|
||||
rl_end = 0;
|
||||
rl_point = 0;
|
||||
wmove(wsearchbar, 0, 0);
|
||||
wclrtoeol(wsearchbar);
|
||||
wrefresh(wsearchbar);
|
||||
service_results.resize(services.size());
|
||||
copy(services.begin(), services.end(), service_results.begin());
|
||||
} return true;
|
||||
case ENTER: {
|
||||
state = STATE_INITIAL;
|
||||
} return true;
|
||||
default: {
|
||||
input = c;
|
||||
input_available = true;
|
||||
int cache = rl_end;
|
||||
rl_callback_read_char();
|
||||
if (cache > rl_end) { // erasing occured
|
||||
service_results.resize(services.size());
|
||||
copy(services.begin(), services.end(), service_results.begin());
|
||||
}
|
||||
search(service_results, rl_line_buffer);
|
||||
if (selection > service_results.size()-1) {
|
||||
selection = service_results.size()-1;
|
||||
}
|
||||
} return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tui_control_menu(const char &c){
|
||||
switch(c) {
|
||||
case 'j':
|
||||
menu_driver(cmd_menu, REQ_DOWN_ITEM);
|
||||
return true;
|
||||
case 'k':
|
||||
menu_driver(cmd_menu, REQ_UP_ITEM);
|
||||
return true;
|
||||
case ESC:
|
||||
state = STATE_INITIAL;
|
||||
delwin(wmenu);
|
||||
tui_redraw();
|
||||
return true;
|
||||
case ENTER:
|
||||
(services[selection]->*menu_callback)(item_index(current_item(cmd_menu)));
|
||||
state = STATE_INITIAL;
|
||||
free_menu(cmd_menu);
|
||||
delwin(wmenud);
|
||||
delwin(wmenu);
|
||||
tui_redraw();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool tui_control_root(const char &c) {
|
||||
switch(c){
|
||||
case 'j':
|
||||
if (selection < services.size()-1) {
|
||||
++selection;
|
||||
if (selection >= (top + windowh(wmaind))) {
|
||||
++top;
|
||||
}
|
||||
}else{
|
||||
selection = 0;
|
||||
top = 0;
|
||||
}
|
||||
return true;
|
||||
case 'k':
|
||||
if (selection > 0) {
|
||||
if (selection == top) {
|
||||
--top;
|
||||
}
|
||||
--selection;
|
||||
} else {
|
||||
if (top > 0) {
|
||||
--top;
|
||||
} else {
|
||||
selection = services.size()-1;
|
||||
top = services.size() - windowh(wmaind);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case 'h':
|
||||
if (cursor != 0) {
|
||||
--cursor;
|
||||
} else {
|
||||
cursor = CUR_ENUM_END-1;
|
||||
}
|
||||
return true;
|
||||
case 'l':
|
||||
++cursor;
|
||||
if (cursor == CUR_ENUM_END) {
|
||||
cursor = 0;
|
||||
}
|
||||
return true;
|
||||
case 'q':
|
||||
abort();
|
||||
case '/':
|
||||
case '?':
|
||||
state = STATE_SEARCH;
|
||||
mvwaddch(wsearchbar, 0, 0, '/');
|
||||
return true;
|
||||
case '\r':
|
||||
if (not is_root) {
|
||||
wattron(whelpbar, COLOR_PAIR(COLOR_PAIR_ERROR));
|
||||
mvwaddstr(whelpbar, 0, COLS - sizeof(NOT_ROOT_MSG), NOT_ROOT_MSG);
|
||||
wattroff(whelpbar, COLOR_PAIR(COLOR_PAIR_ERROR));
|
||||
wrefresh(whelpbar);
|
||||
refresh();
|
||||
auto tmp = (const struct timespec){.tv_sec = 0, .tv_nsec = 100'000'000};
|
||||
nanosleep(&tmp, NULL);
|
||||
flushinp();
|
||||
return true;
|
||||
}
|
||||
state = STATE_CMD_MENU;
|
||||
array<ITEM*> * options;
|
||||
if (cursor == CUR_RUNLEVEL) {
|
||||
options = &runlevel_options;
|
||||
} else if (cursor == CUR_STATUS) {
|
||||
options = &status_options[services[selection]->status != "[started]"];
|
||||
}
|
||||
const int mstartx = tui_rendered_service_button_pos(services[selection], windoww(wmaind));
|
||||
const int mstarty = wmaind->_begy + selection+1;
|
||||
const int mwidth = (
|
||||
cursor
|
||||
?
|
||||
services[selection]->status
|
||||
:
|
||||
services[selection]->runlevel
|
||||
).size() + 2;
|
||||
const int lines_avail = windowh(wmaind) - (selection - top);
|
||||
make_menu(mwidth, mstarty, mstartx, lines_avail, *options, &Service::change_status);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tui_control(const int &c){
|
||||
if (c == KEY_RESIZE) {
|
||||
tui_resize();
|
||||
flushinp();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch(state){
|
||||
case STATE_INITIAL:
|
||||
return tui_control_root(c);
|
||||
case STATE_SEARCH:
|
||||
return tui_control_search(c);
|
||||
case STATE_CMD_MENU:
|
||||
return tui_control_menu(c);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void tui_redraw(){
|
||||
box(wmain, 0, 0);
|
||||
mvwaddstr(wmain, 0, (COLS-(sizeof(TUI_BANNER)-1))/2, TUI_BANNER);
|
||||
wrefresh(wmain);
|
||||
|
||||
tui_draw();
|
||||
}
|
||||
|
||||
void tui_draw(){
|
||||
tui_render_services();
|
||||
wrefresh(wmaind);
|
||||
|
||||
tui_render_help();
|
||||
wrefresh(whelpbar);
|
||||
|
||||
if(state == STATE_CMD_MENU){
|
||||
wborder(wmenu, '|', '|', '=', '=', 'O', 'O', 'O', 'O');
|
||||
wrefresh(wmenu);
|
||||
wrefresh(wmenud);
|
||||
}
|
||||
|
||||
if (state == STATE_SEARCH) {
|
||||
wrefresh(wsearchbar);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
inline void tui_resize() {
|
||||
tui_quit();
|
||||
tui_init();
|
||||
tui_redraw();
|
||||
}
|
11
source/tui.hpp
Normal file
11
source/tui.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef TUI_HPP
|
||||
#define TUI_HPP
|
||||
|
||||
extern bool tui_init();
|
||||
extern void tui_quit();
|
||||
extern bool tui_control(const int &c);
|
||||
extern void tui_redraw();
|
||||
extern void tui_draw();
|
||||
extern inline void tui_resize();
|
||||
|
||||
#endif
|
1
source/version.inc
Normal file
1
source/version.inc
Normal file
@ -0,0 +1 @@
|
||||
"rc-tui v0.9"
|
Loading…
x
Reference in New Issue
Block a user