mirror of https://github.com/fairyglade/ly.git
Add projects
This commit is contained in:
parent
825d1cf9cf
commit
7595e3f905
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
obj
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,44 @@
|
|||
NAME = test
|
||||
CC = gcc
|
||||
FLAGS = -std=c99 -pedantic -g
|
||||
FLAGS+= -Wall -Wextra -Werror=vla -Werror -Wno-unused-parameter
|
||||
|
||||
BIND = bin
|
||||
OBJD = obj
|
||||
SUBD = sub
|
||||
SRCD = src
|
||||
TEST = test
|
||||
|
||||
BINS = $(BIND)/argoat_sample_1
|
||||
BINS+= $(BIND)/argoat_sample_2
|
||||
BINS+= $(BIND)/argoat_sample_3
|
||||
|
||||
INCL = -I$(SRCD) -I$(SUBD)/testoasterror/src
|
||||
DEP = $(SUBD)/testoasterror/src/testoasterror.h
|
||||
|
||||
$(OBJD)/%.o: %.c
|
||||
@echo "building object $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
all: $(DEP) $(BINS) $(BIND)/$(NAME)
|
||||
|
||||
$(DEP):
|
||||
@git submodule update --init --recursive
|
||||
|
||||
$(BIND)/argoat_sample_%: $(OBJD)/$(SRCD)/argoat.o $(OBJD)/$(TEST)/argoat_sample_%.o
|
||||
@echo "compiling executable $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LINK)
|
||||
|
||||
$(BIND)/$(NAME): $(OBJD)/$(TEST)/main.o $(OBJD)/$(SUBD)/testoasterror/src/testoasterror.o
|
||||
@echo "compiling executable $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LINK)
|
||||
|
||||
run:
|
||||
@cd $(BIND) && ./$(NAME)
|
||||
|
||||
clean:
|
||||
@echo "cleaning"
|
||||
@rm -rf $(BIND) $(OBJD)
|
|
@ -0,0 +1,129 @@
|
|||
# Argoat
|
||||
Argoat is a lightweight library for command-line options parsing.
|
||||
This was created because most of the existing solutions rely heavily on macros,
|
||||
and all of them expect you to write a giant switch to handle the given options.
|
||||
|
||||
Argoat allows you to deal with arguments using function pointers.
|
||||
It does not use any macro, switch, or dynamic memory allocation.
|
||||
|
||||
Argoat supports the following syntaxes:
|
||||
- simple options `test -a -b`
|
||||
- compound options `test -ab`
|
||||
- assigned options `test -c=4 -d 2`
|
||||
- long options `test --code 4 --den 2`
|
||||
- lone dash `test --oki - --den 2`
|
||||
- lone double-dash `test --oki -- --doki`
|
||||
- unflagged options `test 0 -c=4 1 -d=2 3`
|
||||
- limited params `test 0 -c 4 1 -d 2 3`
|
||||
|
||||
Argoat does not support the following syntaxes *on purpose*:
|
||||
- simple neighbours `test -a4`
|
||||
- custom symbols `test +a 4`
|
||||
|
||||
All of that in around 200 lines of code (getopt has approximately 700).
|
||||
Don't be shy, sneak a goat in your code.
|
||||
|
||||
## Cloning
|
||||
Clone with `--recurse-submodules` to get the required submodules.
|
||||
|
||||
## Testing
|
||||
Run `make` to compile the testing suite, and `make run` to perform the tests.
|
||||
|
||||
## Using
|
||||
### TL;DR
|
||||
```
|
||||
#include "argoat.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void handle_main(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
}
|
||||
|
||||
void handle_bool(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
*((bool*) data) = true;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool data1 = false;
|
||||
char* unflagged[23];
|
||||
|
||||
const struct argoat_sprig sprigs[2] =
|
||||
{
|
||||
{NULL, 0, NULL, handle_main},
|
||||
{"t", 0, (void*) &data1, handle_bool}
|
||||
};
|
||||
|
||||
struct argoat args = {sprigs, 2, unflagged, 0, 23};
|
||||
argoat_graze(&args, argc, argv);
|
||||
printf("%c\n", data1 ? '1' : '0');
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Details
|
||||
Include `argoat.h` and compile `argoat.c` with your code.
|
||||
|
||||
Write the functions that will handle your parameters.
|
||||
They will be called during the parsing process, in the order given by the user
|
||||
```
|
||||
void handle_main(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
}
|
||||
|
||||
void handle_bool(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
*((bool*) data) = true;
|
||||
}
|
||||
```
|
||||
|
||||
In your `int main(int argc, char** argv)`, declare the variables to configure.
|
||||
They will be passed to the corresponding functions as `void* data`
|
||||
```
|
||||
bool data1 = false;
|
||||
```
|
||||
|
||||
Also declare an array of strings to store the unflagged arguments.
|
||||
Just choose a size corresponding to the maximum number of unflagged arguments
|
||||
your program supports, or create a null pointer if it does not use them.
|
||||
```
|
||||
char* unflagged[UNFLAGGED_MAX];
|
||||
```
|
||||
|
||||
Then, declare an array of flag structures.
|
||||
The first entry only has to contain the unflagged-arguments handling function.
|
||||
The others must specify:
|
||||
- the name of the flag (one char for '-' prefix, multiple chars for '--')
|
||||
- the maximum number of arguments supported by this flag
|
||||
- a pointer to the data that has to be configured by the handling function
|
||||
- a pointer to the handling function
|
||||
```
|
||||
const struct argoat_sprig sprigs[2] =
|
||||
{
|
||||
{NULL, 0, NULL, handle_main},
|
||||
{"t", 0, (void*) &data1, handle_bool}
|
||||
};
|
||||
```
|
||||
|
||||
Then, create the main argoat structure given:
|
||||
- the flags array
|
||||
- its size,
|
||||
- the unflagged string buffer
|
||||
- the initial number of unflagged arguments
|
||||
- the maximum possible
|
||||
```
|
||||
struct argoat args = {sprigs, 2, unflagged, 0, UNFLAGGED_MAX};
|
||||
```
|
||||
|
||||
All that remains to do is calling the parsing function
|
||||
```
|
||||
argoat_graze(&args, argc, argv);
|
||||
```
|
||||
|
||||
And using the configured data
|
||||
```
|
||||
printf("%c\n", data1 ? '1' : '0');
|
||||
```
|
|
@ -0,0 +1,234 @@
|
|||
#include "argoat.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// executes the function for unflagged pars
|
||||
void argoat_unflagged_sacrifice(const struct argoat* args)
|
||||
{
|
||||
args->sprigs[0].func(args->sprigs[0].data,
|
||||
args->unflagged,
|
||||
args->unflagged_count);
|
||||
}
|
||||
|
||||
// returns 1 to increment the pars counter if the one given is flagged
|
||||
// otherwise we store the unflagged par in the buffer and return 0
|
||||
int argoat_increment_pars(struct argoat* args, char* flag, char* pars)
|
||||
{
|
||||
// unflagged pars
|
||||
if (flag == NULL)
|
||||
{
|
||||
// tests bounds and saves
|
||||
int count = args->unflagged_count;
|
||||
|
||||
if (count < args->unflagged_max)
|
||||
{
|
||||
args->unflagged[count] = pars;
|
||||
++args->unflagged_count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
// flagged pars
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// function execution
|
||||
void argoat_sacrifice(struct argoat* args,
|
||||
char* flag,
|
||||
char** pars,
|
||||
int pars_count)
|
||||
{
|
||||
// first flag found or tag compound passed
|
||||
if (flag == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// handles flags with '='
|
||||
int flag_len;
|
||||
char* eq = strchr(flag, '=');
|
||||
|
||||
if (eq != NULL)
|
||||
{
|
||||
flag_len = eq - flag;
|
||||
}
|
||||
else
|
||||
{
|
||||
flag_len = strlen(flag); // safe
|
||||
}
|
||||
|
||||
// searches the tag in the argoat structure
|
||||
// we initialize i to 1 to skip the programm execution command
|
||||
int i = 1;
|
||||
int len = args->sprigs_count;
|
||||
|
||||
while(i < len)
|
||||
{
|
||||
// as we use strncmp we must test the sizes to avoid collisions
|
||||
if ((strncmp(args->sprigs[i].flag, flag, flag_len) == 0)
|
||||
&& (((int) strlen(args->sprigs[i].flag)) == flag_len)) // safe
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
// the flag was not registered
|
||||
if (i == len)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// handles flags with '='
|
||||
// maximum number of pars passed to the function
|
||||
int max;
|
||||
|
||||
if (eq != NULL)
|
||||
{
|
||||
// moves past the '=' char
|
||||
++eq;
|
||||
// moves the pars pointer to the flag
|
||||
--pars;
|
||||
|
||||
// flag with '=' means we wave an additionnal parameter
|
||||
++pars_count;
|
||||
// which will be the only one (the others are left unflagged)
|
||||
max = 1;
|
||||
|
||||
// copies the par following '=' at the beginning of the flag
|
||||
memcpy(pars[0], eq, strlen(eq) + 1); // safe
|
||||
}
|
||||
else
|
||||
{
|
||||
max = args->sprigs[i].pars_max;
|
||||
}
|
||||
|
||||
// saves pars exceeding the limit
|
||||
if (pars_count > max)
|
||||
{
|
||||
for(int k = max; k < pars_count; ++k)
|
||||
{
|
||||
// leverages the pars incrementation side-effects
|
||||
argoat_increment_pars(args, NULL, pars[k]);
|
||||
}
|
||||
|
||||
// fixes the number of pars given to the function
|
||||
pars_count = max;
|
||||
}
|
||||
|
||||
// calls the approriate function
|
||||
args->sprigs[i].func(args->sprigs[i].data, pars, pars_count);
|
||||
}
|
||||
|
||||
// executes functions without pars for compound tags
|
||||
void argoat_compound(struct argoat* args, char** pars)
|
||||
{
|
||||
// currently processed char/flag
|
||||
int scroll = 1;
|
||||
char flag[2]; // safe
|
||||
|
||||
flag[1] = '\0';
|
||||
|
||||
// if this function is excuted this means there is at least one flag
|
||||
// therefore it is safe to test the condition for the next char only
|
||||
do
|
||||
{
|
||||
flag[0] = pars[0][scroll];
|
||||
argoat_sacrifice(args, flag, pars, 0);
|
||||
++scroll;
|
||||
}
|
||||
while(pars[0][scroll] != '\0');
|
||||
}
|
||||
|
||||
// executes functions with pars for each flag
|
||||
void argoat_graze(struct argoat* args, int argc, char** argv)
|
||||
{
|
||||
int pars_count = 0;
|
||||
char** pars = NULL;
|
||||
char* flag = NULL;
|
||||
char dash;
|
||||
|
||||
// skips the program execution command
|
||||
++argv;
|
||||
--argc;
|
||||
|
||||
// identifies every element in argv and executes the right
|
||||
// handling functions during the process
|
||||
for (int i = 0; i < argc; ++i)
|
||||
{
|
||||
// will be tested to identify lone dashes and long flags
|
||||
dash = argv[i][1];
|
||||
|
||||
// pars
|
||||
if (argv[i][0] != '-')
|
||||
{
|
||||
pars_count += argoat_increment_pars(args,
|
||||
flag,
|
||||
argv[i]);
|
||||
}
|
||||
// lone dash pars
|
||||
else if (dash == '\0')
|
||||
{
|
||||
pars_count += argoat_increment_pars(args,
|
||||
flag,
|
||||
argv[i]);
|
||||
}
|
||||
// very probably long flags
|
||||
else if (dash == '-')
|
||||
{
|
||||
// lone double-dash pars
|
||||
if (argv[i][2] == '\0')
|
||||
{
|
||||
pars_count += argoat_increment_pars(args,
|
||||
flag,
|
||||
argv[i]);
|
||||
}
|
||||
// long flags
|
||||
else
|
||||
{
|
||||
// executes for previous flag
|
||||
argoat_sacrifice(args, flag, pars, pars_count);
|
||||
// starts a new flag scope
|
||||
flag = argv[i] + 2;
|
||||
pars = argv + i + 1;
|
||||
pars_count = 0;
|
||||
}
|
||||
}
|
||||
// flags
|
||||
else
|
||||
{
|
||||
// executes for previous flag
|
||||
argoat_sacrifice(args, flag, pars, pars_count);
|
||||
|
||||
// compound flags (eg "-xvzf") directly executes
|
||||
if ((argv[i][2] != '=') && (argv[i][2] != '\0'))
|
||||
{
|
||||
// to get rid of the dash
|
||||
argoat_compound(args, argv + i);
|
||||
flag = NULL;
|
||||
pars = NULL;
|
||||
}
|
||||
// simple flags
|
||||
else
|
||||
{
|
||||
flag = argv[i] + 1;
|
||||
pars = argv + i + 1;
|
||||
}
|
||||
|
||||
pars_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// we call the function corresponding to the last flag
|
||||
argoat_sacrifice(args, flag, pars, pars_count);
|
||||
// we call the function handling unflagged pars
|
||||
if (args->unflagged_max > 0)
|
||||
{
|
||||
argoat_unflagged_sacrifice(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef H_ARGOAT
|
||||
#define H_ARGOAT
|
||||
|
||||
// flag-processor
|
||||
struct argoat_sprig
|
||||
{
|
||||
// dash-prefixed option
|
||||
const char* flag;
|
||||
// maximum pars
|
||||
const int pars_max;
|
||||
// pre-loaded data for the function
|
||||
void* data;
|
||||
// function executed upon detection
|
||||
void (* const func)(void* data, char** pars, const int pars_count);
|
||||
};
|
||||
|
||||
// main structure
|
||||
struct argoat
|
||||
{
|
||||
// the flags-processor list, with handling functions etc.
|
||||
const struct argoat_sprig* sprigs;
|
||||
// size of the list above
|
||||
const int sprigs_count;
|
||||
// unflagged tags buffer
|
||||
char** unflagged;
|
||||
int unflagged_count;
|
||||
int unflagged_max;
|
||||
};
|
||||
|
||||
void argoat_unflagged_sacrifice(const struct argoat* args);
|
||||
int argoat_increment_pars(struct argoat* args, char* flag, char* pars);
|
||||
void argoat_sacrifice(struct argoat* args, char* flag, char** pars, int pars_count);
|
||||
void argoat_compound(struct argoat* args, char** pars);
|
||||
void argoat_graze(struct argoat* args, int argc, char** argv);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,2 @@
|
|||
obj
|
||||
bin
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,39 @@
|
|||
NAME = testoasterror
|
||||
CC = gcc
|
||||
FLAGS = -std=c99 -pedantic -g
|
||||
FLAGS+= -Wall -Wextra -Werror=vla -Werror
|
||||
VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full
|
||||
|
||||
BIND = bin
|
||||
OBJD = obj
|
||||
SRCD = src
|
||||
TESTS = tests
|
||||
|
||||
INCL = -I$(SRCD)
|
||||
|
||||
SRCS = $(SRCD)/testoasterror.c
|
||||
SRCS+= $(TESTS)/main.c
|
||||
|
||||
OBJS:= $(patsubst %.c,$(OBJD)/%.o,$(SRCS))
|
||||
|
||||
.PHONY: $(BIND)/$(NAME)
|
||||
|
||||
$(OBJD)/%.o: %.c
|
||||
@echo "building object $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
$(BIND)/$(NAME): $(OBJS)
|
||||
@echo "compiling executable $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LINK)
|
||||
|
||||
run:
|
||||
@cd $(BIND) && ./$(NAME)
|
||||
|
||||
leakgrind: $(BIND)/$(NAME)
|
||||
@cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log ./$(NAME)
|
||||
|
||||
clean:
|
||||
@echo "cleaning"
|
||||
@rm -rf $(BIND) $(OBJD) valgrind.log
|
|
@ -0,0 +1,79 @@
|
|||
# Testoasterror
|
||||
Testoasterror is a minimalistic testing library. It is written in C99
|
||||
and does not use dynamic memory allocations by default.
|
||||
|
||||
# Testing
|
||||
Run `make` and `make run`. This will execute the example in the `tests` folder.
|
||||
|
||||
# Using
|
||||
## TL;DR
|
||||
Check out the `tests` folder
|
||||
|
||||
## Details
|
||||
Include `testoasterror.h` and compile `testoasterror.c` with your testing `main()`.
|
||||
|
||||
Declare an array of `bool` to hold the results of each tested expression.
|
||||
Its size determines the maximum number of expression checks for the same test.
|
||||
If one outreaches that limit, testoasterror will print a "fail overflow" message.
|
||||
The limit is 255, the maximum for a `uint8_t`.
|
||||
```
|
||||
bool results[255];
|
||||
```
|
||||
|
||||
Also declare an array of function pointers to hold your tests
|
||||
```
|
||||
void (*funcs[3])(struct testoasterror*) =
|
||||
{
|
||||
test1,
|
||||
test2,
|
||||
test3
|
||||
}
|
||||
```
|
||||
|
||||
Then, initialize a testoasterror context, giving:
|
||||
- a pointer to the context to initialize
|
||||
- the expression results buffer
|
||||
- its length
|
||||
- the testing functions array
|
||||
- its length
|
||||
```
|
||||
struct testoasterror test;
|
||||
testoasterror_init(&test, results, 255, funcs, 3);
|
||||
```
|
||||
|
||||
Run the tests and you're good to go!
|
||||
```
|
||||
testoasterror_run(&test);
|
||||
```
|
||||
|
||||
You can now write your tests in other C files, using the same function prototype
|
||||
```
|
||||
#ifndef C_TESTS
|
||||
#define C_TESTS
|
||||
|
||||
#include "testoasterror.h"
|
||||
|
||||
// a test
|
||||
void test1(struct testoasterror* test)
|
||||
{
|
||||
// an expression check
|
||||
testoasterror(test, 1 > 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
It is, in my opinion, a good idea to include them directly with the `main()`.
|
||||
This way, the function pointers will resolve without the need for a header
|
||||
(hence the include guards in the C file example above)
|
||||
```
|
||||
#include "tests.c"
|
||||
```
|
||||
|
||||
Extra: to abort, call the fail function *and return*
|
||||
```
|
||||
testoasterror_fail(test);
|
||||
```
|
||||
|
||||
# Greetings
|
||||
nnorm for ninja-starring this repo (how can you be *this* fast?!)
|
|
@ -0,0 +1,155 @@
|
|||
#include "testoasterror.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void testoasterror_init(
|
||||
struct testoasterror* test,
|
||||
bool* results,
|
||||
uint8_t max,
|
||||
void (**funcs)(struct testoasterror*),
|
||||
uint16_t count)
|
||||
{
|
||||
test->testing = false;
|
||||
test->results = results;
|
||||
test->results_end = results + max;
|
||||
|
||||
test->funcs = funcs;
|
||||
test->funcs_count = count;
|
||||
}
|
||||
|
||||
bool testoasterror_log(struct testoasterror* test)
|
||||
{
|
||||
bool* results = test->results;
|
||||
uint8_t max = test->results_cur - results;
|
||||
uint8_t passed_expr = 0;
|
||||
|
||||
// checks the saved status of all processed expressions
|
||||
for (uint8_t i = 0; i < max; ++i)
|
||||
{
|
||||
if (results[i])
|
||||
{
|
||||
++passed_expr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// first fail
|
||||
if (passed_expr == i)
|
||||
{
|
||||
fprintf(stderr, "failed expression ids:");
|
||||
}
|
||||
|
||||
fprintf(stderr, " %u", i);
|
||||
}
|
||||
}
|
||||
|
||||
// newline if we printed any failed expression id
|
||||
if (passed_expr != max)
|
||||
{
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
if (test->failexec)
|
||||
{
|
||||
fprintf(
|
||||
stderr,
|
||||
"aborted before expression: %u\n",
|
||||
max);
|
||||
}
|
||||
|
||||
// expressions summary
|
||||
fprintf(
|
||||
stderr,
|
||||
"expressions: %u passed, %u failed\n",
|
||||
passed_expr,
|
||||
max - passed_expr);
|
||||
|
||||
return (passed_expr == max);
|
||||
}
|
||||
|
||||
bool testoasterror_run(struct testoasterror* test)
|
||||
{
|
||||
// don't run tests in tests...
|
||||
if (test->testing == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* result;
|
||||
bool func_passed;
|
||||
uint16_t tests_passed = 0;
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"running %u tests with %u expr slots\n\n",
|
||||
test->funcs_count,
|
||||
(uint8_t) (test->results_end - test->results));
|
||||
|
||||
// runs the test functions from the given function pointers
|
||||
for (uint16_t i = 0; i < test->funcs_count; ++i)
|
||||
{
|
||||
// resets the expr results
|
||||
test->results_cur = test->results;
|
||||
test->failoverflow = false;
|
||||
test->failexec = false;
|
||||
|
||||
// runs the test
|
||||
test->funcs_index = i;
|
||||
test->funcs[i](test);
|
||||
|
||||
// outputs info (a fail overflow is considered a fail)
|
||||
func_passed = testoasterror_log(test)
|
||||
&& !test->failoverflow
|
||||
&& !test->failexec;
|
||||
tests_passed += func_passed;
|
||||
|
||||
// generates a message describing the test results
|
||||
if (test->failoverflow == true)
|
||||
{
|
||||
result = "encountered a fail overflow";
|
||||
}
|
||||
else if (test->failexec == true)
|
||||
{
|
||||
result = "aborted";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = func_passed ? "passed" : "failed";
|
||||
}
|
||||
|
||||
// test status
|
||||
fprintf(stderr, "test #%u %s\n\n", i, result);
|
||||
}
|
||||
|
||||
// tests summary
|
||||
fprintf(
|
||||
stderr,
|
||||
"tests: %u passed, %u failed\n",
|
||||
tests_passed,
|
||||
test->funcs_count - tests_passed);
|
||||
|
||||
return (test->funcs_count == tests_passed);
|
||||
}
|
||||
|
||||
// save a test status
|
||||
bool testoasterror(struct testoasterror* test, bool expr)
|
||||
{
|
||||
if (test->results_cur < test->results_end)
|
||||
{
|
||||
*(test->results_cur) = expr;
|
||||
++(test->results_cur);
|
||||
}
|
||||
else
|
||||
{
|
||||
test->failoverflow = true;
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
// handles set execution fails
|
||||
void testoasterror_fail(struct testoasterror* test)
|
||||
{
|
||||
test->failexec = true;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef H_TESTOASTERROR
|
||||
#define H_TESTOASTERROR
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// main structure
|
||||
struct testoasterror
|
||||
{
|
||||
// this is a test library so we handle all weird cases
|
||||
bool testing;
|
||||
|
||||
// test results for one function
|
||||
bool* results;
|
||||
bool* results_cur;
|
||||
bool* results_end;
|
||||
|
||||
// whether the function made too much tests for the results array
|
||||
bool failoverflow; // <3
|
||||
// execution fail
|
||||
bool failexec;
|
||||
|
||||
// test functions
|
||||
void (**funcs)(struct testoasterror*);
|
||||
uint16_t funcs_index;
|
||||
uint16_t funcs_count;
|
||||
};
|
||||
|
||||
// testoasterror can be static if you want it to (:
|
||||
void testoasterror_init(
|
||||
struct testoasterror* test,
|
||||
bool* results,
|
||||
uint8_t max,
|
||||
void (**funcs)(struct testoasterror*),
|
||||
uint16_t count);
|
||||
bool testoasterror_run(struct testoasterror* test);
|
||||
bool testoasterror(struct testoasterror* test, bool expr);
|
||||
void testoasterror_count(struct testoasterror* test, uint16_t count);
|
||||
void testoasterror_fail(struct testoasterror* test);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
#include "testoasterror.h"
|
||||
|
||||
// source include
|
||||
#include "tests.c"
|
||||
|
||||
#define COUNT_RESULTS 2
|
||||
#define COUNT_FUNCS 3
|
||||
|
||||
int main()
|
||||
{
|
||||
bool results[COUNT_RESULTS];
|
||||
void (*funcs[COUNT_FUNCS])(struct testoasterror*) =
|
||||
{
|
||||
test1,
|
||||
test2,
|
||||
test3
|
||||
};
|
||||
|
||||
struct testoasterror test;
|
||||
testoasterror_init(&test, results, COUNT_RESULTS, funcs, COUNT_FUNCS);
|
||||
testoasterror_run(&test);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef C_TESTS
|
||||
#define C_TESTS
|
||||
|
||||
#include "testoasterror.h"
|
||||
#include <string.h>
|
||||
|
||||
void test1(struct testoasterror* test)
|
||||
{
|
||||
testoasterror(test, 1 == 1);
|
||||
}
|
||||
|
||||
void test2(struct testoasterror* test)
|
||||
{
|
||||
testoasterror(test, 0 == 0);
|
||||
testoasterror(test, 1 == 1);
|
||||
testoasterror(test, 2 == 2);
|
||||
}
|
||||
|
||||
void test3(struct testoasterror* test)
|
||||
{
|
||||
bool res;
|
||||
|
||||
res = testoasterror(test, strcmp("fuck", "shit") == 0);
|
||||
|
||||
if (!res)
|
||||
{
|
||||
testoasterror_fail(test);
|
||||
return;
|
||||
}
|
||||
|
||||
testoasterror(test, 0 == 0);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,42 @@
|
|||
#include "argoat.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void handle_bool(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
*((bool*) data) = true;
|
||||
}
|
||||
|
||||
void handle_main(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool data1 = false;
|
||||
bool data2 = false;
|
||||
bool data3 = false;
|
||||
char** unflagged = NULL;
|
||||
|
||||
const struct argoat_sprig sprigs[4] =
|
||||
{
|
||||
{NULL, 0, NULL, handle_main},
|
||||
{"l", 0, (void*) &data1, handle_bool},
|
||||
{"m", 0, (void*) &data2, handle_bool},
|
||||
{"o", 0, (void*) &data3, handle_bool},
|
||||
};
|
||||
|
||||
struct argoat args = {sprigs, 4, unflagged, 0, 0};
|
||||
|
||||
argoat_graze(&args, argc, argv);
|
||||
|
||||
printf("t%c%c%c\n",
|
||||
data1 ? 'l' : ' ',
|
||||
data2 ? 'm' : ' ',
|
||||
data3 ? 'o' : ' ');
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#include "argoat.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define UNFLAGGED_MAX 4
|
||||
|
||||
void handle_bool(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
*((bool*) data) = true;
|
||||
}
|
||||
|
||||
void handle_main(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
if (pars_count > UNFLAGGED_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pars_count; ++i)
|
||||
{
|
||||
printf("%s", pars[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool data1 = false;
|
||||
bool data2 = false;
|
||||
bool data3 = false;
|
||||
char* unflagged[UNFLAGGED_MAX];
|
||||
|
||||
const struct argoat_sprig sprigs[4] =
|
||||
{
|
||||
{NULL, 0, NULL, handle_main},
|
||||
{"long", 0, (void*) &data1, handle_bool},
|
||||
{"mighty", 0, (void*) &data2, handle_bool},
|
||||
{"options", 0, (void*) &data3, handle_bool},
|
||||
};
|
||||
|
||||
struct argoat args = {sprigs, 4, unflagged, 0, UNFLAGGED_MAX};
|
||||
|
||||
argoat_graze(&args, argc, argv);
|
||||
|
||||
printf("t%c%c%c\n",
|
||||
data1 ? 'l' : ' ',
|
||||
data2 ? 'm' : ' ',
|
||||
data3 ? 'o' : ' ');
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
#include "argoat.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define UNFLAGGED_MAX 4
|
||||
|
||||
void handle_bool(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
*((bool*) data) = true;
|
||||
}
|
||||
|
||||
void handle_add(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
if (pars_count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*((int*) data) = atoi(pars[0]) + atoi(pars[1]); // safe for testing
|
||||
}
|
||||
|
||||
void handle_string(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
if (pars_count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*((char**) data) = pars[0];
|
||||
}
|
||||
|
||||
void handle_main(void* data, char** pars, const int pars_count)
|
||||
{
|
||||
if (pars_count > UNFLAGGED_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pars_count; ++i)
|
||||
{
|
||||
printf("%s", pars[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
bool data1 = false;
|
||||
int data2 = 0;
|
||||
char* data3 = "";
|
||||
|
||||
char* unflagged[UNFLAGGED_MAX];
|
||||
|
||||
const struct argoat_sprig sprigs[4] =
|
||||
{
|
||||
{NULL, 0, NULL, handle_main},
|
||||
{"tau", 2, (void*) &data2, handle_add},
|
||||
{"t", 0, (void*) &data1, handle_bool},
|
||||
{"text", 1, (void*) &data3, handle_string},
|
||||
};
|
||||
|
||||
struct argoat args = {sprigs, 4, unflagged, 0, UNFLAGGED_MAX};
|
||||
|
||||
argoat_graze(&args, argc, argv);
|
||||
|
||||
printf("t%c%d%s\n", data1 ? 'l' : ' ', data2, data3);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "testoasterror.h"
|
||||
|
||||
// source include
|
||||
#include "tests.c"
|
||||
|
||||
int main()
|
||||
{
|
||||
bool results[32];
|
||||
void (*funcs[3])(struct testoasterror*) =
|
||||
{
|
||||
test1,
|
||||
test2,
|
||||
test3
|
||||
};
|
||||
|
||||
struct testoasterror test;
|
||||
testoasterror_init(&test, results, 32, funcs, 3);
|
||||
testoasterror_run(&test);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef C_TESTS
|
||||
#define C_TESTS
|
||||
|
||||
#include "testoasterror.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
void test_tool(struct testoasterror* test, uint8_t id, char* args, char* cmp)
|
||||
{
|
||||
char* ret;
|
||||
char buf[32];
|
||||
char cmd[128];
|
||||
char cmp_ln[16];
|
||||
|
||||
snprintf(cmd, 128, "./argoat_sample_%u %s 2>&1", id, args);
|
||||
snprintf(cmp_ln, 16, "%s\n", cmp);
|
||||
|
||||
FILE* fp = popen(cmd, "r");
|
||||
testoasterror(test, fp != NULL);
|
||||
|
||||
ret = fgets(buf, 32, fp);
|
||||
testoasterror(test, (ret != NULL) && (strcmp(buf, cmp_ln) == 0));
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void test1(struct testoasterror* test)
|
||||
{
|
||||
test_tool(test, 1, "", "t ");
|
||||
|
||||
test_tool(test, 1, "-l", "tl ");
|
||||
test_tool(test, 1, "-m", "t m ");
|
||||
test_tool(test, 1, "-o", "t o");
|
||||
|
||||
test_tool(test, 1, "--l", "tl ");
|
||||
test_tool(test, 1, "--long", "t ");
|
||||
|
||||
test_tool(test, 1, "-lmo", "tlmo");
|
||||
test_tool(test, 1, "-lm -o", "tlmo");
|
||||
test_tool(test, 1, "-l -m -o", "tlmo");
|
||||
|
||||
test_tool(test, 1, "-l 1 -m 2 -o 3", "tlmo");
|
||||
|
||||
test_tool(test, 1, "-l - -m", "tlm ");
|
||||
test_tool(test, 1, "-l --m 3", "tlm ");
|
||||
test_tool(test, 1, "-l --m=3", "tlm ");
|
||||
}
|
||||
|
||||
void test2(struct testoasterror* test)
|
||||
{
|
||||
test_tool(test, 2, "--long", "tl ");
|
||||
test_tool(test, 2, "--mighty", "t m ");
|
||||
test_tool(test, 2, "--options", "t o");
|
||||
|
||||
test_tool(test, 2, "-l", "t ");
|
||||
test_tool(test, 2, "-long", "t ");
|
||||
|
||||
test_tool(test, 2, "--long --mighty --options", "tlmo");
|
||||
test_tool(test, 2, "0 --long 1 --mighty 2 --options 3", "0123tlmo");
|
||||
test_tool(test, 2, "0 --long=1 --mighty 2 --options 3", "023tlmo");
|
||||
test_tool(test, 2, "0 --long=1 4 --mighty 2 --options 3", "0423tlmo");
|
||||
|
||||
test_tool(test, 2, "0 --long - --mighty -- --options 3", "0---3tlmo");
|
||||
}
|
||||
|
||||
void test3(struct testoasterror* test)
|
||||
{
|
||||
test_tool(test, 3, "-t", "tl0");
|
||||
test_tool(test, 3, "--tau", "t 0");
|
||||
test_tool(test, 3, "--text", "t 0");
|
||||
|
||||
test_tool(test, 3, "-t --tau 3 4 5", "5tl7");
|
||||
test_tool(test, 3, "--tau=3 4 5", "45t 0");
|
||||
test_tool(test, 3, "--text one two", "twot 0one");
|
||||
|
||||
test_tool(test, 3, "--text= one two", "onetwot 0");
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
obj
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,41 @@
|
|||
NAME = sample
|
||||
CC = gcc
|
||||
FLAGS = -std=c99 -pedantic -g
|
||||
FLAGS+= -Wall -Wno-unused-parameter -Wextra -Werror=vla -Werror
|
||||
VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full
|
||||
|
||||
BIND = bin
|
||||
OBJD = obj
|
||||
SRCD = src
|
||||
RESD = res
|
||||
|
||||
INCL = -I$(SRCD)
|
||||
|
||||
SRCS = $(SRCD)/example.c
|
||||
SRCS+= $(SRCD)/configator.c
|
||||
|
||||
OBJS:= $(patsubst $(SRCD)/%.c,$(OBJD)/$(SRCD)/%.o,$(SRCS))
|
||||
|
||||
.PHONY: all
|
||||
all: $(BIND)/$(NAME) run
|
||||
|
||||
$(OBJD)/%.o: %.c
|
||||
@echo "building object $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
$(BIND)/$(NAME): $(OBJS)
|
||||
@echo "compiling executable $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LINK)
|
||||
@cp $(RESD)/test.ini $(BIND)/config.ini
|
||||
|
||||
run:
|
||||
@cd $(BIND) && ./$(NAME)
|
||||
|
||||
leakgrind: $(BIND)/$(NAME)
|
||||
@cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log ./$(NAME)
|
||||
|
||||
clean:
|
||||
@echo "cleaning"
|
||||
@rm -rf $(BIND) $(OBJD) valgrind.log
|
|
@ -0,0 +1,95 @@
|
|||
# Configator
|
||||
Configator is a lightweight library for ini config file parsing.
|
||||
This was created to make it easy following the "DRY" coding rule
|
||||
without using macros, and with flexibility in mind.
|
||||
|
||||
It integrates very well with the [Argoat](https://github.com/nullgemm/argoat.git)
|
||||
arguments parser library, by using the same function pointers format.
|
||||
This way, you can easily load settings from an ini file while overloading them
|
||||
with command-line arguments if needed: the handling functions will be the same.
|
||||
|
||||
Configator does not use any macro or dynamic memory allocation,
|
||||
and was built in less than 350 lines of C99 code.
|
||||
|
||||
## Testing
|
||||
Run `make` to compile an example executable and perform basic testing
|
||||
|
||||
## Using
|
||||
### TL;DR
|
||||
Please see `example.c` for the condensed version
|
||||
(or better, read the actual documentation below).
|
||||
It is a bit too long to be copied here twice...
|
||||
|
||||
### Details
|
||||
Include `argoat.h` and compile `argoat.c` with your code.
|
||||
|
||||
Write the functions that will handle your parameters.
|
||||
They will be called during the parsing process, in the order given by the user
|
||||
```
|
||||
void handle_config_u8(void* data, char** value, const int pars_count)
|
||||
{
|
||||
if (pars_count > 0)
|
||||
{
|
||||
*((uint8_t*) data) = atoi(*value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In your `main`, declare the variables to configure.
|
||||
They will be passed to the corresponding functions as `void* data`
|
||||
```
|
||||
uint8_t answer = 0;
|
||||
```
|
||||
|
||||
Declare the arrays of parameters by section, starting with the general section.
|
||||
If you don't want to handle parameters in some section, just declare it `NULL`.
|
||||
```
|
||||
struct configator_param* map_no_section = NULL;
|
||||
```
|
||||
|
||||
Declare real sections parameters afterwards
|
||||
```
|
||||
struct configator_param map_test_section[] =
|
||||
{
|
||||
{"ping", &answer, handle_config_u8},
|
||||
{"pong", &answer, handle_config_u8},
|
||||
};
|
||||
```
|
||||
|
||||
Then group them in the map
|
||||
```
|
||||
struct configator_param* map[] =
|
||||
{
|
||||
map_no_section,
|
||||
map_test_section
|
||||
};
|
||||
```
|
||||
|
||||
And declare the sections array. Configator will execute the pointed function
|
||||
with `NULL` arguments at the beginning of each detected section.
|
||||
You can also declare sections with `NULL` parameters, in which case nothing
|
||||
will be executed.
|
||||
```
|
||||
struct configator_param sections[] =
|
||||
{
|
||||
{"network_test", &answer, handle_config_u8},
|
||||
};
|
||||
```
|
||||
|
||||
Don't forget to put the right numbers in the lenght variables
|
||||
```
|
||||
uint16_t map_len[] = {0, 2};
|
||||
uint16_t sections_len = 1;
|
||||
```
|
||||
|
||||
Then initialize and use configator
|
||||
```
|
||||
struct configator config;
|
||||
config.map = map;
|
||||
config.map_len = map_len;
|
||||
config.sections = sections;
|
||||
config.sections_len = sections_len;
|
||||
|
||||
configator(&config, "config.ini");
|
||||
printf("answer = %d\n", answer);
|
||||
```
|
|
@ -0,0 +1,37 @@
|
|||
sectionless = 0000
|
||||
|
||||
[sections_test]
|
||||
[regular]
|
||||
[left-padded]
|
||||
[right-padded]
|
||||
[middle-padded]
|
||||
[smaller]
|
||||
[ right]
|
||||
[left ]
|
||||
[ middle ]
|
||||
[]
|
||||
[ ]
|
||||
[three wrong words]
|
||||
[]]
|
||||
[[]
|
||||
[[]]
|
||||
|
||||
[params_tests]
|
||||
# comment
|
||||
# padded comment
|
||||
test = 1111
|
||||
left-padded = 2222
|
||||
right-padded = 3333
|
||||
minified=4444
|
||||
multi = 4444 3333 2222 1111
|
||||
spaces = " middle "
|
||||
wrong declaration = 5555
|
||||
wrong line
|
||||
|
||||
[test_section]
|
||||
answer = 42
|
||||
|
||||
[network_test]
|
||||
ping = 255
|
||||
|
||||
[question]
|
|
@ -0,0 +1,309 @@
|
|||
#include "configator.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// returns the index of the searched element, or len if it can't be found
|
||||
static uint16_t search(struct configator_param* config, uint16_t len, char* key)
|
||||
{
|
||||
// strcmp indicator
|
||||
int8_t disc;
|
||||
// initial tested index
|
||||
uint16_t i = len / 2;
|
||||
// initial bounds (inclusive)
|
||||
uint16_t k = 0;
|
||||
uint16_t l = len - 1;
|
||||
|
||||
// skip directly to the final check
|
||||
if (len > 1)
|
||||
{
|
||||
// as long as a match is possible
|
||||
do
|
||||
{
|
||||
disc = strcmp(config[i].key, key);
|
||||
|
||||
if (disc == 0)
|
||||
{
|
||||
// found by chance
|
||||
return i;
|
||||
}
|
||||
else if (disc > 0)
|
||||
{
|
||||
l = i;
|
||||
i = (i + k) / 2; // floor
|
||||
}
|
||||
else
|
||||
{
|
||||
k = i;
|
||||
i = (i + l) / 2 + (i + l) % 2; // ceil
|
||||
}
|
||||
|
||||
if (len == 2)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ((k+1) != l);
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
// final check
|
||||
disc = strcmp(config[i].key, key);
|
||||
|
||||
if (disc == 0)
|
||||
{
|
||||
// found by dichotomy
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
return len;
|
||||
}
|
||||
|
||||
static void configator_save_section(struct configator* config, char* line)
|
||||
{
|
||||
char c;
|
||||
uint16_t index;
|
||||
uint16_t k = 0; // last non-space pos
|
||||
uint16_t l = 0; // second last non-space pos
|
||||
|
||||
// leading spaces
|
||||
do
|
||||
{
|
||||
++line;
|
||||
c = line[0];
|
||||
}
|
||||
while ((c != '\0') && isspace(c));
|
||||
|
||||
if (c == '[')
|
||||
{
|
||||
++line;
|
||||
c = line[0];
|
||||
}
|
||||
|
||||
// trailing spaces
|
||||
for (uint16_t i = 1; c != '\0'; ++i)
|
||||
{
|
||||
if ((c != ']') && !isspace(c))
|
||||
{
|
||||
// we use two variables to avoid
|
||||
// counting the ending ']'
|
||||
l = k + 1; // we *must* increment here
|
||||
k = i;
|
||||
}
|
||||
|
||||
c = line[i];
|
||||
}
|
||||
|
||||
// terminator
|
||||
line[l] = '\0';
|
||||
|
||||
if (l == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// saving
|
||||
strncpy(config->section, line, l + 1);
|
||||
|
||||
// searching
|
||||
index = search(
|
||||
config->sections,
|
||||
config->sections_len,
|
||||
config->section);
|
||||
|
||||
#ifdef CONFIGATOR_DEBUG
|
||||
printf("[%s]\n", line);
|
||||
#endif
|
||||
|
||||
// calling the function
|
||||
if (index != config->sections_len)
|
||||
{
|
||||
config->current_section = index + 1;
|
||||
|
||||
if (config->sections[index].handle != NULL)
|
||||
{
|
||||
config->sections[index].handle(
|
||||
config->sections[index].data,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void configator_save_param(struct configator* config, char* line)
|
||||
{
|
||||
char c;
|
||||
uint16_t index;
|
||||
uint16_t i = 0;
|
||||
uint16_t k = 0;
|
||||
|
||||
// leading chars
|
||||
do
|
||||
{
|
||||
++i;
|
||||
c = line[i];
|
||||
}
|
||||
while ((c != '\0') && (c != '=') && !isspace(c));
|
||||
|
||||
// empty line
|
||||
if (c == '\0')
|
||||
{
|
||||
config->param[0] = '\0';
|
||||
config->value[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// end of the param
|
||||
k = i;
|
||||
|
||||
// spaces before next char if any
|
||||
while ((c != '\0') && isspace(c))
|
||||
{
|
||||
++i;
|
||||
c = line[i];
|
||||
}
|
||||
|
||||
// that next char must be '='
|
||||
if (c != '=')
|
||||
{
|
||||
config->param[0] = '\0';
|
||||
config->value[0] = '\0';
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
c = line[i];
|
||||
}
|
||||
|
||||
// spaces after '='
|
||||
while ((c != '\0') && isspace(c))
|
||||
{
|
||||
++i;
|
||||
c = line[i];
|
||||
}
|
||||
|
||||
line[k] = '\0';
|
||||
strncpy(config->param, line, k + 1);
|
||||
strncpy(config->value, line + i, strlen(line + i) + 1);
|
||||
|
||||
// searching
|
||||
if ((config->current_section == 0) && (config->map_len[0] == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
index = search(
|
||||
config->map[config->current_section],
|
||||
config->map_len[config->current_section],
|
||||
config->param);
|
||||
|
||||
#ifdef CONFIGATOR_DEBUG
|
||||
printf("%s = \"%s\"\n", config->param, config->value);
|
||||
#endif
|
||||
|
||||
// calling the function
|
||||
if ((index != config->map_len[config->current_section])
|
||||
&& (config->map[config->current_section][index].handle != NULL))
|
||||
{
|
||||
char* tmp = (char*) config->value;
|
||||
config->map[config->current_section][index].handle(
|
||||
config->map[config->current_section][index].data,
|
||||
&(tmp),
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
static void configator_read(FILE* fp, char* line)
|
||||
{
|
||||
int c = fgetc(fp);
|
||||
uint16_t i = 0;
|
||||
uint16_t k = 0;
|
||||
|
||||
if (c == EOF)
|
||||
{
|
||||
line[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
while ((c != '\n') && (c != EOF))
|
||||
{
|
||||
if ((i < (CONFIGATOR_MAX_LINE + 1)) // maximum len
|
||||
&& ((i > 0) || !isspace(c))) // skips leading spaces
|
||||
{
|
||||
// used to trim trailing spaces
|
||||
// and to terminate overflowing string
|
||||
if (!isspace(c))
|
||||
{
|
||||
k = i;
|
||||
}
|
||||
|
||||
line[i] = c;
|
||||
++i;
|
||||
}
|
||||
|
||||
c = fgetc(fp);
|
||||
}
|
||||
|
||||
if (i == (CONFIGATOR_MAX_LINE + 1))
|
||||
{
|
||||
line[k] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
line[k + 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
int configator(struct configator* config, const char* path)
|
||||
{
|
||||
FILE* fp = fopen(path, "r");
|
||||
|
||||
if (fp == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
config->section[0] = '\0';
|
||||
config->param[0] = '\0';
|
||||
config->value[0] = '\0';
|
||||
config->current_section = 0;
|
||||
|
||||
// event loop
|
||||
char line[CONFIGATOR_MAX_LINE + 1];
|
||||
|
||||
while (1)
|
||||
{
|
||||
configator_read(fp, line);
|
||||
|
||||
// end of file
|
||||
if (feof(fp))
|
||||
{
|
||||
break;
|
||||
}
|
||||
// comment
|
||||
else if (line[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// section
|
||||
else if ((line[0] == '[') && (line[strlen(line) - 1] == ']'))
|
||||
{
|
||||
configator_save_section(config, line);
|
||||
}
|
||||
// param
|
||||
else
|
||||
{
|
||||
configator_save_param(config, line);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef H_CONFIGATOR
|
||||
#define H_CONFIGATOR
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define CONFIGATOR_MAX_LINE 80
|
||||
|
||||
#if 0
|
||||
#define CONFIGATOR_DEBUG
|
||||
#endif
|
||||
|
||||
struct configator_param
|
||||
{
|
||||
char* key;
|
||||
void* data;
|
||||
void (*handle)(void* data, char** value, const int pars_count);
|
||||
};
|
||||
|
||||
struct configator
|
||||
{
|
||||
char section[CONFIGATOR_MAX_LINE];
|
||||
char param[CONFIGATOR_MAX_LINE];
|
||||
char value[CONFIGATOR_MAX_LINE];
|
||||
uint16_t current_section;
|
||||
|
||||
struct configator_param** map;
|
||||
struct configator_param* sections;
|
||||
|
||||
uint16_t* map_len;
|
||||
uint16_t sections_len;
|
||||
};
|
||||
|
||||
int configator(struct configator* config, const char* path);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,71 @@
|
|||
#include "configator.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
void handle_config_u8(void* data, char** value, const int pars_count)
|
||||
{
|
||||
if (pars_count > 0)
|
||||
{
|
||||
*((uint8_t*) data) = atoi(*value);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_question(void* data, char** value, const int pars_count)
|
||||
{
|
||||
*((uint8_t*) data) = 23;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
uint8_t answer = 0;
|
||||
uint8_t question = 0;
|
||||
|
||||
// parameters, grouped in sections
|
||||
struct configator_param* map_no_section = NULL;
|
||||
struct configator_param* map_question_section = NULL;
|
||||
struct configator_param map_test_section[] =
|
||||
{
|
||||
{"aaabbb", &answer, handle_config_u8},
|
||||
{"aabbaa", &answer, handle_config_u8},
|
||||
{"answer", &answer, handle_config_u8},
|
||||
{"cccccc", &answer, handle_config_u8},
|
||||
{"cccddd", &answer, handle_config_u8},
|
||||
{"daaaaa", &answer, handle_config_u8},
|
||||
{"ddaaaa", &answer, handle_config_u8},
|
||||
{"eeeeee", &answer, handle_config_u8}
|
||||
};
|
||||
struct configator_param* map[] =
|
||||
{
|
||||
map_no_section,
|
||||
map_question_section,
|
||||
map_test_section
|
||||
};
|
||||
|
||||
// sections (used to execute functions at sections start)
|
||||
struct configator_param sections[] =
|
||||
{
|
||||
{"question", &question, handle_question},
|
||||
{"test_section", NULL, NULL},
|
||||
};
|
||||
|
||||
// number of parameters, by section
|
||||
uint16_t map_len[] = {0, 0, 8};
|
||||
// number of sections
|
||||
uint16_t sections_len = 2;
|
||||
|
||||
// configator object
|
||||
struct configator config;
|
||||
config.map = map;
|
||||
config.map_len = map_len;
|
||||
config.sections = sections;
|
||||
config.sections_len = sections_len;
|
||||
|
||||
// execute configuration
|
||||
configator(&config, "config.ini");
|
||||
printf("question = %d\n", question);
|
||||
printf("answer = %d\n", answer);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef H_DRAGONFAIL_ERROR
|
||||
#define H_DRAGONFAIL_ERROR
|
||||
|
||||
enum dgn_error
|
||||
{
|
||||
DGN_OK, // do not remove
|
||||
|
||||
DGN_NULL,
|
||||
DGN_ALLOC,
|
||||
DGN_BOUNDS,
|
||||
DGN_DOMAIN,
|
||||
|
||||
DGN_SIZE, // do not remove
|
||||
};
|
||||
|
||||
//#define DRAGONFAIL_SKIP
|
||||
#define DRAGONFAIL_BASIC_LOG
|
||||
#define DRAGONFAIL_THROW_BASIC_LOG
|
||||
#define DRAGONFAIL_THROW_DEBUG_LOG
|
||||
//#define DRAGONFAIL_ABORT
|
||||
|
||||
#endif
|
|
@ -0,0 +1,46 @@
|
|||
#include <stdio.h>
|
||||
#include "dragonfail.h"
|
||||
|
||||
int div(int num, int den)
|
||||
{
|
||||
if (den == 0)
|
||||
{
|
||||
dgn_throw(DGN_DOMAIN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return num / den;
|
||||
}
|
||||
|
||||
void log_init(char** log)
|
||||
{
|
||||
log[DGN_OK] = "out-of-bounds log message"; // special case
|
||||
log[DGN_NULL] = "null pointer";
|
||||
log[DGN_ALLOC] = "failed memory allocation";
|
||||
log[DGN_BOUNDS] = "out-of-bounds index";
|
||||
log[DGN_DOMAIN] = "invalid domain";
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
log_init(dgn_init());
|
||||
|
||||
int i;
|
||||
int q;
|
||||
|
||||
for (i = -2; i < 3; ++i)
|
||||
{
|
||||
q = div(42, i);
|
||||
|
||||
if (dgn_catch())
|
||||
{
|
||||
printf("skipping division by zero\n");
|
||||
dgn_reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("42/%d = %d\n", i, q);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,49 @@
|
|||
NAME = dragonfail
|
||||
CC = gcc
|
||||
FLAGS = -std=c99 -pedantic -g
|
||||
FLAGS+= -Wall -Wno-unused-parameter -Wextra -Werror=vla -Werror
|
||||
VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full
|
||||
|
||||
BIND = bin
|
||||
OBJD = obj
|
||||
SRCD = src
|
||||
EXPD = example
|
||||
|
||||
INCL = -I$(SRCD)
|
||||
INCL+= -I$(EXPD)
|
||||
|
||||
SRCS = $(EXPD)/example.c
|
||||
SRCS+= $(SRCD)/dragonfail.c
|
||||
|
||||
SRCS_OBJS := $(patsubst %.c,$(OBJD)/%.o,$(SRCS))
|
||||
|
||||
# aliases
|
||||
.PHONY: final
|
||||
final: $(BIND)/$(NAME)
|
||||
|
||||
# generic compiling command
|
||||
$(OBJD)/%.o: %.c
|
||||
@echo "building object $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
# final executable
|
||||
$(BIND)/$(NAME): $(SRCS_OBJS) $(FINAL_OBJS)
|
||||
@echo "compiling executable $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LINK)
|
||||
|
||||
run:
|
||||
@cd $(BIND) && ./$(NAME)
|
||||
|
||||
# tools
|
||||
## valgrind memory leak detection
|
||||
leak: $(BIND)/$(NAME)
|
||||
@echo "# running valgrind"
|
||||
rm -f valgrind.log
|
||||
cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log ./$(NAME)
|
||||
less valgrind.log
|
||||
## repository cleaning
|
||||
clean:
|
||||
@echo "# cleaning"
|
||||
rm -rf $(BIND) $(OBJD) valgrind.log
|
|
@ -0,0 +1,77 @@
|
|||
# Dragonfail
|
||||
Dragonfail is a simple library providing basic error handling functionnalities.
|
||||
It was designed to be as lightweight as possible, and can be completely disabled
|
||||
with only one `#define` (more on that later).
|
||||
|
||||
Dragonfail was designed to be fast and uses inline functions exclusively. These
|
||||
calls modify a global context which is not directly accessible by the programmer.
|
||||
|
||||
All the error codes must be written in an enum in **your** `dragonfail_error.h` file.
|
||||
Because of this rather unusual architecture, the file must be found by the compiler
|
||||
when it is processing `dragonfail.c` (in addition to your own source code of course).
|
||||
|
||||
## Testing
|
||||
Run `make` to compile an example, and `make run` to execute it.
|
||||
|
||||
## Defines
|
||||
This header can also contain some `#define` to modify dragonfail's behaviour:
|
||||
- `DRAGONFAIL_SKIP` completely disables the whole library, making it completely
|
||||
disappear from the binary (unless your compiler is a massive douche).
|
||||
- `DRAGONFAIL_BASIC_LOG` enables the `dgn_basic_log()` function calls
|
||||
- `DRAGONFAIL_THROW_BASIC_LOG` makes `dgn_throw()` call `dgn_basic_log()` automatically
|
||||
- `DRAGONFAIL_THROW_DEBUG_LOG` also prints the file and line in which
|
||||
`dgn_throw()` is called (you don't even need to compile with symbols
|
||||
because this is achieved the smart way using simple C99 macros)
|
||||
- `DRAGONFAIL_ABORT` makes `dgn_throw()` call `abort()`
|
||||
|
||||
Again, these `#define` must be placed in **your** `dragonfail_error.h` file.
|
||||
|
||||
## Using
|
||||
### TL;DR
|
||||
see the `example` folder :)
|
||||
|
||||
### Documentation
|
||||
```
|
||||
char** dgn_init();
|
||||
```
|
||||
This intializes the context to `DGN_OK` (no error) and returns the array of strings
|
||||
you can fill with log messages corresponding to the errors you added in the enum.
|
||||
|
||||
```
|
||||
void dgn_reset();
|
||||
```
|
||||
This resets the context to `DGN_OK`.
|
||||
|
||||
```
|
||||
void dgn_basic_log();
|
||||
```
|
||||
This prints the message corresponding to the current error to stderr.
|
||||
|
||||
```
|
||||
void dgn_throw(enum dgn_error new_code);
|
||||
```
|
||||
This sets the error to the given code.
|
||||
|
||||
```
|
||||
char dgn_catch();
|
||||
```
|
||||
This returns true if the context currently holds an error
|
||||
|
||||
## Why is the architecture so strange?
|
||||
The dragonfail context is global (extern) but really *implemented* in `dragonfail.c`.
|
||||
Its type depends on the size of the enum so it is *declared* in `dragonfail_private.h`:
|
||||
this way we can include the user's `dragonfail_error.h` and get `DGN_SIZE`.
|
||||
|
||||
The inline functions need to access this context and **we want it private**, so we can't
|
||||
*implement* them directly in the header as a lot of people seem to appreciate. Instead,
|
||||
we will *declare* them here, and put the *implementations* in `dragonfail.c`: this way
|
||||
we can access the global context without including its declaration, because it is
|
||||
implemented here as well.
|
||||
|
||||
When you include `dragonfail.h`, you get access to the inline functions declarations
|
||||
and thanks to this design any compiler will do the rest of the job automatically. Yes,
|
||||
this whole thing is useless and over-engineered. And yes, I had fun doing it...
|
||||
|
||||
## Greetings
|
||||
Jinjer for the cool music \m/
|
||||
Haiku developers for indirectly giving me the idea
|
|
@ -0,0 +1,106 @@
|
|||
#include "dragonfail.h"
|
||||
#include "dragonfail_private.h"
|
||||
#include "dragonfail_error.h"
|
||||
|
||||
#ifdef DRAGONFAIL_BASIC_LOG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef DRAGONFAIL_ABORT
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
// extern
|
||||
struct dgn dgn;
|
||||
|
||||
inline char** dgn_init()
|
||||
{
|
||||
#ifndef DRAGONFAIL_SKIP
|
||||
dgn.error = DGN_OK;
|
||||
return dgn.log;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void dgn_reset()
|
||||
{
|
||||
#ifndef DRAGONFAIL_SKIP
|
||||
dgn.error = DGN_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void dgn_basic_log()
|
||||
{
|
||||
#ifdef DRAGONFAIL_BASIC_LOG
|
||||
#ifndef DRAGONFAIL_SKIP
|
||||
if (dgn.error < DGN_SIZE)
|
||||
{
|
||||
fprintf(stderr, "%s\n", dgn.log[dgn.error]);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s\n", dgn.log[0]);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
inline char* dgn_output_log()
|
||||
{
|
||||
if (dgn.error < DGN_SIZE)
|
||||
{
|
||||
return dgn.log[dgn.error];
|
||||
}
|
||||
else
|
||||
{
|
||||
return dgn.log[0];
|
||||
}
|
||||
}
|
||||
|
||||
enum dgn_error dgn_output_code()
|
||||
{
|
||||
return dgn.error;
|
||||
}
|
||||
|
||||
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
|
||||
inline void dgn_throw_extra(
|
||||
enum dgn_error new_code,
|
||||
const char* file,
|
||||
unsigned int line)
|
||||
#else
|
||||
inline void dgn_throw(
|
||||
enum dgn_error new_code)
|
||||
#endif
|
||||
{
|
||||
#ifndef DRAGONFAIL_SKIP
|
||||
dgn.error = new_code;
|
||||
|
||||
#ifdef DRAGONFAIL_THROW_BASIC_LOG
|
||||
#ifdef DRAGONFAIL_BASIC_LOG
|
||||
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
|
||||
fprintf(
|
||||
stderr,
|
||||
"error in %s line %u: ",
|
||||
file,
|
||||
line);
|
||||
#endif
|
||||
|
||||
dgn_basic_log();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef DRAGONFAIL_ABORT
|
||||
abort();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
inline char dgn_catch()
|
||||
{
|
||||
#ifndef DRAGONFAIL_SKIP
|
||||
return (dgn.error != DGN_OK);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef H_DRAGONFAIL
|
||||
#define H_DRAGONFAIL
|
||||
|
||||
#include "dragonfail_error.h"
|
||||
|
||||
#ifdef DRAGONFAIL_THROW_DEBUG_LOG
|
||||
#define dgn_throw(new_code) dgn_throw_extra(new_code, DGN_FILE, DGN_LINE)
|
||||
#define DGN_FILE __FILE__
|
||||
#define DGN_LINE __LINE__
|
||||
void dgn_throw_extra(enum dgn_error new_code, const char* file, unsigned int line);
|
||||
#else
|
||||
void dgn_throw(enum dgn_error new_code);
|
||||
#endif
|
||||
|
||||
char** dgn_init();
|
||||
void dgn_reset();
|
||||
void dgn_basic_log();
|
||||
char* dgn_output_log();
|
||||
enum dgn_error dgn_output_code();
|
||||
char dgn_catch();
|
||||
|
||||
#endif
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef H_DRAGONFAIL_PRIVATE
|
||||
#define H_DRAGONFAIL_PRIVATE
|
||||
|
||||
#include "dragonfail_error.h"
|
||||
|
||||
struct dgn
|
||||
{
|
||||
enum dgn_error error;
|
||||
char* log[DGN_SIZE];
|
||||
};
|
||||
|
||||
extern struct dgn dgn;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,7 @@
|
|||
bin
|
||||
obj
|
||||
src/demo/*.o
|
||||
src/demo/keyboard
|
||||
src/demo/output
|
||||
src/demo/paint
|
||||
src/demo/truecolor
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,40 @@
|
|||
NAME=termbox
|
||||
CC=gcc
|
||||
FLAGS+=-std=c99 -pedantic -Wall -Werror -g
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS),Linux)
|
||||
FLAGS+=-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE
|
||||
endif
|
||||
|
||||
BIND=bin
|
||||
SRCD=src
|
||||
OBJD=obj
|
||||
INCL=-I$(SRCD)
|
||||
|
||||
SRCS=$(SRCD)/termbox.c
|
||||
SRCS+=$(SRCD)/input.c
|
||||
SRCS+=$(SRCD)/memstream.c
|
||||
SRCS+=$(SRCD)/ringbuffer.c
|
||||
SRCS+=$(SRCD)/term.c
|
||||
SRCS+=$(SRCD)/utf8.c
|
||||
|
||||
OBJS:=$(patsubst $(SRCD)/%.c,$(OBJD)/$(SRCD)/%.o,$(SRCS))
|
||||
|
||||
.PHONY:all
|
||||
all:$(BIND)/$(NAME).a
|
||||
|
||||
$(OBJD)/%.o:%.c
|
||||
@echo "building source object $@"
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
$(BIND)/$(NAME).a:$(OBJS)
|
||||
@echo "compiling $@"
|
||||
@mkdir -p $(BIND)
|
||||
@ar rvs $(BIND)/$(NAME).a $(OBJS)
|
||||
|
||||
clean:
|
||||
@echo "cleaning workspace"
|
||||
@rm -rf $(BIND)
|
||||
@rm -rf $(OBJD)
|
|
@ -0,0 +1,57 @@
|
|||
# Termbox
|
||||
[Termbox](https://github.com/nsf/termbox)
|
||||
was a promising Text User Interface (TUI) library.
|
||||
Unfortunately, its original author
|
||||
[changed his mind](https://github.com/nsf/termbox/issues/37#issuecomment-261075481)
|
||||
about consoles and despite the
|
||||
[community's efforts](https://github.com/nsf/termbox/pull/104#issuecomment-300308156)
|
||||
to keep the library's development going, preferred to let it die. Before it happened,
|
||||
[some people](https://wiki.musl-libc.org/alternatives.html)
|
||||
already noticed the robustness of the initial architecture
|
||||
[became compromised](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff#commitcomment-3790714)
|
||||
in a nonsensical refactoring frenzy. Now, the author refuses to merge features
|
||||
like true-color support, invoking some
|
||||
[spurious correlations](https://github.com/nsf/termbox/pull/104#issuecomment-300292223)
|
||||
we will discuss no further.
|
||||
|
||||
## The new Termbox-next
|
||||
This fork was made to restore the codebase to its original quality (before
|
||||
[66c3f91](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff))
|
||||
while providing all the functionnalities of the current implementation.
|
||||
This was achieved by branching at
|
||||
[a2e217f](https://github.com/nsf/termbox/commit/a2e217f0fb97e6bbd589136ea3945f9d5a123d81)
|
||||
and cherry-picking all the commits up to
|
||||
[d63b83a](https://github.com/nsf/termbox/commit/d63b83af04e0fd6da836bb8f37e5cec72a1dc95a)
|
||||
if they weren't harmful.
|
||||
|
||||
## Changes
|
||||
A lot of things changed during the process:
|
||||
- *waf*, the original build system, was completely removed from the
|
||||
project and replaced by make.
|
||||
- anything related to python was removed as well
|
||||
|
||||
## Getting started
|
||||
Termbox's interface only consists of 12 functions:
|
||||
```
|
||||
tb_init() // initialization
|
||||
tb_shutdown() // shutdown
|
||||
|
||||
tb_width() // width of the terminal screen
|
||||
tb_height() // height of the terminal screen
|
||||
|
||||
tb_clear() // clear buffer
|
||||
tb_present() // sync internal buffer with terminal
|
||||
|
||||
tb_put_cell()
|
||||
tb_change_cell()
|
||||
tb_blit() // drawing functions
|
||||
|
||||
tb_select_input_mode() // change input mode
|
||||
tb_peek_event() // peek a keyboard event
|
||||
tb_poll_event() // wait for a keyboard event
|
||||
```
|
||||
See src/termbox.h header file for full detail.
|
||||
|
||||
## TL;DR
|
||||
`make` to build a static version of the lib under bin/termbox.a
|
||||
`cd src/demo && make` to build the example programs in src/demo/
|
|
@ -0,0 +1,827 @@
|
|||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "termbox.h"
|
||||
|
||||
struct key
|
||||
{
|
||||
unsigned char x;
|
||||
unsigned char y;
|
||||
uint32_t ch;
|
||||
};
|
||||
|
||||
#define STOP {0,0,0}
|
||||
struct key K_ESC[] = {{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}, STOP};
|
||||
struct key K_F1[] = {{6, 1, 'F'}, {7, 1, '1'}, STOP};
|
||||
struct key K_F2[] = {{9, 1, 'F'}, {10, 1, '2'}, STOP};
|
||||
struct key K_F3[] = {{12, 1, 'F'}, {13, 1, '3'}, STOP};
|
||||
struct key K_F4[] = {{15, 1, 'F'}, {16, 1, '4'}, STOP};
|
||||
struct key K_F5[] = {{19, 1, 'F'}, {20, 1, '5'}, STOP};
|
||||
struct key K_F6[] = {{22, 1, 'F'}, {23, 1, '6'}, STOP};
|
||||
struct key K_F7[] = {{25, 1, 'F'}, {26, 1, '7'}, STOP};
|
||||
struct key K_F8[] = {{28, 1, 'F'}, {29, 1, '8'}, STOP};
|
||||
struct key K_F9[] = {{33, 1, 'F'}, {34, 1, '9'}, STOP};
|
||||
struct key K_F10[] = {{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}, STOP};
|
||||
struct key K_F11[] = {{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}, STOP};
|
||||
struct key K_F12[] = {{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}, STOP};
|
||||
struct key K_PRN[] = {{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}, STOP};
|
||||
struct key K_SCR[] = {{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}, STOP};
|
||||
struct key K_BRK[] = {{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}, STOP};
|
||||
struct key K_LED1[] = {{66, 1, '-'}, STOP};
|
||||
struct key K_LED2[] = {{70, 1, '-'}, STOP};
|
||||
struct key K_LED3[] = {{74, 1, '-'}, STOP};
|
||||
|
||||
struct key K_TILDE[] = {{1, 4, '`'}, STOP};
|
||||
struct key K_TILDE_SHIFT[] = {{1, 4, '~'}, STOP};
|
||||
struct key K_1[] = {{4, 4, '1'}, STOP};
|
||||
struct key K_1_SHIFT[] = {{4, 4, '!'}, STOP};
|
||||
struct key K_2[] = {{7, 4, '2'}, STOP};
|
||||
struct key K_2_SHIFT[] = {{7, 4, '@'}, STOP};
|
||||
struct key K_3[] = {{10, 4, '3'}, STOP};
|
||||
struct key K_3_SHIFT[] = {{10, 4, '#'}, STOP};
|
||||
struct key K_4[] = {{13, 4, '4'}, STOP};
|
||||
struct key K_4_SHIFT[] = {{13, 4, '$'}, STOP};
|
||||
struct key K_5[] = {{16, 4, '5'}, STOP};
|
||||
struct key K_5_SHIFT[] = {{16, 4, '%'}, STOP};
|
||||
struct key K_6[] = {{19, 4, '6'}, STOP};
|
||||
struct key K_6_SHIFT[] = {{19, 4, '^'}, STOP};
|
||||
struct key K_7[] = {{22, 4, '7'}, STOP};
|
||||
struct key K_7_SHIFT[] = {{22, 4, '&'}, STOP};
|
||||
struct key K_8[] = {{25, 4, '8'}, STOP};
|
||||
struct key K_8_SHIFT[] = {{25, 4, '*'}, STOP};
|
||||
struct key K_9[] = {{28, 4, '9'}, STOP};
|
||||
struct key K_9_SHIFT[] = {{28, 4, '('}, STOP};
|
||||
struct key K_0[] = {{31, 4, '0'}, STOP};
|
||||
struct key K_0_SHIFT[] = {{31, 4, ')'}, STOP};
|
||||
struct key K_MINUS[] = {{34, 4, '-'}, STOP};
|
||||
struct key K_MINUS_SHIFT[] = {{34, 4, '_'}, STOP};
|
||||
struct key K_EQUALS[] = {{37, 4, '='}, STOP};
|
||||
struct key K_EQUALS_SHIFT[] = {{37, 4, '+'}, STOP};
|
||||
struct key K_BACKSLASH[] = {{40, 4, '\\'}, STOP};
|
||||
struct key K_BACKSLASH_SHIFT[] = {{40, 4, '|'}, STOP};
|
||||
struct key K_BACKSPACE[] = {{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}, STOP};
|
||||
struct key K_INS[] = {{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}, STOP};
|
||||
struct key K_HOM[] = {{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}, STOP};
|
||||
struct key K_PGU[] = {{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}, STOP};
|
||||
struct key K_K_NUMLOCK[] = {{65, 4, 'N'}, STOP};
|
||||
struct key K_K_SLASH[] = {{68, 4, '/'}, STOP};
|
||||
struct key K_K_STAR[] = {{71, 4, '*'}, STOP};
|
||||
struct key K_K_MINUS[] = {{74, 4, '-'}, STOP};
|
||||
|
||||
struct key K_TAB[] = {{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}, STOP};
|
||||
struct key K_q[] = {{6, 6, 'q'}, STOP};
|
||||
struct key K_Q[] = {{6, 6, 'Q'}, STOP};
|
||||
struct key K_w[] = {{9, 6, 'w'}, STOP};
|
||||
struct key K_W[] = {{9, 6, 'W'}, STOP};
|
||||
struct key K_e[] = {{12, 6, 'e'}, STOP};
|
||||
struct key K_E[] = {{12, 6, 'E'}, STOP};
|
||||
struct key K_r[] = {{15, 6, 'r'}, STOP};
|
||||
struct key K_R[] = {{15, 6, 'R'}, STOP};
|
||||
struct key K_t[] = {{18, 6, 't'}, STOP};
|
||||
struct key K_T[] = {{18, 6, 'T'}, STOP};
|
||||
struct key K_y[] = {{21, 6, 'y'}, STOP};
|
||||
struct key K_Y[] = {{21, 6, 'Y'}, STOP};
|
||||
struct key K_u[] = {{24, 6, 'u'}, STOP};
|
||||
struct key K_U[] = {{24, 6, 'U'}, STOP};
|
||||
struct key K_i[] = {{27, 6, 'i'}, STOP};
|
||||
struct key K_I[] = {{27, 6, 'I'}, STOP};
|
||||
struct key K_o[] = {{30, 6, 'o'}, STOP};
|
||||
struct key K_O[] = {{30, 6, 'O'}, STOP};
|
||||
struct key K_p[] = {{33, 6, 'p'}, STOP};
|
||||
struct key K_P[] = {{33, 6, 'P'}, STOP};
|
||||
struct key K_LSQB[] = {{36, 6, '['}, STOP};
|
||||
struct key K_LCUB[] = {{36, 6, '{'}, STOP};
|
||||
struct key K_RSQB[] = {{39, 6, ']'}, STOP};
|
||||
struct key K_RCUB[] = {{39, 6, '}'}, STOP};
|
||||
struct key K_ENTER[] =
|
||||
{
|
||||
{43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591},
|
||||
{43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591},
|
||||
{41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591},
|
||||
{45, 8, 0x2591}, {46, 8, 0x2591}, STOP
|
||||
};
|
||||
struct key K_DEL[] = {{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}, STOP};
|
||||
struct key K_END[] = {{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}, STOP};
|
||||
struct key K_PGD[] = {{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}, STOP};
|
||||
struct key K_K_7[] = {{65, 6, '7'}, STOP};
|
||||
struct key K_K_8[] = {{68, 6, '8'}, STOP};
|
||||
struct key K_K_9[] = {{71, 6, '9'}, STOP};
|
||||
struct key K_K_PLUS[] = {{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}, STOP};
|
||||
|
||||
struct key K_CAPS[] = {{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}, STOP};
|
||||
struct key K_a[] = {{7, 8, 'a'}, STOP};
|
||||
struct key K_A[] = {{7, 8, 'A'}, STOP};
|
||||
struct key K_s[] = {{10, 8, 's'}, STOP};
|
||||
struct key K_S[] = {{10, 8, 'S'}, STOP};
|
||||
struct key K_d[] = {{13, 8, 'd'}, STOP};
|
||||
struct key K_D[] = {{13, 8, 'D'}, STOP};
|
||||
struct key K_f[] = {{16, 8, 'f'}, STOP};
|
||||
struct key K_F[] = {{16, 8, 'F'}, STOP};
|
||||
struct key K_g[] = {{19, 8, 'g'}, STOP};
|
||||
struct key K_G[] = {{19, 8, 'G'}, STOP};
|
||||
struct key K_h[] = {{22, 8, 'h'}, STOP};
|
||||
struct key K_H[] = {{22, 8, 'H'}, STOP};
|
||||
struct key K_j[] = {{25, 8, 'j'}, STOP};
|
||||
struct key K_J[] = {{25, 8, 'J'}, STOP};
|
||||
struct key K_k[] = {{28, 8, 'k'}, STOP};
|
||||
struct key K_K[] = {{28, 8, 'K'}, STOP};
|
||||
struct key K_l[] = {{31, 8, 'l'}, STOP};
|
||||
struct key K_L[] = {{31, 8, 'L'}, STOP};
|
||||
struct key K_SEMICOLON[] = {{34, 8, ';'}, STOP};
|
||||
struct key K_PARENTHESIS[] = {{34, 8, ':'}, STOP};
|
||||
struct key K_QUOTE[] = {{37, 8, '\''}, STOP};
|
||||
struct key K_DOUBLEQUOTE[] = {{37, 8, '"'}, STOP};
|
||||
struct key K_K_4[] = {{65, 8, '4'}, STOP};
|
||||
struct key K_K_5[] = {{68, 8, '5'}, STOP};
|
||||
struct key K_K_6[] = {{71, 8, '6'}, STOP};
|
||||
|
||||
struct key K_LSHIFT[] = {{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}, STOP};
|
||||
struct key K_z[] = {{9, 10, 'z'}, STOP};
|
||||
struct key K_Z[] = {{9, 10, 'Z'}, STOP};
|
||||
struct key K_x[] = {{12, 10, 'x'}, STOP};
|
||||
struct key K_X[] = {{12, 10, 'X'}, STOP};
|
||||
struct key K_c[] = {{15, 10, 'c'}, STOP};
|
||||
struct key K_C[] = {{15, 10, 'C'}, STOP};
|
||||
struct key K_v[] = {{18, 10, 'v'}, STOP};
|
||||
struct key K_V[] = {{18, 10, 'V'}, STOP};
|
||||
struct key K_b[] = {{21, 10, 'b'}, STOP};
|
||||
struct key K_B[] = {{21, 10, 'B'}, STOP};
|
||||
struct key K_n[] = {{24, 10, 'n'}, STOP};
|
||||
struct key K_N[] = {{24, 10, 'N'}, STOP};
|
||||
struct key K_m[] = {{27, 10, 'm'}, STOP};
|
||||
struct key K_M[] = {{27, 10, 'M'}, STOP};
|
||||
struct key K_COMMA[] = {{30, 10, ','}, STOP};
|
||||
struct key K_LANB[] = {{30, 10, '<'}, STOP};
|
||||
struct key K_PERIOD[] = {{33, 10, '.'}, STOP};
|
||||
struct key K_RANB[] = {{33, 10, '>'}, STOP};
|
||||
struct key K_SLASH[] = {{36, 10, '/'}, STOP};
|
||||
struct key K_QUESTION[] = {{36, 10, '?'}, STOP};
|
||||
struct key K_RSHIFT[] = {{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}, STOP};
|
||||
struct key K_ARROW_UP[] = {{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}, STOP};
|
||||
struct key K_K_1[] = {{65, 10, '1'}, STOP};
|
||||
struct key K_K_2[] = {{68, 10, '2'}, STOP};
|
||||
struct key K_K_3[] = {{71, 10, '3'}, STOP};
|
||||
struct key K_K_ENTER[] = {{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}, STOP};
|
||||
|
||||
struct key K_LCTRL[] = {{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}, STOP};
|
||||
struct key K_LWIN[] = {{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}, STOP};
|
||||
struct key K_LALT[] = {{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}, STOP};
|
||||
struct key K_SPACE[] =
|
||||
{
|
||||
{14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '},
|
||||
{19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'},
|
||||
{24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '},
|
||||
STOP
|
||||
};
|
||||
struct key K_RALT[] = {{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}, STOP};
|
||||
struct key K_RWIN[] = {{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}, STOP};
|
||||
struct key K_RPROP[] = {{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}, STOP};
|
||||
struct key K_RCTRL[] = {{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}, STOP};
|
||||
struct key K_ARROW_LEFT[] = {{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}, STOP};
|
||||
struct key K_ARROW_DOWN[] = {{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}, STOP};
|
||||
struct key K_ARROW_RIGHT[] = {{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}, STOP};
|
||||
struct key K_K_0[] = {{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}, STOP};
|
||||
struct key K_K_PERIOD[] = {{71, 12, '.'}, STOP};
|
||||
|
||||
struct combo
|
||||
{
|
||||
struct key* keys[6];
|
||||
};
|
||||
|
||||
struct combo combos[] =
|
||||
{
|
||||
{{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_A, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_B, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_C, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_D, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_E, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_F, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_G, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_J, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_K, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_L, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_N, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_O, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_P, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_Q, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_R, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_S, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_T, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_U, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_V, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_W, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_X, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_Y, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_Z, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_6, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}},
|
||||
{{K_SPACE, 0}},
|
||||
{{K_1_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_3_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_4_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_5_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_7_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_QUOTE, 0}},
|
||||
{{K_9_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_0_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_COMMA, 0}},
|
||||
{{K_MINUS, K_K_MINUS, 0}},
|
||||
{{K_PERIOD, K_K_PERIOD, 0}},
|
||||
{{K_SLASH, K_K_SLASH, 0}},
|
||||
{{K_0, K_K_0, 0}},
|
||||
{{K_1, K_K_1, 0}},
|
||||
{{K_2, K_K_2, 0}},
|
||||
{{K_3, K_K_3, 0}},
|
||||
{{K_4, K_K_4, 0}},
|
||||
{{K_5, K_K_5, 0}},
|
||||
{{K_6, K_K_6, 0}},
|
||||
{{K_7, K_K_7, 0}},
|
||||
{{K_8, K_K_8, 0}},
|
||||
{{K_9, K_K_9, 0}},
|
||||
{{K_PARENTHESIS, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_SEMICOLON, 0}},
|
||||
{{K_LANB, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_EQUALS, 0}},
|
||||
{{K_RANB, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_QUESTION, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_2_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_A, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_B, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_C, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_D, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_E, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_F, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_G, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_H, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_I, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_J, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_K, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_L, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_M, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_N, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_O, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_P, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_Q, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_R, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_S, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_T, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_U, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_V, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_W, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_X, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_Y, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_Z, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_LSQB, 0}},
|
||||
{{K_BACKSLASH, 0}},
|
||||
{{K_RSQB, 0}},
|
||||
{{K_6_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_TILDE, 0}},
|
||||
{{K_a, 0}},
|
||||
{{K_b, 0}},
|
||||
{{K_c, 0}},
|
||||
{{K_d, 0}},
|
||||
{{K_e, 0}},
|
||||
{{K_f, 0}},
|
||||
{{K_g, 0}},
|
||||
{{K_h, 0}},
|
||||
{{K_i, 0}},
|
||||
{{K_j, 0}},
|
||||
{{K_k, 0}},
|
||||
{{K_l, 0}},
|
||||
{{K_m, 0}},
|
||||
{{K_n, 0}},
|
||||
{{K_o, 0}},
|
||||
{{K_p, 0}},
|
||||
{{K_q, 0}},
|
||||
{{K_r, 0}},
|
||||
{{K_s, 0}},
|
||||
{{K_t, 0}},
|
||||
{{K_u, 0}},
|
||||
{{K_v, 0}},
|
||||
{{K_w, 0}},
|
||||
{{K_x, 0}},
|
||||
{{K_y, 0}},
|
||||
{{K_z, 0}},
|
||||
{{K_LCUB, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_RCUB, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT, 0}},
|
||||
{{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}
|
||||
};
|
||||
|
||||
struct combo func_combos[] =
|
||||
{
|
||||
{{K_F1, 0}},
|
||||
{{K_F2, 0}},
|
||||
{{K_F3, 0}},
|
||||
{{K_F4, 0}},
|
||||
{{K_F5, 0}},
|
||||
{{K_F6, 0}},
|
||||
{{K_F7, 0}},
|
||||
{{K_F8, 0}},
|
||||
{{K_F9, 0}},
|
||||
{{K_F10, 0}},
|
||||
{{K_F11, 0}},
|
||||
{{K_F12, 0}},
|
||||
{{K_INS, 0}},
|
||||
{{K_DEL, 0}},
|
||||
{{K_HOM, 0}},
|
||||
{{K_END, 0}},
|
||||
{{K_PGU, 0}},
|
||||
{{K_PGD, 0}},
|
||||
{{K_ARROW_UP, 0}},
|
||||
{{K_ARROW_DOWN, 0}},
|
||||
{{K_ARROW_LEFT, 0}},
|
||||
{{K_ARROW_RIGHT, 0}}
|
||||
};
|
||||
|
||||
void print_tb(const char* str, int x, int y, uint32_t fg, uint32_t bg)
|
||||
{
|
||||
while (*str)
|
||||
{
|
||||
uint32_t uni;
|
||||
str += utf8_char_to_unicode(&uni, str);
|
||||
tb_change_cell(x, y, uni, fg, bg);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
void printf_tb(int x, int y, uint32_t fg, uint32_t bg, const char* fmt, ...)
|
||||
{
|
||||
char buf[4096];
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, vl);
|
||||
va_end(vl);
|
||||
print_tb(buf, x, y, fg, bg);
|
||||
}
|
||||
|
||||
void draw_key(struct key* k, uint32_t fg, uint32_t bg)
|
||||
{
|
||||
while (k->x)
|
||||
{
|
||||
tb_change_cell(k->x + 2, k->y + 4, k->ch, fg, bg);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_keyboard()
|
||||
{
|
||||
int i;
|
||||
tb_change_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT);
|
||||
|
||||
for (i = 1; i < 79; ++i)
|
||||
{
|
||||
tb_change_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT);
|
||||
}
|
||||
|
||||
for (i = 1; i < 23; ++i)
|
||||
{
|
||||
tb_change_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT);
|
||||
}
|
||||
|
||||
tb_change_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT);
|
||||
tb_change_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT);
|
||||
|
||||
for (i = 5; i < 17; ++i)
|
||||
{
|
||||
tb_change_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW);
|
||||
tb_change_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW);
|
||||
}
|
||||
|
||||
draw_key(K_ESC, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F1, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F2, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F3, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F4, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F5, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F6, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F7, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F8, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F9, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F10, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F11, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_F12, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_PRN, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_SCR, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_BRK, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LED1, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LED2, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LED3, TB_WHITE, TB_BLUE);
|
||||
|
||||
draw_key(K_TILDE, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_1, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_2, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_3, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_4, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_5, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_6, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_7, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_8, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_9, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_0, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_MINUS, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_EQUALS, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_INS, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_HOM, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_PGU, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_SLASH, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_STAR, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_MINUS, TB_WHITE, TB_BLUE);
|
||||
|
||||
draw_key(K_TAB, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_q, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_w, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_e, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_r, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_t, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_y, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_u, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_i, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_o, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_p, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LSQB, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RSQB, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_ENTER, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_DEL, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_END, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_PGD, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_7, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_8, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_9, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_PLUS, TB_WHITE, TB_BLUE);
|
||||
|
||||
draw_key(K_CAPS, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_a, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_s, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_d, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_f, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_g, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_h, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_j, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_k, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_l, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_QUOTE, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_4, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_5, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_6, TB_WHITE, TB_BLUE);
|
||||
|
||||
draw_key(K_LSHIFT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_z, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_x, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_c, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_v, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_b, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_n, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_m, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_COMMA, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_PERIOD, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_SLASH, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RSHIFT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_1, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_2, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_3, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_ENTER, TB_WHITE, TB_BLUE);
|
||||
|
||||
draw_key(K_LCTRL, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LWIN, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_LALT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_SPACE, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RCTRL, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RPROP, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RWIN, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_RALT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_0, TB_WHITE, TB_BLUE);
|
||||
draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE);
|
||||
|
||||
printf_tb(33, 1, TB_MAGENTA | TB_BOLD, TB_DEFAULT, "Keyboard demo!");
|
||||
printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT,
|
||||
"(press CTRL+X and then CTRL+Q to exit)");
|
||||
printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT,
|
||||
"(press CTRL+X and then CTRL+C to change input mode)");
|
||||
|
||||
int inputmode = tb_select_input_mode(0);
|
||||
char inputmode_str[64];
|
||||
|
||||
if (inputmode & TB_INPUT_ESC)
|
||||
{
|
||||
sprintf(inputmode_str, "TB_INPUT_ESC");
|
||||
}
|
||||
|
||||
if (inputmode & TB_INPUT_ALT)
|
||||
{
|
||||
sprintf(inputmode_str, "TB_INPUT_ALT");
|
||||
}
|
||||
|
||||
if (inputmode & TB_INPUT_MOUSE)
|
||||
{
|
||||
sprintf(inputmode_str + 12, " | TB_INPUT_MOUSE");
|
||||
}
|
||||
|
||||
printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str);
|
||||
}
|
||||
|
||||
const char* funckeymap(int k)
|
||||
{
|
||||
static const char* fcmap[] =
|
||||
{
|
||||
"CTRL+2, CTRL+~",
|
||||
"CTRL+A",
|
||||
"CTRL+B",
|
||||
"CTRL+C",
|
||||
"CTRL+D",
|
||||
"CTRL+E",
|
||||
"CTRL+F",
|
||||
"CTRL+G",
|
||||
"CTRL+H, BACKSPACE",
|
||||
"CTRL+I, TAB",
|
||||
"CTRL+J",
|
||||
"CTRL+K",
|
||||
"CTRL+L",
|
||||
"CTRL+M, ENTER",
|
||||
"CTRL+N",
|
||||
"CTRL+O",
|
||||
"CTRL+P",
|
||||
"CTRL+Q",
|
||||
"CTRL+R",
|
||||
"CTRL+S",
|
||||
"CTRL+T",
|
||||
"CTRL+U",
|
||||
"CTRL+V",
|
||||
"CTRL+W",
|
||||
"CTRL+X",
|
||||
"CTRL+Y",
|
||||
"CTRL+Z",
|
||||
"CTRL+3, ESC, CTRL+[",
|
||||
"CTRL+4, CTRL+\\",
|
||||
"CTRL+5, CTRL+]",
|
||||
"CTRL+6",
|
||||
"CTRL+7, CTRL+/, CTRL+_",
|
||||
"SPACE"
|
||||
};
|
||||
static const char* fkmap[] =
|
||||
{
|
||||
"F1",
|
||||
"F2",
|
||||
"F3",
|
||||
"F4",
|
||||
"F5",
|
||||
"F6",
|
||||
"F7",
|
||||
"F8",
|
||||
"F9",
|
||||
"F10",
|
||||
"F11",
|
||||
"F12",
|
||||
"INSERT",
|
||||
"DELETE",
|
||||
"HOME",
|
||||
"END",
|
||||
"PGUP",
|
||||
"PGDN",
|
||||
"ARROW UP",
|
||||
"ARROW DOWN",
|
||||
"ARROW LEFT",
|
||||
"ARROW RIGHT"
|
||||
};
|
||||
|
||||
if (k == TB_KEY_CTRL_8)
|
||||
{
|
||||
return "CTRL+8, BACKSPACE 2"; // 0x7F
|
||||
}
|
||||
else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF)
|
||||
{
|
||||
return fkmap[0xFFFF - k];
|
||||
}
|
||||
else if (k <= TB_KEY_SPACE)
|
||||
{
|
||||
return fcmap[k];
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
void pretty_print_press(struct tb_event* ev)
|
||||
{
|
||||
char buf[7];
|
||||
buf[utf8_unicode_to_char(buf, ev->ch)] = '\0';
|
||||
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Key: ");
|
||||
printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key);
|
||||
printf_tb(8, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->key);
|
||||
printf_tb(8, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->key);
|
||||
printf_tb(8, 22, TB_RED, TB_DEFAULT, "string: %s", funckeymap(ev->key));
|
||||
|
||||
printf_tb(54, 19, TB_WHITE, TB_DEFAULT, "Char: ");
|
||||
printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch);
|
||||
printf_tb(60, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->ch);
|
||||
printf_tb(60, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->ch);
|
||||
printf_tb(60, 22, TB_RED, TB_DEFAULT, "string: %s", buf);
|
||||
|
||||
printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %s",
|
||||
(ev->mod) ? "TB_MOD_ALT" : "none");
|
||||
|
||||
}
|
||||
|
||||
void pretty_print_resize(struct tb_event* ev)
|
||||
{
|
||||
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
|
||||
void pretty_print_mouse(struct tb_event* ev)
|
||||
{
|
||||
printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d", ev->x, ev->y);
|
||||
char* btn = "";
|
||||
|
||||
switch (ev->key)
|
||||
{
|
||||
case TB_KEY_MOUSE_LEFT:
|
||||
btn = "MouseLeft: %d";
|
||||
break;
|
||||
|
||||
case TB_KEY_MOUSE_MIDDLE:
|
||||
btn = "MouseMiddle: %d";
|
||||
break;
|
||||
|
||||
case TB_KEY_MOUSE_RIGHT:
|
||||
btn = "MouseRight: %d";
|
||||
break;
|
||||
|
||||
case TB_KEY_MOUSE_WHEEL_UP:
|
||||
btn = "MouseWheelUp: %d";
|
||||
break;
|
||||
|
||||
case TB_KEY_MOUSE_WHEEL_DOWN:
|
||||
btn = "MouseWheelDown: %d";
|
||||
break;
|
||||
|
||||
case TB_KEY_MOUSE_RELEASE:
|
||||
btn = "MouseRelease: %d";
|
||||
}
|
||||
|
||||
counter++;
|
||||
printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: ");
|
||||
printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter);
|
||||
}
|
||||
|
||||
void dispatch_press(struct tb_event* ev)
|
||||
{
|
||||
if (ev->mod & TB_MOD_ALT)
|
||||
{
|
||||
draw_key(K_LALT, TB_WHITE, TB_RED);
|
||||
draw_key(K_RALT, TB_WHITE, TB_RED);
|
||||
}
|
||||
|
||||
struct combo* k = 0;
|
||||
|
||||
if (ev->key >= TB_KEY_ARROW_RIGHT)
|
||||
{
|
||||
k = &func_combos[0xFFFF - ev->key];
|
||||
}
|
||||
else if (ev->ch < 128)
|
||||
{
|
||||
if (ev->ch == 0 && ev->key < 128)
|
||||
{
|
||||
k = &combos[ev->key];
|
||||
}
|
||||
else
|
||||
{
|
||||
k = &combos[ev->ch];
|
||||
}
|
||||
}
|
||||
|
||||
if (!k)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
struct key** keys = k->keys;
|
||||
|
||||
while (*keys)
|
||||
{
|
||||
draw_key(*keys, TB_WHITE, TB_RED);
|
||||
keys++;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
int ret;
|
||||
|
||||
ret = tb_init();
|
||||
|
||||
if (ret)
|
||||
{
|
||||
fprintf(stderr, "tb_init() failed with error code %d\n", ret);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
|
||||
struct tb_event ev;
|
||||
|
||||
tb_clear();
|
||||
draw_keyboard();
|
||||
tb_present();
|
||||
int inputmode = 0;
|
||||
int ctrlxpressed = 0;
|
||||
|
||||
while (tb_poll_event(&ev))
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case TB_EVENT_KEY:
|
||||
if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed)
|
||||
{
|
||||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ev.key == TB_KEY_CTRL_C && ctrlxpressed)
|
||||
{
|
||||
static int chmap[] =
|
||||
{
|
||||
TB_INPUT_ESC | TB_INPUT_MOUSE, // 101
|
||||
TB_INPUT_ALT | TB_INPUT_MOUSE, // 110
|
||||
TB_INPUT_ESC, // 001
|
||||
TB_INPUT_ALT, // 010
|
||||
};
|
||||
inputmode++;
|
||||
|
||||
if (inputmode >= 4)
|
||||
{
|
||||
inputmode = 0;
|
||||
}
|
||||
|
||||
tb_select_input_mode(chmap[inputmode]);
|
||||
}
|
||||
|
||||
if (ev.key == TB_KEY_CTRL_X)
|
||||
{
|
||||
ctrlxpressed = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctrlxpressed = 0;
|
||||
}
|
||||
|
||||
tb_clear();
|
||||
draw_keyboard();
|
||||
dispatch_press(&ev);
|
||||
pretty_print_press(&ev);
|
||||
tb_present();
|
||||
break;
|
||||
|
||||
case TB_EVENT_RESIZE:
|
||||
tb_clear();
|
||||
draw_keyboard();
|
||||
pretty_print_resize(&ev);
|
||||
tb_present();
|
||||
break;
|
||||
|
||||
case TB_EVENT_MOUSE:
|
||||
tb_clear();
|
||||
draw_keyboard();
|
||||
pretty_print_mouse(&ev);
|
||||
tb_present();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
CC=gcc
|
||||
FLAGS=-std=c99 -pedantic -Wall -Werror -g -static
|
||||
INCL=-I../
|
||||
BIND=../../bin
|
||||
|
||||
%.o:%.c
|
||||
@echo "building source object $@"
|
||||
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
|
||||
|
||||
all:keyboard output paint truecolor
|
||||
|
||||
keyboard:keyboard.o
|
||||
@echo "compiling $@"
|
||||
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
|
||||
|
||||
output:output.o
|
||||
@echo "compiling $@"
|
||||
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
|
||||
|
||||
paint:paint.o
|
||||
@echo "compiling $@"
|
||||
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
|
||||
|
||||
truecolor:truecolor.o
|
||||
@echo "compiling $@"
|
||||
@$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a
|
||||
|
||||
clean:
|
||||
@echo "cleaning workspace"
|
||||
@rm -rf *.o keyboard output paint truecolor
|
|
@ -0,0 +1,156 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "../termbox.h"
|
||||
|
||||
static const char chars[] = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB";
|
||||
|
||||
static const uint32_t all_attrs[] =
|
||||
{
|
||||
0,
|
||||
TB_BOLD,
|
||||
TB_UNDERLINE,
|
||||
TB_BOLD | TB_UNDERLINE,
|
||||
};
|
||||
|
||||
static int next_char(int current)
|
||||
{
|
||||
current++;
|
||||
|
||||
if (!chars[current])
|
||||
{
|
||||
current = 0;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
static void draw_line(int x, int y, uint32_t bg)
|
||||
{
|
||||
int a, c;
|
||||
int current_char = 0;
|
||||
|
||||
for (a = 0; a < 4; a++)
|
||||
{
|
||||
for (c = TB_DEFAULT; c <= TB_WHITE; c++)
|
||||
{
|
||||
uint32_t fg = all_attrs[a] | c;
|
||||
tb_change_cell(x, y, chars[current_char], fg, bg);
|
||||
current_char = next_char(current_char);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_combinations_table(int sx, int sy, const uint32_t* attrs,
|
||||
int attrs_n)
|
||||
{
|
||||
int i, c;
|
||||
|
||||
for (i = 0; i < attrs_n; i++)
|
||||
{
|
||||
for (c = TB_DEFAULT; c <= TB_WHITE; c++)
|
||||
{
|
||||
uint32_t bg = attrs[i] | c;
|
||||
draw_line(sx, sy, bg);
|
||||
sy++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_all()
|
||||
{
|
||||
tb_clear();
|
||||
|
||||
tb_select_output_mode(TB_OUTPUT_NORMAL);
|
||||
static const uint32_t col1[] = {0, TB_BOLD};
|
||||
static const uint32_t col2[] = {TB_REVERSE};
|
||||
print_combinations_table(1, 1, col1, 2);
|
||||
print_combinations_table(2 + strlen(chars), 1, col2, 1);
|
||||
tb_present();
|
||||
|
||||
tb_select_output_mode(TB_OUTPUT_GRAYSCALE);
|
||||
int c, x, y;
|
||||
|
||||
for (x = 0, y = 23; x < 24; ++x)
|
||||
{
|
||||
tb_change_cell(x, y, '@', x, 0);
|
||||
tb_change_cell(x + 25, y, ' ', 0, x);
|
||||
}
|
||||
|
||||
tb_present();
|
||||
|
||||
tb_select_output_mode(TB_OUTPUT_216);
|
||||
y++;
|
||||
|
||||
for (c = 0, x = 0; c < 216; ++c, ++x)
|
||||
{
|
||||
if (!(x % 24))
|
||||
{
|
||||
x = 0;
|
||||
++y;
|
||||
}
|
||||
|
||||
tb_change_cell(x, y, '@', c, 0);
|
||||
tb_change_cell(x + 25, y, ' ', 0, c);
|
||||
}
|
||||
|
||||
tb_present();
|
||||
|
||||
tb_select_output_mode(TB_OUTPUT_256);
|
||||
y++;
|
||||
|
||||
for (c = 0, x = 0; c < 256; ++c, ++x)
|
||||
{
|
||||
if (!(x % 24))
|
||||
{
|
||||
x = 0;
|
||||
++y;
|
||||
}
|
||||
|
||||
tb_change_cell(x, y, '+', c | ((y & 1) ? TB_UNDERLINE : 0), 0);
|
||||
tb_change_cell(x + 25, y, ' ', 0, c);
|
||||
}
|
||||
|
||||
tb_present();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
int ret = tb_init();
|
||||
|
||||
if (ret)
|
||||
{
|
||||
fprintf(stderr, "tb_init() failed with error code %d\n", ret);
|
||||
return 1;
|
||||
}
|
||||
|
||||
draw_all();
|
||||
|
||||
struct tb_event ev;
|
||||
|
||||
while (tb_poll_event(&ev))
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case TB_EVENT_KEY:
|
||||
switch (ev.key)
|
||||
{
|
||||
case TB_KEY_ESC:
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TB_EVENT_RESIZE:
|
||||
draw_all();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
#include "../termbox.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int curCol = 0;
|
||||
static int curRune = 0;
|
||||
static struct tb_cell* backbuf;
|
||||
static int bbw = 0, bbh = 0;
|
||||
|
||||
static const uint32_t runes[] =
|
||||
{
|
||||
0x20, // ' '
|
||||
0x2591, // '░'
|
||||
0x2592, // '▒'
|
||||
0x2593, // '▓'
|
||||
0x2588, // '█'
|
||||
};
|
||||
|
||||
#define len(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
static const uint32_t colors[] =
|
||||
{
|
||||
TB_BLACK,
|
||||
TB_RED,
|
||||
TB_GREEN,
|
||||
TB_YELLOW,
|
||||
TB_BLUE,
|
||||
TB_MAGENTA,
|
||||
TB_CYAN,
|
||||
TB_WHITE,
|
||||
};
|
||||
|
||||
void updateAndDrawButtons(int* current, int x, int y, int mx, int my, int n,
|
||||
void (*attrFunc)(int, uint32_t*, uint32_t*, uint32_t*))
|
||||
{
|
||||
int lx = x;
|
||||
int ly = y;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (lx <= mx && mx <= lx + 3 && ly <= my && my <= ly + 1)
|
||||
{
|
||||
*current = i;
|
||||
}
|
||||
|
||||
uint32_t r;
|
||||
uint32_t fg, bg;
|
||||
(*attrFunc)(i, &r, &fg, &bg);
|
||||
tb_change_cell(lx + 0, ly + 0, r, fg, bg);
|
||||
tb_change_cell(lx + 1, ly + 0, r, fg, bg);
|
||||
tb_change_cell(lx + 2, ly + 0, r, fg, bg);
|
||||
tb_change_cell(lx + 3, ly + 0, r, fg, bg);
|
||||
tb_change_cell(lx + 0, ly + 1, r, fg, bg);
|
||||
tb_change_cell(lx + 1, ly + 1, r, fg, bg);
|
||||
tb_change_cell(lx + 2, ly + 1, r, fg, bg);
|
||||
tb_change_cell(lx + 3, ly + 1, r, fg, bg);
|
||||
lx += 4;
|
||||
}
|
||||
|
||||
lx = x;
|
||||
ly = y;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (*current == i)
|
||||
{
|
||||
uint32_t fg = TB_RED | TB_BOLD;
|
||||
uint32_t bg = TB_DEFAULT;
|
||||
tb_change_cell(lx + 0, ly + 2, '^', fg, bg);
|
||||
tb_change_cell(lx + 1, ly + 2, '^', fg, bg);
|
||||
tb_change_cell(lx + 2, ly + 2, '^', fg, bg);
|
||||
tb_change_cell(lx + 3, ly + 2, '^', fg, bg);
|
||||
}
|
||||
|
||||
lx += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void runeAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg)
|
||||
{
|
||||
*r = runes[i];
|
||||
*fg = TB_DEFAULT;
|
||||
*bg = TB_DEFAULT;
|
||||
}
|
||||
|
||||
void colorAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg)
|
||||
{
|
||||
*r = ' ';
|
||||
*fg = TB_DEFAULT;
|
||||
*bg = colors[i];
|
||||
}
|
||||
|
||||
void updateAndRedrawAll(int mx, int my)
|
||||
{
|
||||
tb_clear();
|
||||
|
||||
if (mx != -1 && my != -1)
|
||||
{
|
||||
backbuf[bbw * my + mx].ch = runes[curRune];
|
||||
backbuf[bbw * my + mx].fg = colors[curCol];
|
||||
}
|
||||
|
||||
memcpy(tb_cell_buffer(), backbuf, sizeof(struct tb_cell)*bbw * bbh);
|
||||
int h = tb_height();
|
||||
updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), runeAttrFunc);
|
||||
updateAndDrawButtons(&curCol, 0, h - 3, mx, my, len(colors), colorAttrFunc);
|
||||
tb_present();
|
||||
}
|
||||
|
||||
void reallocBackBuffer(int w, int h)
|
||||
{
|
||||
bbw = w;
|
||||
bbh = h;
|
||||
|
||||
if (backbuf)
|
||||
{
|
||||
free(backbuf);
|
||||
}
|
||||
|
||||
backbuf = calloc(sizeof(struct tb_cell), w * h);
|
||||
}
|
||||
|
||||
int main(int argv, char** argc)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
int code = tb_init();
|
||||
|
||||
if (code < 0)
|
||||
{
|
||||
fprintf(stderr, "termbox init failed, code: %d\n", code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
|
||||
int w = tb_width();
|
||||
int h = tb_height();
|
||||
reallocBackBuffer(w, h);
|
||||
updateAndRedrawAll(-1, -1);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
struct tb_event ev;
|
||||
int mx = -1;
|
||||
int my = -1;
|
||||
int t = tb_poll_event(&ev);
|
||||
|
||||
if (t == -1)
|
||||
{
|
||||
tb_shutdown();
|
||||
fprintf(stderr, "termbox poll event error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case TB_EVENT_KEY:
|
||||
if (ev.key == TB_KEY_ESC)
|
||||
{
|
||||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TB_EVENT_MOUSE:
|
||||
if (ev.key == TB_KEY_MOUSE_LEFT)
|
||||
{
|
||||
mx = ev.x;
|
||||
my = ev.y;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TB_EVENT_RESIZE:
|
||||
reallocBackBuffer(ev.w, ev.h);
|
||||
break;
|
||||
}
|
||||
|
||||
updateAndRedrawAll(mx, my);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#include "termbox.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
tb_init();
|
||||
tb_select_output_mode(TB_OUTPUT_TRUECOLOR);
|
||||
int w = tb_width();
|
||||
int h = tb_height();
|
||||
uint32_t bg = 0x000000, fg = 0x000000;
|
||||
tb_clear();
|
||||
int z = 0;
|
||||
|
||||
for (int y = 1; y < h; y++)
|
||||
{
|
||||
for (int x = 1; x < w; x++)
|
||||
{
|
||||
uint32_t ch;
|
||||
utf8_char_to_unicode(&ch, "x");
|
||||
fg = 0;
|
||||
|
||||
if (z % 2 == 0)
|
||||
{
|
||||
fg |= TB_BOLD;
|
||||
}
|
||||
|
||||
if (z % 3 == 0)
|
||||
{
|
||||
fg |= TB_UNDERLINE;
|
||||
}
|
||||
|
||||
if (z % 5 == 0)
|
||||
{
|
||||
fg |= TB_REVERSE;
|
||||
}
|
||||
|
||||
tb_change_cell(x, y, ch, fg, bg);
|
||||
bg += 0x000101;
|
||||
z++;
|
||||
}
|
||||
|
||||
bg += 0x080000;
|
||||
|
||||
if (bg > 0xFFFFFF)
|
||||
{
|
||||
bg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
tb_present();
|
||||
|
||||
while (1)
|
||||
{
|
||||
struct tb_event ev;
|
||||
int t = tb_poll_event(&ev);
|
||||
|
||||
if (t == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (t == TB_EVENT_KEY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tb_shutdown();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "term.h"
|
||||
|
||||
#define BUFFER_SIZE_MAX 16
|
||||
|
||||
// if s1 starts with s2 returns 1, else 0
|
||||
static int starts_with(const char* s1, const char* s2)
|
||||
{
|
||||
// nice huh?
|
||||
while (*s2)
|
||||
{
|
||||
if (*s1++ != *s2++)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_mouse_event(struct tb_event* event, const char* buf, int len)
|
||||
{
|
||||
if ((len >= 6) && starts_with(buf, "\033[M"))
|
||||
{
|
||||
// X10 mouse encoding, the simplest one
|
||||
// \033 [ M Cb Cx Cy
|
||||
int b = buf[3] - 32;
|
||||
|
||||
switch (b & 3)
|
||||
{
|
||||
case 0:
|
||||
if ((b & 64) != 0)
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_WHEEL_UP;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_LEFT;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if ((b & 64) != 0)
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_MIDDLE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
event->key = TB_KEY_MOUSE_RIGHT;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
event->key = TB_KEY_MOUSE_RELEASE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -6;
|
||||
}
|
||||
|
||||
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
|
||||
|
||||
if ((b & 32) != 0)
|
||||
{
|
||||
event->mod |= TB_MOD_MOTION;
|
||||
}
|
||||
|
||||
// the coord is 1,1 for upper left
|
||||
event->x = (uint8_t)buf[4] - 1 - 32;
|
||||
event->y = (uint8_t)buf[5] - 1 - 32;
|
||||
return 6;
|
||||
}
|
||||
else if (starts_with(buf, "\033[<") || starts_with(buf, "\033["))
|
||||
{
|
||||
// xterm 1006 extended mode or urxvt 1015 extended mode
|
||||
// xterm: \033 [ < Cb ; Cx ; Cy (M or m)
|
||||
// urxvt: \033 [ Cb ; Cx ; Cy M
|
||||
int i, mi = -1, starti = -1;
|
||||
int isM, isU, s1 = -1, s2 = -1;
|
||||
int n1 = 0, n2 = 0, n3 = 0;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
// We search the first (s1) and the last (s2) ';'
|
||||
if (buf[i] == ';')
|
||||
{
|
||||
if (s1 == -1)
|
||||
{
|
||||
s1 = i;
|
||||
}
|
||||
|
||||
s2 = i;
|
||||
}
|
||||
|
||||
// We search for the first 'm' or 'M'
|
||||
if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1)
|
||||
{
|
||||
mi = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mi == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// whether it's a capital M or not
|
||||
isM = (buf[mi] == 'M');
|
||||
|
||||
if (buf[2] == '<')
|
||||
{
|
||||
isU = 0;
|
||||
starti = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
isU = 1;
|
||||
starti = 2;
|
||||
}
|
||||
|
||||
if (s1 == -1 || s2 == -1 || s1 == s2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
n1 = strtoul(&buf[starti], NULL, 10);
|
||||
n2 = strtoul(&buf[s1 + 1], NULL, 10);
|
||||
n3 = strtoul(&buf[s2 + 1], NULL, 10);
|
||||
|
||||
if (isU)
|
||||
{
|
||||
n1 -= 32;
|
||||
}
|
||||
|
||||
switch (n1 & 3)
|
||||
{
|
||||
case 0:
|
||||
if ((n1 & 64) != 0)
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_WHEEL_UP;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_LEFT;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if ((n1 & 64) != 0)
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->key = TB_KEY_MOUSE_MIDDLE;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
event->key = TB_KEY_MOUSE_RIGHT;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
event->key = TB_KEY_MOUSE_RELEASE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return mi + 1;
|
||||
}
|
||||
|
||||
if (!isM)
|
||||
{
|
||||
// on xterm mouse release is signaled by lowercase m
|
||||
event->key = TB_KEY_MOUSE_RELEASE;
|
||||
}
|
||||
|
||||
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
|
||||
|
||||
if ((n1 & 32) != 0)
|
||||
{
|
||||
event->mod |= TB_MOD_MOTION;
|
||||
}
|
||||
|
||||
event->x = (uint8_t)n2 - 1;
|
||||
event->y = (uint8_t)n3 - 1;
|
||||
return mi + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// convert escape sequence to event, and return consumed bytes on success (failure == 0)
|
||||
static int parse_escape_seq(struct tb_event* event, const char* buf, int len)
|
||||
{
|
||||
int mouse_parsed = parse_mouse_event(event, buf, len);
|
||||
|
||||
if (mouse_parsed != 0)
|
||||
{
|
||||
return mouse_parsed;
|
||||
}
|
||||
|
||||
// it's pretty simple here, find 'starts_with' match and return success, else return failure
|
||||
int i;
|
||||
|
||||
for (i = 0; keys[i]; i++)
|
||||
{
|
||||
if (starts_with(buf, keys[i]))
|
||||
{
|
||||
event->ch = 0;
|
||||
event->key = 0xFFFF - i;
|
||||
return strlen(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool extract_event(struct tb_event* event, struct ringbuffer* inbuf,
|
||||
int inputmode)
|
||||
{
|
||||
char buf[BUFFER_SIZE_MAX + 1];
|
||||
int nbytes = ringbuffer_data_size(inbuf);
|
||||
|
||||
if (nbytes > BUFFER_SIZE_MAX)
|
||||
{
|
||||
nbytes = BUFFER_SIZE_MAX;
|
||||
}
|
||||
|
||||
if (nbytes == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ringbuffer_read(inbuf, buf, nbytes);
|
||||
buf[nbytes] = '\0';
|
||||
|
||||
if (buf[0] == '\033')
|
||||
{
|
||||
int n = parse_escape_seq(event, buf, nbytes);
|
||||
|
||||
if (n != 0)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
if (n < 0)
|
||||
{
|
||||
success = false;
|
||||
n = -n;
|
||||
}
|
||||
|
||||
ringbuffer_pop(inbuf, 0, n);
|
||||
return success;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's not escape sequence, then it's ALT or ESC, check inputmode
|
||||
if (inputmode & TB_INPUT_ESC)
|
||||
{
|
||||
// if we're in escape mode, fill ESC event, pop buffer, return success
|
||||
event->ch = 0;
|
||||
event->key = TB_KEY_ESC;
|
||||
event->mod = 0;
|
||||
ringbuffer_pop(inbuf, 0, 1);
|
||||
return true;
|
||||
}
|
||||
else if (inputmode & TB_INPUT_ALT)
|
||||
{
|
||||
// if we're in alt mode, set ALT modifier to event and redo parsing
|
||||
event->mod = TB_MOD_ALT;
|
||||
ringbuffer_pop(inbuf, 0, 1);
|
||||
return extract_event(event, inbuf, inputmode);
|
||||
}
|
||||
|
||||
assert(!"never got here");
|
||||
}
|
||||
}
|
||||
|
||||
// if we're here, this is not an escape sequence and not an alt sequence
|
||||
// so, it's a FUNCTIONAL KEY or a UNICODE character
|
||||
|
||||
// first of all check if it's a functional key*/
|
||||
if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
|
||||
(unsigned char)buf[0] == TB_KEY_BACKSPACE2)
|
||||
{
|
||||
// fill event, pop buffer, return success
|
||||
event->ch = 0;
|
||||
event->key = (uint16_t)buf[0];
|
||||
ringbuffer_pop(inbuf, 0, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// feh... we got utf8 here
|
||||
|
||||
// check if there is all bytes
|
||||
if (nbytes >= utf8_char_length(buf[0]))
|
||||
{
|
||||
// everything ok, fill event, pop buffer, return success
|
||||
utf8_char_to_unicode(&event->ch, buf);
|
||||
event->key = 0;
|
||||
ringbuffer_pop(inbuf, 0, utf8_char_length(buf[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include "memstream.h"
|
||||
|
||||
void memstream_init(struct memstream* s, int fd, void* buffer, size_t len)
|
||||
{
|
||||
s->file = fd;
|
||||
s->data = buffer;
|
||||
s->pos = 0;
|
||||
s->capa = len;
|
||||
}
|
||||
|
||||
void memstream_flush(struct memstream* s)
|
||||
{
|
||||
write(s->file, s->data, s->pos);
|
||||
s->pos = 0;
|
||||
}
|
||||
|
||||
void memstream_write(struct memstream* s, void* source, size_t len)
|
||||
{
|
||||
unsigned char* data = source;
|
||||
|
||||
if (s->pos + len > s->capa)
|
||||
{
|
||||
memstream_flush(s);
|
||||
}
|
||||
|
||||
memcpy(s->data + s->pos, data, len);
|
||||
s->pos += len;
|
||||
}
|
||||
|
||||
void memstream_puts(struct memstream* s, const char* str)
|
||||
{
|
||||
memstream_write(s, (void*) str, strlen(str));
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef H_MEMSTREAM
|
||||
#define H_MEMSTREAM
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
struct memstream
|
||||
{
|
||||
size_t pos;
|
||||
size_t capa;
|
||||
int file;
|
||||
unsigned char* data;
|
||||
};
|
||||
|
||||
void memstream_init(struct memstream* s, int fd, void* buffer, size_t len);
|
||||
void memstream_flush(struct memstream* s);
|
||||
void memstream_write(struct memstream* s, void* source, size_t len);
|
||||
void memstream_puts(struct memstream* s, const char* str);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,195 @@
|
|||
#include "ringbuffer.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h> // for ptrdiff_t
|
||||
|
||||
int init_ringbuffer(struct ringbuffer* r, size_t size)
|
||||
{
|
||||
r->buf = (char*)malloc(size);
|
||||
|
||||
if (!r->buf)
|
||||
{
|
||||
return ERINGBUFFER_ALLOC_FAIL;
|
||||
}
|
||||
|
||||
r->size = size;
|
||||
clear_ringbuffer(r);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_ringbuffer(struct ringbuffer* r)
|
||||
{
|
||||
free(r->buf);
|
||||
}
|
||||
|
||||
void clear_ringbuffer(struct ringbuffer* r)
|
||||
{
|
||||
r->begin = 0;
|
||||
r->end = 0;
|
||||
}
|
||||
|
||||
size_t ringbuffer_free_space(struct ringbuffer* r)
|
||||
{
|
||||
if (r->begin == 0 && r->end == 0)
|
||||
{
|
||||
return r->size;
|
||||
}
|
||||
|
||||
if (r->begin < r->end)
|
||||
{
|
||||
return r->size - (r->end - r->begin) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return r->begin - r->end - 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t ringbuffer_data_size(struct ringbuffer* r)
|
||||
{
|
||||
if (r->begin == 0 && r->end == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (r->begin <= r->end)
|
||||
{
|
||||
return r->end - r->begin + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return r->size - (r->begin - r->end) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size)
|
||||
{
|
||||
if (ringbuffer_free_space(r) < size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (r->begin == 0 && r->end == 0)
|
||||
{
|
||||
memcpy(r->buf, data, size);
|
||||
r->begin = r->buf;
|
||||
r->end = r->buf + size - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
r->end++;
|
||||
|
||||
if (r->begin < r->end)
|
||||
{
|
||||
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
|
||||
{
|
||||
// we can fit without cut
|
||||
memcpy(r->end, data, size);
|
||||
r->end += size - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// make a cut
|
||||
size_t s = r->buf + r->size - r->end;
|
||||
memcpy(r->end, data, s);
|
||||
size -= s;
|
||||
memcpy(r->buf, (char*)data + s, size);
|
||||
r->end = r->buf + size - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(r->end, data, size);
|
||||
r->end += size - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size)
|
||||
{
|
||||
if (ringbuffer_data_size(r) < size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int need_clear = 0;
|
||||
|
||||
if (ringbuffer_data_size(r) == size)
|
||||
{
|
||||
need_clear = 1;
|
||||
}
|
||||
|
||||
if (r->begin < r->end)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
memcpy(data, r->begin, size);
|
||||
}
|
||||
|
||||
r->begin += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
|
||||
{
|
||||
if (data)
|
||||
{
|
||||
memcpy(data, r->begin, size);
|
||||
}
|
||||
|
||||
r->begin += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t s = r->buf + r->size - r->begin;
|
||||
|
||||
if (data)
|
||||
{
|
||||
memcpy(data, r->begin, s);
|
||||
}
|
||||
|
||||
size -= s;
|
||||
|
||||
if (data)
|
||||
{
|
||||
memcpy((char*)data + s, r->buf, size);
|
||||
}
|
||||
|
||||
r->begin = r->buf + size;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_clear)
|
||||
{
|
||||
clear_ringbuffer(r);
|
||||
}
|
||||
}
|
||||
|
||||
void ringbuffer_read(struct ringbuffer* r, void* data, size_t size)
|
||||
{
|
||||
if (ringbuffer_data_size(r) < size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (r->begin < r->end)
|
||||
{
|
||||
memcpy(data, r->begin, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size)
|
||||
{
|
||||
memcpy(data, r->begin, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t s = r->buf + r->size - r->begin;
|
||||
memcpy(data, r->begin, s);
|
||||
size -= s;
|
||||
memcpy((char*)data + s, r->buf, size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef H_RINGBUFFER
|
||||
#define H_RINGBUFFER
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define ERINGBUFFER_ALLOC_FAIL -1
|
||||
|
||||
struct ringbuffer
|
||||
{
|
||||
char* buf;
|
||||
size_t size;
|
||||
|
||||
char* begin;
|
||||
char* end;
|
||||
};
|
||||
|
||||
int init_ringbuffer(struct ringbuffer* r, size_t size);
|
||||
void free_ringbuffer(struct ringbuffer* r);
|
||||
void clear_ringbuffer(struct ringbuffer* r);
|
||||
size_t ringbuffer_free_space(struct ringbuffer* r);
|
||||
size_t ringbuffer_data_size(struct ringbuffer* r);
|
||||
void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size);
|
||||
void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size);
|
||||
void ringbuffer_read(struct ringbuffer* r, void* data, size_t size);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,412 @@
|
|||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "term.h"
|
||||
#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
|
||||
#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
|
||||
|
||||
#define EUNSUPPORTED_TERM -1
|
||||
|
||||
// rxvt-256color
|
||||
static const char* rxvt_256color_keys[] =
|
||||
{
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
|
||||
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
|
||||
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
|
||||
"\033[A", "\033[B", "\033[D", "\033[C", NULL
|
||||
};
|
||||
static const char* rxvt_256color_funcs[] =
|
||||
{
|
||||
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l",
|
||||
"\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
|
||||
"\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
|
||||
};
|
||||
|
||||
// Eterm
|
||||
static const char* eterm_keys[] =
|
||||
{
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
|
||||
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
|
||||
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
|
||||
"\033[A", "\033[B", "\033[D", "\033[C", NULL
|
||||
};
|
||||
static const char* eterm_funcs[] =
|
||||
{
|
||||
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l",
|
||||
"\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
|
||||
"", "", "", "",
|
||||
};
|
||||
|
||||
// screen
|
||||
static const char* screen_keys[] =
|
||||
{
|
||||
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~",
|
||||
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
|
||||
"\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~",
|
||||
"\033OA", "\033OB", "\033OD", "\033OC", NULL
|
||||
};
|
||||
static const char* screen_funcs[] =
|
||||
{
|
||||
"\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l",
|
||||
"\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
|
||||
"\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
|
||||
};
|
||||
|
||||
// rxvt-unicode
|
||||
static const char* rxvt_unicode_keys[] =
|
||||
{
|
||||
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~",
|
||||
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
|
||||
"\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~",
|
||||
"\033[A", "\033[B", "\033[D", "\033[C", NULL
|
||||
};
|
||||
static const char* rxvt_unicode_funcs[] =
|
||||
{
|
||||
"\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l",
|
||||
"\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m",
|
||||
"\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
|
||||
};
|
||||
|
||||
// linux
|
||||
static const char* linux_keys[] =
|
||||
{
|
||||
"\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~",
|
||||
"\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~",
|
||||
"\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~",
|
||||
"\033[A", "\033[B", "\033[D", "\033[C", NULL
|
||||
};
|
||||
static const char* linux_funcs[] =
|
||||
{
|
||||
"", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J",
|
||||
"\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
|
||||
};
|
||||
|
||||
// xterm
|
||||
static const char* xterm_keys[] =
|
||||
{
|
||||
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~",
|
||||
"\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~",
|
||||
"\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB",
|
||||
"\033OD", "\033OC", NULL
|
||||
};
|
||||
static const char* xterm_funcs[] =
|
||||
{
|
||||
"\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l",
|
||||
"\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m",
|
||||
"\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ,
|
||||
};
|
||||
|
||||
struct term
|
||||
{
|
||||
const char* name;
|
||||
const char** keys;
|
||||
const char** funcs;
|
||||
};
|
||||
|
||||
static struct term terms[] =
|
||||
{
|
||||
{"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
|
||||
{"Eterm", eterm_keys, eterm_funcs},
|
||||
{"screen", screen_keys, screen_funcs},
|
||||
{"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
|
||||
{"linux", linux_keys, linux_funcs},
|
||||
{"xterm", xterm_keys, xterm_funcs},
|
||||
{0, 0, 0},
|
||||
};
|
||||
|
||||
static int init_from_terminfo = 0;
|
||||
const char** keys;
|
||||
const char** funcs;
|
||||
|
||||
static int try_compatible(const char* term, const char* name,
|
||||
const char** tkeys, const char** tfuncs)
|
||||
{
|
||||
if (strstr(term, name))
|
||||
{
|
||||
keys = tkeys;
|
||||
funcs = tfuncs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return EUNSUPPORTED_TERM;
|
||||
}
|
||||
|
||||
static int init_term_builtin(void)
|
||||
{
|
||||
int i;
|
||||
const char* term = getenv("TERM");
|
||||
|
||||
if (term)
|
||||
{
|
||||
for (i = 0; terms[i].name; i++)
|
||||
{
|
||||
if (!strcmp(terms[i].name, term))
|
||||
{
|
||||
keys = terms[i].keys;
|
||||
funcs = terms[i].funcs;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// let's do some heuristic, maybe it's a compatible terminal
|
||||
if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// let's assume that 'cygwin' is xterm compatible
|
||||
if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return EUNSUPPORTED_TERM;
|
||||
}
|
||||
|
||||
// terminfo
|
||||
static char* read_file(const char* file)
|
||||
{
|
||||
FILE* f = fopen(file, "rb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
|
||||
if (fstat(fileno(f), &st) != 0)
|
||||
{
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* data = malloc(st.st_size);
|
||||
|
||||
if (!data)
|
||||
{
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fread(data, 1, st.st_size, f) != (size_t)st.st_size)
|
||||
{
|
||||
fclose(f);
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return data;
|
||||
}
|
||||
|
||||
static char* terminfo_try_path(const char* path, const char* term)
|
||||
{
|
||||
char tmp[4096];
|
||||
snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
char* data = read_file(tmp);
|
||||
|
||||
if (data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
// fallback to darwin specific dirs structure
|
||||
snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
return read_file(tmp);
|
||||
}
|
||||
|
||||
static char* load_terminfo(void)
|
||||
{
|
||||
char tmp[4096];
|
||||
const char* term = getenv("TERM");
|
||||
|
||||
if (!term)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if TERMINFO is set, no other directory should be searched
|
||||
const char* terminfo = getenv("TERMINFO");
|
||||
|
||||
if (terminfo)
|
||||
{
|
||||
return terminfo_try_path(terminfo, term);
|
||||
}
|
||||
|
||||
// next, consider ~/.terminfo
|
||||
const char* home = getenv("HOME");
|
||||
|
||||
if (home)
|
||||
{
|
||||
snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
char* data = terminfo_try_path(tmp, term);
|
||||
|
||||
if (data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// next, TERMINFO_DIRS
|
||||
const char* dirs = getenv("TERMINFO_DIRS");
|
||||
|
||||
if (dirs)
|
||||
{
|
||||
snprintf(tmp, sizeof(tmp), "%s", dirs);
|
||||
tmp[sizeof(tmp) - 1] = '\0';
|
||||
char* dir = strtok(tmp, ":");
|
||||
|
||||
while (dir)
|
||||
{
|
||||
const char* cdir = dir;
|
||||
|
||||
if (strcmp(cdir, "") == 0)
|
||||
{
|
||||
cdir = "/usr/share/terminfo";
|
||||
}
|
||||
|
||||
char* data = terminfo_try_path(cdir, term);
|
||||
|
||||
if (data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
dir = strtok(0, ":");
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to /usr/share/terminfo
|
||||
return terminfo_try_path("/usr/share/terminfo", term);
|
||||
}
|
||||
|
||||
#define TI_MAGIC 0432
|
||||
#define TI_ALT_MAGIC 542
|
||||
#define TI_HEADER_LENGTH 12
|
||||
#define TB_KEYS_NUM 22
|
||||
|
||||
static const char* terminfo_copy_string(char* data, int str, int table)
|
||||
{
|
||||
const int16_t off = *(int16_t*)(data + str);
|
||||
const char* src = data + table + off;
|
||||
int len = strlen(src);
|
||||
char* dst = malloc(len + 1);
|
||||
strcpy(dst, src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
const int16_t ti_funcs[] =
|
||||
{
|
||||
28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
|
||||
};
|
||||
|
||||
const int16_t ti_keys[] =
|
||||
{
|
||||
// apparently not a typo; 67 is F10 for whatever reason
|
||||
66, 68, 69, 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82,
|
||||
81, 87, 61, 79, 83,
|
||||
};
|
||||
|
||||
int init_term(void)
|
||||
{
|
||||
int i;
|
||||
char* data = load_terminfo();
|
||||
|
||||
if (!data)
|
||||
{
|
||||
init_from_terminfo = 0;
|
||||
return init_term_builtin();
|
||||
}
|
||||
|
||||
int16_t* header = (int16_t*)data;
|
||||
|
||||
const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2;
|
||||
|
||||
if ((header[1] + header[2]) % 2)
|
||||
{
|
||||
// old quirk to align everything on word boundaries
|
||||
header[2] += 1;
|
||||
}
|
||||
|
||||
const int str_offset = TI_HEADER_LENGTH +
|
||||
header[1] + header[2] + number_sec_len * header[3];
|
||||
const int table_offset = str_offset + 2 * header[4];
|
||||
|
||||
keys = malloc(sizeof(const char*) * (TB_KEYS_NUM + 1));
|
||||
|
||||
for (i = 0; i < TB_KEYS_NUM; i++)
|
||||
{
|
||||
keys[i] = terminfo_copy_string(data,
|
||||
str_offset + 2 * ti_keys[i], table_offset);
|
||||
}
|
||||
|
||||
keys[i] = NULL;
|
||||
|
||||
funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
|
||||
|
||||
// the last two entries are reserved for mouse. because the table offset is
|
||||
// not there, the two entries have to fill in manually
|
||||
for (i = 0; i < T_FUNCS_NUM - 2; i++)
|
||||
{
|
||||
funcs[i] = terminfo_copy_string(data,
|
||||
str_offset + 2 * ti_funcs[i], table_offset);
|
||||
}
|
||||
|
||||
funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ;
|
||||
funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ;
|
||||
init_from_terminfo = 1;
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void shutdown_term(void)
|
||||
{
|
||||
if (init_from_terminfo)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < TB_KEYS_NUM; i++)
|
||||
{
|
||||
free((void*)keys[i]);
|
||||
}
|
||||
|
||||
// the last two entries are reserved for mouse. because the table offset
|
||||
// is not there, the two entries have to fill in manually and do not
|
||||
// need to be freed.
|
||||
for (i = 0; i < T_FUNCS_NUM - 2; i++)
|
||||
{
|
||||
free((void*)funcs[i]);
|
||||
}
|
||||
|
||||
free(keys);
|
||||
free(funcs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef H_TERM
|
||||
#define H_TERM
|
||||
|
||||
#include "termbox.h"
|
||||
#include "ringbuffer.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define EUNSUPPORTED_TERM -1
|
||||
|
||||
enum
|
||||
{
|
||||
T_ENTER_CA,
|
||||
T_EXIT_CA,
|
||||
T_SHOW_CURSOR,
|
||||
T_HIDE_CURSOR,
|
||||
T_CLEAR_SCREEN,
|
||||
T_SGR0,
|
||||
T_UNDERLINE,
|
||||
T_BOLD,
|
||||
T_BLINK,
|
||||
T_REVERSE,
|
||||
T_ENTER_KEYPAD,
|
||||
T_EXIT_KEYPAD,
|
||||
T_ENTER_MOUSE,
|
||||
T_EXIT_MOUSE,
|
||||
T_FUNCS_NUM,
|
||||
};
|
||||
|
||||
extern const char** keys;
|
||||
extern const char** funcs;
|
||||
|
||||
// true on success, false on failure
|
||||
bool extract_event(struct tb_event* event, struct ringbuffer* inbuf,
|
||||
int inputmode);
|
||||
int init_term(void);
|
||||
void shutdown_term(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,885 @@
|
|||
#include "term.h"
|
||||
#include "termbox.h"
|
||||
#include "memstream.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
struct cellbuf
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
struct tb_cell* cells;
|
||||
};
|
||||
|
||||
#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
|
||||
#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
|
||||
#define LAST_COORD_INIT -1
|
||||
|
||||
static struct termios orig_tios;
|
||||
|
||||
static struct cellbuf back_buffer;
|
||||
static struct cellbuf front_buffer;
|
||||
static unsigned char write_buffer_data[32 * 1024];
|
||||
static struct memstream write_buffer;
|
||||
|
||||
static int termw = -1;
|
||||
static int termh = -1;
|
||||
|
||||
static int inputmode = TB_INPUT_ESC;
|
||||
static int outputmode = TB_OUTPUT_NORMAL;
|
||||
|
||||
static struct ringbuffer inbuf;
|
||||
|
||||
static int out;
|
||||
static FILE* in;
|
||||
|
||||
static int out_fileno;
|
||||
static int in_fileno;
|
||||
|
||||
static int winch_fds[2];
|
||||
|
||||
static int lastx = LAST_COORD_INIT;
|
||||
static int lasty = LAST_COORD_INIT;
|
||||
static int cursor_x = -1;
|
||||
static int cursor_y = -1;
|
||||
|
||||
static uint32_t background = TB_DEFAULT;
|
||||
static uint32_t foreground = TB_DEFAULT;
|
||||
|
||||
static void write_cursor(int x, int y);
|
||||
static void write_sgr(uint32_t fg, uint32_t bg);
|
||||
|
||||
static void cellbuf_init(struct cellbuf* buf, int width, int height);
|
||||
static void cellbuf_resize(struct cellbuf* buf, int width, int height);
|
||||
static void cellbuf_clear(struct cellbuf* buf);
|
||||
static void cellbuf_free(struct cellbuf* buf);
|
||||
|
||||
static void update_size(void);
|
||||
static void update_term_size(void);
|
||||
static void send_attr(uint32_t fg, uint32_t bg);
|
||||
static void send_char(int x, int y, uint32_t c);
|
||||
static void send_clear(void);
|
||||
static void sigwinch_handler(int xxx);
|
||||
static int wait_fill_event(struct tb_event* event, struct timeval* timeout);
|
||||
|
||||
// may happen in a different thread
|
||||
static volatile int buffer_size_change_request;
|
||||
|
||||
int tb_init_file(const char* name)
|
||||
{
|
||||
out = open(name, O_WRONLY);
|
||||
in = fopen(name, "r");
|
||||
|
||||
if (out == -1 || !in)
|
||||
{
|
||||
if (out != -1)
|
||||
{
|
||||
close(out);
|
||||
}
|
||||
|
||||
if (in)
|
||||
{
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
return TB_EFAILED_TO_OPEN_TTY;
|
||||
}
|
||||
|
||||
out_fileno = out;
|
||||
in_fileno = fileno(in);
|
||||
|
||||
if (init_term() < 0)
|
||||
{
|
||||
close(out);
|
||||
fclose(in);
|
||||
|
||||
return TB_EUNSUPPORTED_TERMINAL;
|
||||
}
|
||||
|
||||
if (pipe(winch_fds) < 0)
|
||||
{
|
||||
close(out);
|
||||
fclose(in);
|
||||
|
||||
return TB_EPIPE_TRAP_ERROR;
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = sigwinch_handler;
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGWINCH, &sa, 0);
|
||||
tcgetattr(out_fileno, &orig_tios);
|
||||
|
||||
struct termios tios;
|
||||
|
||||
memcpy(&tios, &orig_tios, sizeof(tios));
|
||||
tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
|
||||
| INLCR | IGNCR | ICRNL | IXON);
|
||||
tios.c_oflag &= ~OPOST;
|
||||
tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
tios.c_cflag &= ~(CSIZE | PARENB);
|
||||
tios.c_cflag |= CS8;
|
||||
tios.c_cc[VMIN] = 0;
|
||||
tios.c_cc[VTIME] = 0;
|
||||
tcsetattr(out_fileno, TCSAFLUSH, &tios);
|
||||
|
||||
memstream_init(&write_buffer, out_fileno, write_buffer_data,
|
||||
sizeof(write_buffer_data));
|
||||
memstream_puts(&write_buffer, funcs[T_ENTER_CA]);
|
||||
memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]);
|
||||
memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
|
||||
send_clear();
|
||||
|
||||
update_term_size();
|
||||
cellbuf_init(&back_buffer, termw, termh);
|
||||
cellbuf_init(&front_buffer, termw, termh);
|
||||
cellbuf_clear(&back_buffer);
|
||||
cellbuf_clear(&front_buffer);
|
||||
init_ringbuffer(&inbuf, 4096);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tb_init(void)
|
||||
{
|
||||
return tb_init_file("/dev/tty");
|
||||
}
|
||||
|
||||
void tb_shutdown(void)
|
||||
{
|
||||
if (termw == -1)
|
||||
{
|
||||
fputs("tb_shutdown() should not be called twice.", stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
|
||||
memstream_puts(&write_buffer, funcs[T_SGR0]);
|
||||
memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
|
||||
memstream_puts(&write_buffer, funcs[T_EXIT_CA]);
|
||||
memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]);
|
||||
memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]);
|
||||
memstream_flush(&write_buffer);
|
||||
tcsetattr(out_fileno, TCSAFLUSH, &orig_tios);
|
||||
|
||||
shutdown_term();
|
||||
close(out);
|
||||
fclose(in);
|
||||
close(winch_fds[0]);
|
||||
close(winch_fds[1]);
|
||||
|
||||
cellbuf_free(&back_buffer);
|
||||
cellbuf_free(&front_buffer);
|
||||
free_ringbuffer(&inbuf);
|
||||
termw = termh = -1;
|
||||
}
|
||||
|
||||
void tb_present(void)
|
||||
{
|
||||
int x, y, w, i;
|
||||
struct tb_cell* back, *front;
|
||||
|
||||
// invalidate cursor position
|
||||
lastx = LAST_COORD_INIT;
|
||||
lasty = LAST_COORD_INIT;
|
||||
|
||||
if (buffer_size_change_request)
|
||||
{
|
||||
update_size();
|
||||
buffer_size_change_request = 0;
|
||||
}
|
||||
|
||||
for (y = 0; y < front_buffer.height; ++y)
|
||||
{
|
||||
for (x = 0; x < front_buffer.width;)
|
||||
{
|
||||
back = &CELL(&back_buffer, x, y);
|
||||
front = &CELL(&front_buffer, x, y);
|
||||
w = wcwidth(back->ch);
|
||||
|
||||
if (w < 1)
|
||||
{
|
||||
w = 1;
|
||||
}
|
||||
|
||||
if (memcmp(back, front, sizeof(struct tb_cell)) == 0)
|
||||
{
|
||||
x += w;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(front, back, sizeof(struct tb_cell));
|
||||
send_attr(back->fg, back->bg);
|
||||
|
||||
if (w > 1 && x >= front_buffer.width - (w - 1))
|
||||
{
|
||||
// Not enough room for wide ch, so send spaces
|
||||
for (i = x; i < front_buffer.width; ++i)
|
||||
{
|
||||
send_char(i, y, ' ');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
send_char(x, y, back->ch);
|
||||
|
||||
for (i = 1; i < w; ++i)
|
||||
{
|
||||
front = &CELL(&front_buffer, x + i, y);
|
||||
front->ch = 0;
|
||||
front->fg = back->fg;
|
||||
front->bg = back->bg;
|
||||
}
|
||||
}
|
||||
|
||||
x += w;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
|
||||
{
|
||||
write_cursor(cursor_x, cursor_y);
|
||||
}
|
||||
|
||||
memstream_flush(&write_buffer);
|
||||
}
|
||||
|
||||
void tb_set_cursor(int cx, int cy)
|
||||
{
|
||||
if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]);
|
||||
}
|
||||
|
||||
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]);
|
||||
}
|
||||
|
||||
cursor_x = cx;
|
||||
cursor_y = cy;
|
||||
|
||||
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
|
||||
{
|
||||
write_cursor(cursor_x, cursor_y);
|
||||
}
|
||||
}
|
||||
|
||||
void tb_put_cell(int x, int y, const struct tb_cell* cell)
|
||||
{
|
||||
if ((unsigned)x >= (unsigned)back_buffer.width)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((unsigned)y >= (unsigned)back_buffer.height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CELL(&back_buffer, x, y) = *cell;
|
||||
}
|
||||
|
||||
void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg)
|
||||
{
|
||||
struct tb_cell c = {ch, fg, bg};
|
||||
tb_put_cell(x, y, &c);
|
||||
}
|
||||
|
||||
void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells)
|
||||
{
|
||||
if (x + w < 0 || x >= back_buffer.width)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (y + h < 0 || y >= back_buffer.height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int xo = 0, yo = 0, ww = w, hh = h;
|
||||
|
||||
if (x < 0)
|
||||
{
|
||||
xo = -x;
|
||||
ww -= xo;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (y < 0)
|
||||
{
|
||||
yo = -y;
|
||||
hh -= yo;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if (ww > back_buffer.width - x)
|
||||
{
|
||||
ww = back_buffer.width - x;
|
||||
}
|
||||
|
||||
if (hh > back_buffer.height - y)
|
||||
{
|
||||
hh = back_buffer.height - y;
|
||||
}
|
||||
|
||||
int sy;
|
||||
struct tb_cell* dst = &CELL(&back_buffer, x, y);
|
||||
const struct tb_cell* src = cells + yo * w + xo;
|
||||
size_t size = sizeof(struct tb_cell) * ww;
|
||||
|
||||
for (sy = 0; sy < hh; ++sy)
|
||||
{
|
||||
memcpy(dst, src, size);
|
||||
dst += back_buffer.width;
|
||||
src += w;
|
||||
}
|
||||
}
|
||||
|
||||
struct tb_cell* tb_cell_buffer(void)
|
||||
{
|
||||
return back_buffer.cells;
|
||||
}
|
||||
|
||||
int tb_poll_event(struct tb_event* event)
|
||||
{
|
||||
return wait_fill_event(event, 0);
|
||||
}
|
||||
|
||||
int tb_peek_event(struct tb_event* event, int timeout)
|
||||
{
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
|
||||
return wait_fill_event(event, &tv);
|
||||
}
|
||||
|
||||
int tb_width(void)
|
||||
{
|
||||
return termw;
|
||||
}
|
||||
|
||||
int tb_height(void)
|
||||
{
|
||||
return termh;
|
||||
}
|
||||
|
||||
void tb_clear(void)
|
||||
{
|
||||
if (buffer_size_change_request)
|
||||
{
|
||||
update_size();
|
||||
buffer_size_change_request = 0;
|
||||
}
|
||||
|
||||
cellbuf_clear(&back_buffer);
|
||||
}
|
||||
|
||||
int tb_select_input_mode(int mode)
|
||||
{
|
||||
if (mode)
|
||||
{
|
||||
if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
|
||||
{
|
||||
mode |= TB_INPUT_ESC;
|
||||
}
|
||||
|
||||
// technically termbox can handle that, but let's be nice
|
||||
// and show here what mode is actually used
|
||||
if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
|
||||
{
|
||||
mode &= ~TB_INPUT_ALT;
|
||||
}
|
||||
|
||||
inputmode = mode;
|
||||
|
||||
if (mode & TB_INPUT_MOUSE)
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_ENTER_MOUSE]);
|
||||
memstream_flush(&write_buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]);
|
||||
memstream_flush(&write_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
return inputmode;
|
||||
}
|
||||
|
||||
int tb_select_output_mode(int mode)
|
||||
{
|
||||
if (mode)
|
||||
{
|
||||
outputmode = mode;
|
||||
}
|
||||
|
||||
return outputmode;
|
||||
}
|
||||
|
||||
void tb_set_clear_attributes(uint32_t fg, uint32_t bg)
|
||||
{
|
||||
foreground = fg;
|
||||
background = bg;
|
||||
}
|
||||
|
||||
static unsigned convertnum(uint32_t num, char* buf)
|
||||
{
|
||||
unsigned i, l = 0;
|
||||
int ch;
|
||||
|
||||
do
|
||||
{
|
||||
buf[l++] = '0' + (num % 10);
|
||||
num /= 10;
|
||||
}
|
||||
while (num);
|
||||
|
||||
for (i = 0; i < l / 2; i++)
|
||||
{
|
||||
ch = buf[i];
|
||||
buf[i] = buf[l - 1 - i];
|
||||
buf[l - 1 - i] = ch;
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
#define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1)
|
||||
#define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf))
|
||||
|
||||
static void write_cursor(int x, int y)
|
||||
{
|
||||
char buf[32];
|
||||
WRITE_LITERAL("\033[");
|
||||
WRITE_INT(y + 1);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(x + 1);
|
||||
WRITE_LITERAL("H");
|
||||
}
|
||||
|
||||
static void write_sgr(uint32_t fg, uint32_t bg)
|
||||
{
|
||||
char buf[32];
|
||||
|
||||
if (outputmode != TB_OUTPUT_TRUECOLOR && fg == TB_DEFAULT && bg == TB_DEFAULT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (outputmode)
|
||||
{
|
||||
case TB_OUTPUT_TRUECOLOR:
|
||||
WRITE_LITERAL("\033[38;2;");
|
||||
WRITE_INT(fg >> 16 & 0xFF);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(fg >> 8 & 0xFF);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(fg & 0xFF);
|
||||
WRITE_LITERAL(";48;2;");
|
||||
WRITE_INT(bg >> 16 & 0xFF);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(bg >> 8 & 0xFF);
|
||||
WRITE_LITERAL(";");
|
||||
WRITE_INT(bg & 0xFF);
|
||||
WRITE_LITERAL("m");
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_256:
|
||||
case TB_OUTPUT_216:
|
||||
case TB_OUTPUT_GRAYSCALE:
|
||||
WRITE_LITERAL("\033[");
|
||||
|
||||
if (fg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL("38;5;");
|
||||
WRITE_INT(fg);
|
||||
|
||||
if (bg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL(";");
|
||||
}
|
||||
}
|
||||
|
||||
if (bg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL("48;5;");
|
||||
WRITE_INT(bg);
|
||||
}
|
||||
|
||||
WRITE_LITERAL("m");
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_NORMAL:
|
||||
default:
|
||||
WRITE_LITERAL("\033[");
|
||||
|
||||
if (fg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL("3");
|
||||
WRITE_INT(fg - 1);
|
||||
|
||||
if (bg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL(";");
|
||||
}
|
||||
}
|
||||
|
||||
if (bg != TB_DEFAULT)
|
||||
{
|
||||
WRITE_LITERAL("4");
|
||||
WRITE_INT(bg - 1);
|
||||
}
|
||||
|
||||
WRITE_LITERAL("m");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cellbuf_init(struct cellbuf* buf, int width, int height)
|
||||
{
|
||||
buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
|
||||
assert(buf->cells);
|
||||
buf->width = width;
|
||||
buf->height = height;
|
||||
}
|
||||
|
||||
static void cellbuf_resize(struct cellbuf* buf, int width, int height)
|
||||
{
|
||||
if (buf->width == width && buf->height == height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldw = buf->width;
|
||||
int oldh = buf->height;
|
||||
struct tb_cell* oldcells = buf->cells;
|
||||
|
||||
cellbuf_init(buf, width, height);
|
||||
cellbuf_clear(buf);
|
||||
|
||||
int minw = (width < oldw) ? width : oldw;
|
||||
int minh = (height < oldh) ? height : oldh;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < minh; ++i)
|
||||
{
|
||||
struct tb_cell* csrc = oldcells + (i * oldw);
|
||||
struct tb_cell* cdst = buf->cells + (i * width);
|
||||
memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
|
||||
}
|
||||
|
||||
free(oldcells);
|
||||
}
|
||||
|
||||
static void cellbuf_clear(struct cellbuf* buf)
|
||||
{
|
||||
int i;
|
||||
int ncells = buf->width * buf->height;
|
||||
|
||||
for (i = 0; i < ncells; ++i)
|
||||
{
|
||||
buf->cells[i].ch = ' ';
|
||||
buf->cells[i].fg = foreground;
|
||||
buf->cells[i].bg = background;
|
||||
}
|
||||
}
|
||||
|
||||
static void cellbuf_free(struct cellbuf* buf)
|
||||
{
|
||||
free(buf->cells);
|
||||
}
|
||||
|
||||
static void get_term_size(int* w, int* h)
|
||||
{
|
||||
struct winsize sz;
|
||||
memset(&sz, 0, sizeof(sz));
|
||||
|
||||
ioctl(out_fileno, TIOCGWINSZ, &sz);
|
||||
|
||||
if (w)
|
||||
{
|
||||
*w = sz.ws_col;
|
||||
}
|
||||
|
||||
if (h)
|
||||
{
|
||||
*h = sz.ws_row;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_term_size(void)
|
||||
{
|
||||
struct winsize sz;
|
||||
memset(&sz, 0, sizeof(sz));
|
||||
|
||||
ioctl(out_fileno, TIOCGWINSZ, &sz);
|
||||
|
||||
termw = sz.ws_col;
|
||||
termh = sz.ws_row;
|
||||
}
|
||||
|
||||
static void send_attr(uint32_t fg, uint32_t bg)
|
||||
{
|
||||
#define LAST_ATTR_INIT 0xFFFFFFFF
|
||||
static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
|
||||
|
||||
if (fg != lastfg || bg != lastbg)
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_SGR0]);
|
||||
uint32_t fgcol;
|
||||
uint32_t bgcol;
|
||||
|
||||
switch (outputmode)
|
||||
{
|
||||
case TB_OUTPUT_TRUECOLOR:
|
||||
fgcol = fg;
|
||||
bgcol = bg;
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_256:
|
||||
fgcol = fg & 0xFF;
|
||||
bgcol = bg & 0xFF;
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_216:
|
||||
fgcol = fg & 0xFF;
|
||||
|
||||
if (fgcol > 215)
|
||||
{
|
||||
fgcol = 7;
|
||||
}
|
||||
|
||||
bgcol = bg & 0xFF;
|
||||
|
||||
if (bgcol > 215)
|
||||
{
|
||||
bgcol = 0;
|
||||
}
|
||||
|
||||
fgcol += 0x10;
|
||||
bgcol += 0x10;
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_GRAYSCALE:
|
||||
fgcol = fg & 0xFF;
|
||||
|
||||
if (fgcol > 23)
|
||||
{
|
||||
fgcol = 23;
|
||||
}
|
||||
|
||||
bgcol = bg & 0xFF;
|
||||
|
||||
if (bgcol > 23)
|
||||
{
|
||||
bgcol = 0;
|
||||
}
|
||||
|
||||
fgcol += 0xe8;
|
||||
bgcol += 0xe8;
|
||||
break;
|
||||
|
||||
case TB_OUTPUT_NORMAL:
|
||||
default:
|
||||
fgcol = fg & 0x0F;
|
||||
bgcol = bg & 0x0F;
|
||||
}
|
||||
|
||||
if (fg & TB_BOLD)
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_BOLD]);
|
||||
}
|
||||
|
||||
if (bg & TB_BOLD)
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_BLINK]);
|
||||
}
|
||||
|
||||
if (fg & TB_UNDERLINE)
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_UNDERLINE]);
|
||||
}
|
||||
|
||||
if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
|
||||
{
|
||||
memstream_puts(&write_buffer, funcs[T_REVERSE]);
|
||||
}
|
||||
|
||||
write_sgr(fgcol, bgcol);
|
||||
|
||||
lastfg = fg;
|
||||
lastbg = bg;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_char(int x, int y, uint32_t c)
|
||||
{
|
||||
char buf[7];
|
||||
int bw = utf8_unicode_to_char(buf, c);
|
||||
buf[bw] = '\0';
|
||||
|
||||
if (x - 1 != lastx || y != lasty)
|
||||
{
|
||||
write_cursor(x, y);
|
||||
}
|
||||
|
||||
lastx = x;
|
||||
lasty = y;
|
||||
|
||||
if (!c)
|
||||
{
|
||||
buf[0] = ' '; // replace 0 with whitespace
|
||||
}
|
||||
|
||||
memstream_puts(&write_buffer, buf);
|
||||
}
|
||||
|
||||
static void send_clear(void)
|
||||
{
|
||||
send_attr(foreground, background);
|
||||
memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]);
|
||||
|
||||
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
|
||||
{
|
||||
write_cursor(cursor_x, cursor_y);
|
||||
}
|
||||
|
||||
memstream_flush(&write_buffer);
|
||||
|
||||
// we need to invalidate cursor position too and these two vars are
|
||||
// used only for simple cursor positioning optimization, cursor
|
||||
// actually may be in the correct place, but we simply discard
|
||||
// optimization once and it gives us simple solution for the case when
|
||||
// cursor moved
|
||||
lastx = LAST_COORD_INIT;
|
||||
lasty = LAST_COORD_INIT;
|
||||
}
|
||||
|
||||
static void sigwinch_handler(int xxx)
|
||||
{
|
||||
(void) xxx;
|
||||
const int zzz = 1;
|
||||
write(winch_fds[1], &zzz, sizeof(int));
|
||||
}
|
||||
|
||||
static void update_size(void)
|
||||
{
|
||||
update_term_size();
|
||||
cellbuf_resize(&back_buffer, termw, termh);
|
||||
cellbuf_resize(&front_buffer, termw, termh);
|
||||
cellbuf_clear(&front_buffer);
|
||||
send_clear();
|
||||
}
|
||||
|
||||
static int wait_fill_event(struct tb_event* event, struct timeval* timeout)
|
||||
{
|
||||
#define ENOUGH_DATA_FOR_INPUT_PARSING 128
|
||||
int result;
|
||||
char buf[ENOUGH_DATA_FOR_INPUT_PARSING];
|
||||
fd_set events;
|
||||
memset(event, 0, sizeof(struct tb_event));
|
||||
|
||||
// try to extract event from input buffer, return on success
|
||||
event->type = TB_EVENT_KEY;
|
||||
|
||||
if (extract_event(event, &inbuf, inputmode))
|
||||
{
|
||||
return event->type;
|
||||
}
|
||||
|
||||
// it looks like input buffer is incomplete, let's try the short path
|
||||
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
|
||||
|
||||
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
|
||||
{
|
||||
clearerr(in);
|
||||
}
|
||||
|
||||
if (r > 0)
|
||||
{
|
||||
if (ringbuffer_free_space(&inbuf) < r)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
ringbuffer_push(&inbuf, buf, r);
|
||||
|
||||
if (extract_event(event, &inbuf, inputmode))
|
||||
{
|
||||
return event->type;
|
||||
}
|
||||
}
|
||||
|
||||
// no stuff in FILE's internal buffer, block in select
|
||||
while (1)
|
||||
{
|
||||
FD_ZERO(&events);
|
||||
FD_SET(in_fileno, &events);
|
||||
FD_SET(winch_fds[0], &events);
|
||||
int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno;
|
||||
result = select(maxfd + 1, &events, 0, 0, timeout);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (FD_ISSET(in_fileno, &events))
|
||||
{
|
||||
event->type = TB_EVENT_KEY;
|
||||
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
|
||||
|
||||
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
|
||||
{
|
||||
clearerr(in);
|
||||
}
|
||||
|
||||
if (r == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if there is no free space in input buffer, return error
|
||||
if (ringbuffer_free_space(&inbuf) < r)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// fill buffer
|
||||
ringbuffer_push(&inbuf, buf, r);
|
||||
|
||||
if (extract_event(event, &inbuf, inputmode))
|
||||
{
|
||||
return event->type;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(winch_fds[0], &events))
|
||||
{
|
||||
event->type = TB_EVENT_RESIZE;
|
||||
int zzz = 0;
|
||||
read(winch_fds[0], &zzz, sizeof(int));
|
||||
buffer_size_change_request = 1;
|
||||
get_term_size(&event->w, &event->h);
|
||||
return TB_EVENT_RESIZE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
#ifndef H_TERMBOX
|
||||
#define H_TERMBOX
|
||||
#include <stdint.h>
|
||||
|
||||
// shared objects
|
||||
#if __GNUC__ >= 4
|
||||
#define SO_IMPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define SO_IMPORT
|
||||
#endif
|
||||
|
||||
// c++
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Key constants. See also struct tb_event's key field.
|
||||
// These are a safe subset of terminfo keys, which exist on all popular
|
||||
// terminals. Termbox uses only them to stay truly portable.
|
||||
#define TB_KEY_F1 (0xFFFF-0)
|
||||
#define TB_KEY_F2 (0xFFFF-1)
|
||||
#define TB_KEY_F3 (0xFFFF-2)
|
||||
#define TB_KEY_F4 (0xFFFF-3)
|
||||
#define TB_KEY_F5 (0xFFFF-4)
|
||||
#define TB_KEY_F6 (0xFFFF-5)
|
||||
#define TB_KEY_F7 (0xFFFF-6)
|
||||
#define TB_KEY_F8 (0xFFFF-7)
|
||||
#define TB_KEY_F9 (0xFFFF-8)
|
||||
#define TB_KEY_F10 (0xFFFF-9)
|
||||
#define TB_KEY_F11 (0xFFFF-10)
|
||||
#define TB_KEY_F12 (0xFFFF-11)
|
||||
#define TB_KEY_INSERT (0xFFFF-12)
|
||||
#define TB_KEY_DELETE (0xFFFF-13)
|
||||
#define TB_KEY_HOME (0xFFFF-14)
|
||||
#define TB_KEY_END (0xFFFF-15)
|
||||
#define TB_KEY_PGUP (0xFFFF-16)
|
||||
#define TB_KEY_PGDN (0xFFFF-17)
|
||||
#define TB_KEY_ARROW_UP (0xFFFF-18)
|
||||
#define TB_KEY_ARROW_DOWN (0xFFFF-19)
|
||||
#define TB_KEY_ARROW_LEFT (0xFFFF-20)
|
||||
#define TB_KEY_ARROW_RIGHT (0xFFFF-21)
|
||||
#define TB_KEY_MOUSE_LEFT (0xFFFF-22)
|
||||
#define TB_KEY_MOUSE_RIGHT (0xFFFF-23)
|
||||
#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24)
|
||||
#define TB_KEY_MOUSE_RELEASE (0xFFFF-25)
|
||||
#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26)
|
||||
#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
|
||||
|
||||
// These are all ASCII code points below SPACE character and a BACKSPACE key.
|
||||
#define TB_KEY_CTRL_TILDE 0x00
|
||||
#define TB_KEY_CTRL_2 0x00 // clash with 'CTRL_TILDE'
|
||||
#define TB_KEY_CTRL_A 0x01
|
||||
#define TB_KEY_CTRL_B 0x02
|
||||
#define TB_KEY_CTRL_C 0x03
|
||||
#define TB_KEY_CTRL_D 0x04
|
||||
#define TB_KEY_CTRL_E 0x05
|
||||
#define TB_KEY_CTRL_F 0x06
|
||||
#define TB_KEY_CTRL_G 0x07
|
||||
#define TB_KEY_BACKSPACE 0x08
|
||||
#define TB_KEY_CTRL_H 0x08 // clash with 'CTRL_BACKSPACE'
|
||||
#define TB_KEY_TAB 0x09
|
||||
#define TB_KEY_CTRL_I 0x09 // clash with 'TAB'
|
||||
#define TB_KEY_CTRL_J 0x0A
|
||||
#define TB_KEY_CTRL_K 0x0B
|
||||
#define TB_KEY_CTRL_L 0x0C
|
||||
#define TB_KEY_ENTER 0x0D
|
||||
#define TB_KEY_CTRL_M 0x0D // clash with 'ENTER'
|
||||
#define TB_KEY_CTRL_N 0x0E
|
||||
#define TB_KEY_CTRL_O 0x0F
|
||||
#define TB_KEY_CTRL_P 0x10
|
||||
#define TB_KEY_CTRL_Q 0x11
|
||||
#define TB_KEY_CTRL_R 0x12
|
||||
#define TB_KEY_CTRL_S 0x13
|
||||
#define TB_KEY_CTRL_T 0x14
|
||||
#define TB_KEY_CTRL_U 0x15
|
||||
#define TB_KEY_CTRL_V 0x16
|
||||
#define TB_KEY_CTRL_W 0x17
|
||||
#define TB_KEY_CTRL_X 0x18
|
||||
#define TB_KEY_CTRL_Y 0x19
|
||||
#define TB_KEY_CTRL_Z 0x1A
|
||||
#define TB_KEY_ESC 0x1B
|
||||
#define TB_KEY_CTRL_LSQ_BRACKET 0x1B // clash with 'ESC'
|
||||
#define TB_KEY_CTRL_3 0x1B // clash with 'ESC'
|
||||
#define TB_KEY_CTRL_4 0x1C
|
||||
#define TB_KEY_CTRL_BACKSLASH 0x1C // clash with 'CTRL_4'
|
||||
#define TB_KEY_CTRL_5 0x1D
|
||||
#define TB_KEY_CTRL_RSQ_BRACKET 0x1D // clash with 'CTRL_5'
|
||||
#define TB_KEY_CTRL_6 0x1E
|
||||
#define TB_KEY_CTRL_7 0x1F
|
||||
#define TB_KEY_CTRL_SLASH 0x1F // clash with 'CTRL_7'
|
||||
#define TB_KEY_CTRL_UNDERSCORE 0x1F // clash with 'CTRL_7'
|
||||
#define TB_KEY_SPACE 0x20
|
||||
#define TB_KEY_BACKSPACE2 0x7F
|
||||
#define TB_KEY_CTRL_8 0x7F // clash with 'BACKSPACE2'
|
||||
|
||||
// These are non-existing ones.
|
||||
// #define TB_KEY_CTRL_1 clash with '1'
|
||||
// #define TB_KEY_CTRL_9 clash with '9'
|
||||
// #define TB_KEY_CTRL_0 clash with '0'
|
||||
|
||||
// Alt modifier constant, see tb_event.mod field and tb_select_input_mode function.
|
||||
// Mouse-motion modifier
|
||||
#define TB_MOD_ALT 0x01
|
||||
#define TB_MOD_MOTION 0x02
|
||||
|
||||
// Colors (see struct tb_cell's fg and bg fields).
|
||||
#define TB_DEFAULT 0x00
|
||||
#define TB_BLACK 0x01
|
||||
#define TB_RED 0x02
|
||||
#define TB_GREEN 0x03
|
||||
#define TB_YELLOW 0x04
|
||||
#define TB_BLUE 0x05
|
||||
#define TB_MAGENTA 0x06
|
||||
#define TB_CYAN 0x07
|
||||
#define TB_WHITE 0x08
|
||||
|
||||
// Attributes, it is possible to use multiple attributes by combining them
|
||||
// using bitwise OR ('|'). Although, colors cannot be combined. But you can
|
||||
// combine attributes and a single color. See also struct tb_cell's fg and bg
|
||||
// fields.
|
||||
#define TB_BOLD 0x01000000
|
||||
#define TB_UNDERLINE 0x02000000
|
||||
#define TB_REVERSE 0x04000000
|
||||
|
||||
// A cell, single conceptual entity on the terminal screen. The terminal screen
|
||||
// is basically a 2d array of cells. It has the following fields:
|
||||
// - 'ch' is a unicode character
|
||||
// - 'fg' foreground color and attributes
|
||||
// - 'bg' background color and attributes
|
||||
struct tb_cell
|
||||
{
|
||||
uint32_t ch;
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
};
|
||||
|
||||
#define TB_EVENT_KEY 1
|
||||
#define TB_EVENT_RESIZE 2
|
||||
#define TB_EVENT_MOUSE 3
|
||||
|
||||
// An event, single interaction from the user. The 'mod' and 'ch' fields are
|
||||
// valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type'
|
||||
// is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is
|
||||
// TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY
|
||||
// or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only
|
||||
// one of them can be non-zero at a time.
|
||||
struct tb_event
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t mod; // modifiers to either 'key' or 'ch' below
|
||||
uint16_t key; // one of the TB_KEY_* constants
|
||||
uint32_t ch; // unicode character
|
||||
int32_t w;
|
||||
int32_t h;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
|
||||
// Error codes returned by tb_init(). All of them are self-explanatory, except
|
||||
// the pipe trap error. Termbox uses unix pipes in order to deliver a message
|
||||
// from a signal handler (SIGWINCH) to the main event reading loop. Honestly in
|
||||
// most cases you should just check the returned code as < 0.
|
||||
#define TB_EUNSUPPORTED_TERMINAL -1
|
||||
#define TB_EFAILED_TO_OPEN_TTY -2
|
||||
#define TB_EPIPE_TRAP_ERROR -3
|
||||
|
||||
// Initializes the termbox library. This function should be called before any
|
||||
// other functions. Function tb_init is same as tb_init_file("/dev/tty"). After successful initialization, the library must be
|
||||
// finalized using the tb_shutdown() function.
|
||||
SO_IMPORT int tb_init(void);
|
||||
SO_IMPORT int tb_init_file(const char* name);
|
||||
SO_IMPORT void tb_shutdown(void);
|
||||
|
||||
// Returns the size of the internal back buffer (which is the same as
|
||||
// terminal's window size in characters). The internal buffer can be resized
|
||||
// after tb_clear() or tb_present() function calls. Both dimensions have an
|
||||
// unspecified negative value when called before tb_init() or after
|
||||
// tb_shutdown().
|
||||
SO_IMPORT int tb_width(void);
|
||||
SO_IMPORT int tb_height(void);
|
||||
|
||||
// Clears the internal back buffer using TB_DEFAULT color or the
|
||||
// color/attributes set by tb_set_clear_attributes() function.
|
||||
SO_IMPORT void tb_clear(void);
|
||||
SO_IMPORT void tb_set_clear_attributes(uint32_t fg, uint32_t bg);
|
||||
|
||||
// Synchronizes the internal back buffer with the terminal.
|
||||
SO_IMPORT void tb_present(void);
|
||||
|
||||
#define TB_HIDE_CURSOR -1
|
||||
|
||||
// Sets the position of the cursor. Upper-left character is (0, 0). If you pass
|
||||
// TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor
|
||||
// is hidden by default.
|
||||
SO_IMPORT void tb_set_cursor(int cx, int cy);
|
||||
|
||||
// Changes cell's parameters in the internal back buffer at the specified
|
||||
// position.
|
||||
SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell* cell);
|
||||
SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg,
|
||||
uint32_t bg);
|
||||
|
||||
// Copies the buffer from 'cells' at the specified position, assuming the
|
||||
// buffer is a two-dimensional array of size ('w' x 'h'), represented as a
|
||||
// one-dimensional buffer containing lines of cells starting from the top.
|
||||
// (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own)
|
||||
SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells);
|
||||
|
||||
// Returns a pointer to internal cell back buffer. You can get its dimensions
|
||||
// using tb_width() and tb_height() functions. The pointer stays valid as long
|
||||
// as no tb_clear() and tb_present() calls are made. The buffer is
|
||||
// one-dimensional buffer containing lines of cells starting from the top.
|
||||
SO_IMPORT struct tb_cell* tb_cell_buffer(void);
|
||||
|
||||
#define TB_INPUT_CURRENT 0 // 000
|
||||
#define TB_INPUT_ESC 1 // 001
|
||||
#define TB_INPUT_ALT 2 // 010
|
||||
#define TB_INPUT_MOUSE 4 // 100
|
||||
|
||||
// Sets the termbox input mode. Termbox has two input modes:
|
||||
// 1. Esc input mode.
|
||||
// When ESC sequence is in the buffer and it doesn't match any known
|
||||
// ESC sequence => ESC means TB_KEY_ESC.
|
||||
// 2. Alt input mode.
|
||||
// When ESC sequence is in the buffer and it doesn't match any known
|
||||
// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
|
||||
//
|
||||
// You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the
|
||||
// modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes
|
||||
// were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some
|
||||
// reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it
|
||||
// will behave as if only TB_INPUT_ESC was selected.
|
||||
//
|
||||
// If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.
|
||||
//
|
||||
// Default termbox input mode is TB_INPUT_ESC.
|
||||
SO_IMPORT int tb_select_input_mode(int mode);
|
||||
|
||||
#define TB_OUTPUT_CURRENT 0
|
||||
#define TB_OUTPUT_NORMAL 1
|
||||
#define TB_OUTPUT_256 2
|
||||
#define TB_OUTPUT_216 3
|
||||
#define TB_OUTPUT_GRAYSCALE 4
|
||||
#define TB_OUTPUT_TRUECOLOR 5
|
||||
|
||||
// Sets the termbox output mode. Termbox has three output options:
|
||||
// 1. TB_OUTPUT_NORMAL => [1..8]
|
||||
// This mode provides 8 different colors:
|
||||
// black, red, green, yellow, blue, magenta, cyan, white
|
||||
// Shortcut: TB_BLACK, TB_RED, ...
|
||||
// Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE
|
||||
//
|
||||
// Example usage:
|
||||
// tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED);
|
||||
//
|
||||
// 2. TB_OUTPUT_256 => [0..256]
|
||||
// In this mode you can leverage the 256 terminal mode:
|
||||
// 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL
|
||||
// 0x08 - 0x0f: TB_* | TB_BOLD
|
||||
// 0x10 - 0xe7: 216 different colors
|
||||
// 0xe8 - 0xff: 24 different shades of grey
|
||||
//
|
||||
// Example usage:
|
||||
// tb_change_cell(x, y, '@', 184, 240);
|
||||
// tb_change_cell(x, y, '@', 0xb8, 0xf0);
|
||||
//
|
||||
// 3. TB_OUTPUT_216 => [0..216]
|
||||
// This mode supports the 3rd range of the 256 mode only.
|
||||
// But you don't need to provide an offset.
|
||||
//
|
||||
// 4. TB_OUTPUT_GRAYSCALE => [0..23]
|
||||
// This mode supports the 4th range of the 256 mode only.
|
||||
// But you dont need to provide an offset.
|
||||
//
|
||||
// 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xFFFFFF]
|
||||
// This mode supports 24-bit true color. Format is 0xRRGGBB.
|
||||
//
|
||||
// Execute build/src/demo/output to see its impact on your terminal.
|
||||
//
|
||||
// If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode.
|
||||
//
|
||||
// Default termbox output mode is TB_OUTPUT_NORMAL.
|
||||
SO_IMPORT int tb_select_output_mode(int mode);
|
||||
|
||||
// Wait for an event up to 'timeout' milliseconds and fill the 'event'
|
||||
// structure with it, when the event is available. Returns the type of the
|
||||
// event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
|
||||
// there were no event during 'timeout' period.
|
||||
SO_IMPORT int tb_peek_event(struct tb_event* event, int timeout);
|
||||
|
||||
// Wait for an event forever and fill the 'event' structure with it, when the
|
||||
// event is available. Returns the type of the event (one of TB_EVENT_
|
||||
// constants) or -1 if there was an error.
|
||||
SO_IMPORT int tb_poll_event(struct tb_event* event);
|
||||
|
||||
// Utility utf8 functions.
|
||||
#define TB_EOF -1
|
||||
SO_IMPORT int utf8_char_length(char c);
|
||||
SO_IMPORT int utf8_char_to_unicode(uint32_t* out, const char* c);
|
||||
SO_IMPORT int utf8_unicode_to_char(char* out, uint32_t c);
|
||||
|
||||
// c++
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,106 @@
|
|||
#include "termbox.h"
|
||||
|
||||
static const unsigned char utf8_length[256] =
|
||||
{
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1
|
||||
};
|
||||
|
||||
static const unsigned char utf8_mask[6] =
|
||||
{
|
||||
0x7F,
|
||||
0x1F,
|
||||
0x0F,
|
||||
0x07,
|
||||
0x03,
|
||||
0x01
|
||||
};
|
||||
|
||||
int utf8_char_length(char c)
|
||||
{
|
||||
return utf8_length[(unsigned char)c];
|
||||
}
|
||||
|
||||
int utf8_char_to_unicode(uint32_t* out, const char* c)
|
||||
{
|
||||
if (*c == 0)
|
||||
{
|
||||
return TB_EOF;
|
||||
}
|
||||
|
||||
int i;
|
||||
unsigned char len = utf8_char_length(*c);
|
||||
unsigned char mask = utf8_mask[len - 1];
|
||||
uint32_t result = c[0] & mask;
|
||||
|
||||
for (i = 1; i < len; ++i)
|
||||
{
|
||||
result <<= 6;
|
||||
result |= c[i] & 0x3f;
|
||||
}
|
||||
|
||||
*out = result;
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
int utf8_unicode_to_char(char* out, uint32_t c)
|
||||
{
|
||||
int len = 0;
|
||||
int first;
|
||||
int i;
|
||||
|
||||
if (c < 0x80)
|
||||
{
|
||||
first = 0;
|
||||
len = 1;
|
||||
}
|
||||
else if (c < 0x800)
|
||||
{
|
||||
first = 0xc0;
|
||||
len = 2;
|
||||
}
|
||||
else if (c < 0x10000)
|
||||
{
|
||||
first = 0xe0;
|
||||
len = 3;
|
||||
}
|
||||
else if (c < 0x200000)
|
||||
{
|
||||
first = 0xf0;
|
||||
len = 4;
|
||||
}
|
||||
else if (c < 0x4000000)
|
||||
{
|
||||
first = 0xf8;
|
||||
len = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = 0xfc;
|
||||
len = 6;
|
||||
}
|
||||
|
||||
for (i = len - 1; i > 0; --i)
|
||||
{
|
||||
out[i] = (c & 0x3f) | 0x80;
|
||||
c >>= 6;
|
||||
}
|
||||
|
||||
out[0] = c | first;
|
||||
|
||||
return len;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
--style=break
|
||||
--indent=force-tab=4
|
||||
--indent-classes
|
||||
--indent-switches
|
||||
--indent-namespaces
|
||||
--indent-after-parens
|
||||
--indent-continuation=1
|
||||
--indent-preproc-block
|
||||
--indent-preproc-define
|
||||
--indent-preproc-cond
|
||||
--indent-col1-comments
|
||||
--min-conditional-indent=0
|
||||
--max-continuation-indent=40
|
||||
--break-blocks
|
||||
--pad-oper
|
||||
--pad-comma
|
||||
--pad-header
|
||||
--unpad-paren
|
||||
--align-pointer=type
|
||||
--align-reference=type
|
||||
--break-one-line-headers
|
||||
--add-braces
|
||||
--attach-return-type
|
||||
--attach-return-type-decl
|
||||
--remove-comment-prefix
|
||||
--max-code-length=80
|
||||
--mode=c
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys, os, subprocess
|
||||
|
||||
def escaped(s):
|
||||
return s.replace("\033", "\\033")
|
||||
|
||||
def tput(term, name):
|
||||
try:
|
||||
return subprocess.check_output(['tput', '-T%s' % term, name]).decode()
|
||||
except subprocess.CalledProcessError as e:
|
||||
return e.output.decode()
|
||||
|
||||
|
||||
def w(s):
|
||||
if s == None:
|
||||
return
|
||||
sys.stdout.write(s)
|
||||
|
||||
terminals = {
|
||||
'xterm' : 'xterm',
|
||||
'rxvt-256color' : 'rxvt_256color',
|
||||
'rxvt-unicode' : 'rxvt_unicode',
|
||||
'linux' : 'linux',
|
||||
'Eterm' : 'eterm',
|
||||
'screen' : 'screen'
|
||||
}
|
||||
|
||||
keys = [
|
||||
"F1", "kf1",
|
||||
"F2", "kf2",
|
||||
"F3", "kf3",
|
||||
"F4", "kf4",
|
||||
"F5", "kf5",
|
||||
"F6", "kf6",
|
||||
"F7", "kf7",
|
||||
"F8", "kf8",
|
||||
"F9", "kf9",
|
||||
"F10", "kf10",
|
||||
"F11", "kf11",
|
||||
"F12", "kf12",
|
||||
"INSERT", "kich1",
|
||||
"DELETE", "kdch1",
|
||||
"HOME", "khome",
|
||||
"END", "kend",
|
||||
"PGUP", "kpp",
|
||||
"PGDN", "knp",
|
||||
"KEY_UP", "kcuu1",
|
||||
"KEY_DOWN", "kcud1",
|
||||
"KEY_LEFT", "kcub1",
|
||||
"KEY_RIGHT", "kcuf1"
|
||||
]
|
||||
|
||||
funcs = [
|
||||
"T_ENTER_CA", "smcup",
|
||||
"T_EXIT_CA", "rmcup",
|
||||
"T_SHOW_CURSOR", "cnorm",
|
||||
"T_HIDE_CURSOR", "civis",
|
||||
"T_CLEAR_SCREEN", "clear",
|
||||
"T_SGR0", "sgr0",
|
||||
"T_UNDERLINE", "smul",
|
||||
"T_BOLD", "bold",
|
||||
"T_BLINK", "blink",
|
||||
"T_REVERSE", "rev",
|
||||
"T_ENTER_KEYPAD", "smkx",
|
||||
"T_EXIT_KEYPAD", "rmkx"
|
||||
]
|
||||
|
||||
def iter_pairs(iterable):
|
||||
iterable = iter(iterable)
|
||||
while True:
|
||||
yield (next(iterable), next(iterable))
|
||||
|
||||
def do_term(term, nick):
|
||||
w("// %s\n" % term)
|
||||
w("static const char *%s_keys[] = {\n\t" % nick)
|
||||
for k, v in iter_pairs(keys):
|
||||
w('"')
|
||||
w(escaped(tput(term, v)))
|
||||
w('",')
|
||||
w(" 0\n};\n")
|
||||
w("static const char *%s_funcs[] = {\n\t" % nick)
|
||||
for k,v in iter_pairs(funcs):
|
||||
w('"')
|
||||
if v == "sgr":
|
||||
w("\\033[3%d;4%dm")
|
||||
elif v == "cup":
|
||||
w("\\033[%d;%dH")
|
||||
else:
|
||||
w(escaped(tput(term, v)))
|
||||
w('", ')
|
||||
w("\n};\n\n")
|
||||
|
||||
def do_terms(d):
|
||||
w("static struct term {\n")
|
||||
w("\tconst char *name;\n")
|
||||
w("\tconst char **keys;\n")
|
||||
w("\tconst char **funcs;\n")
|
||||
w("} terms[] = {\n")
|
||||
for k, v in d.items():
|
||||
w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v))
|
||||
w("\t{0, 0, 0},\n")
|
||||
w("};\n")
|
||||
|
||||
for k,v in terminals.items():
|
||||
do_term(k, v)
|
||||
|
||||
do_terms(terminals)
|
Loading…
Reference in New Issue