This commit is contained in:
anon 2024-01-30 22:37:16 +01:00
commit 5abe469c43
13 changed files with 1010 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.out
object/*
.gdb_history
rc-tui

40
Makefile Executable file
View 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
View 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
View File

2
documentation/TODO.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
"rc-tui v0.9"