From 3889e10610da7915544d8ad865b2f9385c6dc383 Mon Sep 17 00:00:00 2001 From: ache Date: Sat, 27 May 2017 18:38:45 +0200 Subject: New files --- basic_curses.c | 132 ++++++++++ basic_curses.h | 15 ++ h.c | 760 ++------------------------------------------------------- item.c | 101 ++++++++ item.h | 13 + main.h | 79 ++++++ readline.c | 198 +++++++++++++++ readline.h | 27 ++ regex.c | 151 ++++++++++++ regex.h | 17 ++ wind.c | 147 +++++++++++ wind.h | 24 ++ 12 files changed, 928 insertions(+), 736 deletions(-) create mode 100644 basic_curses.c create mode 100644 basic_curses.h create mode 100644 item.c create mode 100644 item.h create mode 100644 main.h create mode 100644 readline.c create mode 100644 readline.h create mode 100644 regex.c create mode 100644 regex.h create mode 100644 wind.c create mode 100644 wind.h diff --git a/basic_curses.c b/basic_curses.c new file mode 100644 index 0000000..ce6a338 --- /dev/null +++ b/basic_curses.c @@ -0,0 +1,132 @@ +#include "basic_curses.h" + +void printfc(char* fstrc, int max_length, ...) { + int i = 0; + int nbC = 0; + char* tmp = NULL; + int toP = 0; + + + va_list ap; + va_start(ap, max_length); + + + while(fstrc[i] && nbC < max_length) { + if( strchr( "${<%>}#", fstrc[i]) ) { + if( tmp ) { + printw("%.*s", toP, tmp); + tmp=NULL; + toP=0; + } + switch( fstrc[i] ) { + case '$': + if( fstrc[i+1] ) { + if( isdigit(fstrc[i+1]) ) { + if( fstrc[i+1] == '9') { + ; + }else + attrset( COLOR_PAIR(fstrc[i+1]-'0') | A_NORMAL); + }else{ + fprintf(stderr,"%c isn't a color, skipping", fstrc[i+1]); + } + }else{ + fprintf(stderr,"end of line by '$', expect a color after '$'"); + return; + } + i+=2; + break; + case '%': + { + int j = 1; + char format[10] = "%"; + while(j < 9 && !strchr("diouxXeEfFgGaAcsp%", format[j] = fstrc[i+j]) ); + if( j == 9 ) { + fprintf(stderr,"format too long %s...",format); + return; + } + if( strchr("di", format[j] ) ) { + printw(format, va_arg(ap, int)); + }else if( strchr("ouxX", format[j]) ) { + printw(format, va_arg(ap, unsigned int)); + }else if( strchr("eE", format[j]) ) { + printw(format, va_arg(ap, double)); + }else if( strchr("fF", format[j]) ) { + printw(format, va_arg(ap, double)); + }else if( strchr("gG", format[j]) ) { + printw(format, va_arg(ap, double)); + }else if( strchr("aA", format[j]) ) { + printw(format, va_arg(ap, double)); + }else if( strchr("c", format[j]) ) { + printw(format, va_arg(ap, int)); + }else if( strchr("s", format[j]) ) { + printw(format, va_arg(ap, const char*)); + }else if( strchr("p", format[j]) ) { + printw(format, va_arg(ap, void*)); + }else if( strchr("%", format[j]) ) { + printw("%%"); + } +// nbC+= + + i+=j; + } + } + }else{ + if( ! tmp ) + tmp = fstrc+i; + toP += 1; + nbC++; + i++; + } + } + if(tmp) + printw("%.*s", toP, tmp); + fflush(NULL); +} + +void printc(char* fstrc, int max_length) { + int i = 0; + int nbC = 0; + char* tmp = NULL; + int toP = 0; + while(fstrc[i] && nbC < max_length) { + if( strchr( "${<>}#", fstrc[i]) ) { + if( tmp ) { + printw("%.*s", toP, tmp); + tmp=NULL; + toP=0; + } + switch( fstrc[i] ) { + case '$': + if( fstrc[i+1] ) { + if( isdigit(fstrc[i+1]) ) { + if( fstrc[i+1] == '9') { + ; + }else + attrset( COLOR_PAIR(fstrc[i+1]-'0') | A_NORMAL); + }else{ + fprintf(stderr,"%c isn't a color, skipping", fstrc[i+1]); + } + }else{ + fprintf(stderr,"end of line by '$', expect a color after '$'"); + return; + } + i+=2; + } + }else{ + if( ! tmp ) + tmp = fstrc+i; + toP += 1; + nbC++; + i++; + } + } + if(tmp) + printw("%.*s", toP, tmp); + fflush(NULL); +} +void mvprintc(int x, int y, char* fstrc, int max_length) { + move(y, x); + printc(fstrc, max_length); +} + + diff --git a/basic_curses.h b/basic_curses.h new file mode 100644 index 0000000..00be967 --- /dev/null +++ b/basic_curses.h @@ -0,0 +1,15 @@ + +#ifndef BASIC_CURSES_H +#define BASIC_CURSES_H + +#include "main.h" + +void printfc(char* fstrc, int max_length, ...); + +void printc(char* fstrc, int max_length); +void mvprintc(int x, int y, char* fstrc, int max_length); + + +#define mvprintfc( x, y, fstrc, max, ...) (move((y),(x)),printfc(fstrc, max, __VA_ARGS__ )) + +#endif diff --git a/h.c b/h.c index c40af47..bf7f9e4 100755 --- a/h.c +++ b/h.c @@ -1,21 +1,18 @@ -#define _DEFAULT_SOURCE -#define _XOPEN_SOURCE 700 // For strnlen() -#include -#include -#include -#include -#include + + +#include "main.h" +#include "regex.h" +#include "readline.h" + #include -#include -#include -#include -#include #include #include #include -#include #include +#include "item.h" +#include "wind.h" + #ifdef DEBUG @@ -23,604 +20,37 @@ #endif +const int color[] = { + COLOR_BLACK, COLOR_RED, + COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, + COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE}; -#define MAX_PATH 1024 - -#define max(a, b) \ - ({ typeof(a) _a = a; \ - typeof(b) _b = b; \ - _a > _b ? _a : _b; }) +#define MAX_PATH 1024 +extern char* msg_win_str; -static short my_fg = COLOR_WHITE; -static short my_bg = COLOR_BLACK; static bool visual_mode = false; -static bool input_avail = false; -static unsigned char input; -static char *msg_win_str = NULL; -static bool should_exit = false; -static WINDOW *cmd_win; -static WINDOW *sep_win; - +WINDOW *cmd_win; +WINDOW *sep_win; char status[10]; -int color[] = { - COLOR_BLACK, COLOR_RED, - COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, - COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE}; - - - -typedef struct tagInfo { - TagLib_File *file; - TagLib_Tag *tag; - const TagLib_AudioProperties *properties; -} tagInfo; - -typedef struct itemC { - char* cstr; - int opt; - char* suffix; - char* prefix; - tagInfo info; - int selected; - int id; -} itemC; - -typedef struct menuC { - itemC* list; - int nbElem; - int hl; - int firstElem; - int opt; // SCROLL | HL_HIDE | HL_HIDE | HL_BOLD | HL_CLIGN | HL_INV [ | BORDER ] - char* suffix; - char* prefix; - int x,y,w,h; -} menuC; - - -#define HIDDEN 1 - - -// Calculates the cursor column for the readline window in a way that supports -// multibyte, multi-column and combining characters. readline itself calculates -// this as part of its default redisplay function and does not export the -// cursor column. -// -// Returns the total width (in columns) of the characters in the 'n'-byte -// prefix of the null-terminated multibyte string 's'. If 'n' is larger than -// 's', returns the total width of the string. Tries to emulate how readline -// prints some special characters. -// -// 'offset' is the current horizontal offset within the line. This is used to -// get tabstops right. -// -// Makes a guess for malformed strings. -static size_t strnwidth(const char *s, size_t n, size_t offset) -{ - mbstate_t shift_state; - wchar_t wc; - size_t wc_len; - size_t width = 0; - - // Start in the initial shift state - memset(&shift_state, '\0', sizeof shift_state); - - for (size_t i = 0; i < n; i += wc_len) { - // Extract the next multibyte character - wc_len = mbrtowc(&wc, s + i, MB_CUR_MAX, &shift_state); - switch (wc_len) { - case 0: - // Reached the end of the string - goto done; - - case (size_t)-1: case (size_t)-2: - // Failed to extract character. Guess that the remaining characters - // are one byte/column wide each. - width += strnlen(s, n - i); - goto done; - } - - if (wc == '\t') - width = ((width + offset + 8) & ~7) - offset; - else - // TODO: readline also outputs ~ and the like for some - // non-printable characters - width += iswcntrl(wc) ? 2 : max(0, wcwidth(wc)); - } - -done: - return width; -} - -// Like strnwidth, but calculates the width of the entire string -static size_t strwidth(const char *s, size_t offset) -{ - return strnwidth(s, SIZE_MAX, offset); -} - - -static noreturn void fail_exit(const char *msg) -{ - // Make sure endwin() is only called in visual mode. As a note, calling it - // twice does not seem to be supported and messed with the cursor position. +void fail_exit(const char *msg) { if (visual_mode) endwin(); fprintf(stderr, "%s\n", msg); exit(EXIT_FAILURE); } -// Checks errors for (most) ncurses functions. CHECK(fn, x, y, z) is a checked -// version of fn(x, y, z). -#define CHECK(fn, ...) \ - do \ - if (fn(__VA_ARGS__) == ERR) \ - fail_exit(#fn"() failed"); \ - while (false) - - -// Not bothering with 'input_avail' and just returning 0 here seems to do the -// right thing too, but this might be safer across readline versions -static int readline_input_avail(void) -{ - return input_avail; -} - -static int readline_getc(FILE *dummy) -{ - input_avail = false; - return input; -} -static void forward_to_readline(char c) -{ - input = c; - input_avail = true; - rl_callback_read_char(); -} -static void msg_win_redisplay(bool for_resize) -{ - CHECK(mvaddstr, 0, 0, msg_win_str ? msg_win_str : ""); - - // We batch window updates when resizing -/* - if (for_resize) - CHECK(wnoutrefresh, msg_win); - else - CHECK(wrefresh, msg_win); -*/ -} -static void got_command(char *line) -{ - if( line ) - if (*line != '\0') - add_history(line); - - free(msg_win_str); - msg_win_str = line; - //msg_win_redisplay(false); - should_exit = true; -} - -static void cmd_win_redisplay(bool for_resize) -{ - size_t prompt_width = strwidth(rl_display_prompt, 0); - size_t cursor_col = prompt_width + - strnwidth(rl_line_buffer, rl_point, prompt_width); - - CHECK(werase, cmd_win); - // This might write a string wider than the terminal currently, so don't - // check for errors - mvwprintw(cmd_win, 0, 0, "%s%s", rl_display_prompt, rl_line_buffer); - if (cursor_col >= COLS) - // Hide the cursor if it lies outside the window. Otherwise it'll - // appear on the very right. - curs_set(0); - else { - CHECK(wmove, cmd_win, 0, cursor_col); - //curs_set(2); - } - // We batch window updates when resizing - if (for_resize) - CHECK(wnoutrefresh, cmd_win); - else - CHECK(wrefresh, cmd_win); -} - -static void readline_redisplay(void) -{ - cmd_win_redisplay(false); -} - - - -void rintfc(char* fstrc, int max_length); - - -int sort_i(const void* A, const void* B) { - itemC* a = (itemC*)A; - itemC* b = (itemC*)B; - - if(a->opt > b->opt) - return -1; - else if(a->opt < b->opt) - return 1; - else { - return strcmp(a->cstr,b->cstr); - } -} -void listdir(int option, itemC** m, int* s) { - DIR *dir; - int nbitem = 0; - itemC* menu = NULL; - struct dirent *entry; - - if (!(dir = opendir("."))) - return; - - if (!(entry = readdir(dir))) - return; - - do { - if( entry->d_name[0] == '.' && !(option & HIDDEN) && strcmp(entry->d_name, "..") ) - continue; - - menu = realloc(menu, ++nbitem * sizeof *menu); - - if (entry->d_type == DT_DIR) { - char* tmp = malloc( strlen(entry->d_name)+3); - sprintf(tmp, "[%s]", entry->d_name); - menu[nbitem-1].cstr = tmp; - menu[nbitem-1].opt = 1; - menu[nbitem-1].id = nbitem-1; - menu[nbitem-1].selected = 0; - } - else { - TagLib_File *file; - TagLib_Tag *tag; - const TagLib_AudioProperties *properties; - - file = taglib_file_new(entry->d_name); - - if(file == NULL) { - menu = realloc(menu, --nbitem * sizeof *menu); - continue; - } - - tag = taglib_file_tag(file); - properties = taglib_file_audioproperties(file); - - if( ! properties ) { - fprintf(stderr, "ID3 file but not audio"); - taglib_tag_free_strings(); - taglib_file_free(file); - } - menu[nbitem-1].cstr = strdup(entry->d_name); - menu[nbitem-1].opt = 0; - menu[nbitem-1].id = nbitem-1; - menu[nbitem-1].info = (tagInfo){file, tag, properties}; - menu[nbitem-1].selected = 0; - - } - } while (entry = readdir(dir)); -end: - - closedir(dir); - *m = menu; - *s = nbitem; -} - -void printfc(char* fstrc, int max_length, ...) { - int i = 0; - int nbC = 0; - char* tmp = NULL; - int toP = 0; - va_list ap; - va_start(ap, max_length); - - while(fstrc[i] && nbC < max_length) { - if( strchr( "${<%>}#", fstrc[i]) ) { - if( tmp ) { - printw("%.*s", toP, tmp); - tmp=NULL; - toP=0; - } - switch( fstrc[i] ) { - case '$': - if( fstrc[i+1] ) { - if( isdigit(fstrc[i+1]) ) { - if( fstrc[i+1] == '9') { - ; - }else - attrset( COLOR_PAIR(fstrc[i+1]-'0') | A_NORMAL); - }else{ - fprintf(stderr,"%c isn't a color, skipping", fstrc[i+1]); - } - }else{ - fprintf(stderr,"end of line by '$', expect a color after '$'"); - return; - } - i+=2; - break; - case '%': - { - int j = 1; - char format[10] = "%"; - while(j < 9 && !strchr("diouxXeEfFgGaAcsp%", format[j] = fstrc[i+j]) ); - if( j == 9 ) { - fprintf(stderr,"format too long %s...",format); - return; - } - if( strchr("di", format[j] ) ) { - printw(format, va_arg(ap, int)); - }else if( strchr("ouxX", format[j]) ) { - printw(format, va_arg(ap, unsigned int)); - }else if( strchr("eE", format[j]) ) { - printw(format, va_arg(ap, double)); - }else if( strchr("fF", format[j]) ) { - printw(format, va_arg(ap, double)); - }else if( strchr("gG", format[j]) ) { - printw(format, va_arg(ap, double)); - }else if( strchr("aA", format[j]) ) { - printw(format, va_arg(ap, double)); - }else if( strchr("c", format[j]) ) { - printw(format, va_arg(ap, int)); - }else if( strchr("s", format[j]) ) { - printw(format, va_arg(ap, const char*)); - }else if( strchr("p", format[j]) ) { - printw(format, va_arg(ap, void*)); - }else if( strchr("%", format[j]) ) { - printw("%%"); - } -// nbC+= - - i+=j; - } - } - }else{ - if( ! tmp ) - tmp = fstrc+i; - toP += 1; - nbC++; - i++; - } - } - if(tmp) - printw("%.*s", toP, tmp); - fflush(NULL); -} - -void printc(char* fstrc, int max_length) { - int i = 0; - int nbC = 0; - char* tmp = NULL; - int toP = 0; - while(fstrc[i] && nbC < max_length) { - if( strchr( "${<>}#", fstrc[i]) ) { - if( tmp ) { - printw("%.*s", toP, tmp); - tmp=NULL; - toP=0; - } - switch( fstrc[i] ) { - case '$': - if( fstrc[i+1] ) { - if( isdigit(fstrc[i+1]) ) { - if( fstrc[i+1] == '9') { - ; - }else - attrset( COLOR_PAIR(fstrc[i+1]-'0') | A_NORMAL); - }else{ - fprintf(stderr,"%c isn't a color, skipping", fstrc[i+1]); - } - }else{ - fprintf(stderr,"end of line by '$', expect a color after '$'"); - return; - } - i+=2; - } - }else{ - if( ! tmp ) - tmp = fstrc+i; - toP += 1; - nbC++; - i++; - } - } - if(tmp) - printw("%.*s", toP, tmp); - fflush(NULL); -} -void mvprintc(int x, int y, char* fstrc, int max_length) { - move(y, x); - printc(fstrc, max_length); -} -#define mvprintfc( x, y, fstrc, max, ...) (move((y),(x)),printfc(fstrc, max, __VA_ARGS__ )) -void printTagInfoHeader() { - attrset( COLOR_PAIR( COLOR_BLUE + 1) | A_BOLD); - mvprintc(COLS/2+2, 1, "Title :",15); - mvprintc(COLS/2+2, 2, "Artist :",15); - mvprintc(COLS/2+2, 3, "Album :",15); - mvprintc(COLS/2+2, 4, "Year :",15); - mvprintc(COLS/2+2, 5, "Track :",15); - mvprintc(COLS/2+2, 6, "Genre :",15); - mvprintc(COLS/2+2, 7, "Comment :",15); - attrset( COLOR_PAIR( 0 ) | A_NORMAL); -} -void printTagInfo(menuC* menu) { - static int mustClear = 0; - if( mustClear == 1 ) { - int x = menu->x; - int y = menu->y; - int h = menu->h; - int w = menu->w; - - for(int i = 0 ; i < 8 ; i++) { - move(1+i,COLS/2+14); - printw("%*s", COLS/2-14, " "); - } - mustClear = 0; - } - itemC* it = menu->list+menu->hl; - if( !it->opt ) { - char inT[6] = ""; - mvprintc( COLS/2+14, 1, taglib_tag_title(it->info.tag), COLS/2-14); - mvprintc( COLS/2+14, 2, taglib_tag_artist(it->info.tag), COLS/2-14); - mvprintc( COLS/2+14, 3, taglib_tag_album (it->info.tag), COLS/2-14); - sprintf(inT, "%d", taglib_tag_year (it->info.tag)); - mvprintc( COLS/2+14, 4, inT, COLS/2-14); - sprintf(inT, "%d", taglib_tag_track(it->info.tag)); - mvprintc( COLS/2+14, 5, inT, COLS/2-14); - mvprintc( COLS/2+14, 6, taglib_tag_genre(it->info.tag), COLS/2-14); - mvprintc( COLS/2+14, 7, taglib_tag_comment(it->info.tag), COLS/2-14); - mustClear = 1; - } - - -} -void cleanmenu(menuC* menu) { - for(int i = menu->firstElem ; i < menu->h && i < menu->nbElem; i++) { - move(menu->y+i,menu->x); - printw("%*s", menu->w, " "); - } - refresh(); -} -void printmenu(menuC* menu) { - - int x = menu->x; - int y = menu->y; - int h = menu->h; - int w = menu->w; - itemC* it = menu->list; - int s = menu->nbElem; - - if( menu->hl < (menu->firstElem) ) { - cleanmenu(menu); - } - - if( menu->hl >= (menu->firstElem+menu->h ) ) { - attrset(0 | A_NORMAL ); - menu->firstElem+=menu->h/2; - for(int i = 0 ; i < h ; i++) { - move(y+i,x); - printw("%*s", w, " "); - } - refresh(); - } - - - for(int i = menu->firstElem ; i < (h+menu->firstElem) && i < s ; i++) { - int color = 0, attr = A_NORMAL; - if( it[i].opt == 1 ) - color = COLOR_BLUE+1; - if( i == menu->hl ) - attr = A_REVERSE; - if( it[i].selected ) { - attr |= A_BOLD; - } - - attrset( COLOR_PAIR(color) | attr); - move(y,x); - printw("%*s", w, " "); - mvprintc(x,y++,it[i].cstr, w); - attrset(0 | A_NORMAL); - } -} -void printStatus(void) { - mvprintc(COLS-5,LINES-3," ",4); - mvprintc(COLS-5,LINES-3,status,4); -} - - -static void init_readline(void) -{ - // Disable completion. TODO: Is there a more robust way to do this? - if (rl_bind_key('\t', rl_insert) != 0) - fail_exit("Invalid key passed to rl_bind_key()"); - - // Let ncurses do all terminal and signal handling - rl_catch_signals = 0; - rl_catch_sigwinch = 0; - rl_deprep_term_function = NULL; - rl_prep_term_function = NULL; - - // Prevent readline from setting the LINES and COLUMNS environment - // variables, which override dynamic size adjustments in ncurses. When - // using the alternate readline interface (as we do here), LINES and - // COLUMNS are not updated if the terminal is resized between two calls to - // rl_callback_read_char() (which is almost always the case). - rl_change_environment = 0; - - // Handle input by manually feeding characters to readline - rl_getc_function = readline_getc; - rl_input_available_hook = readline_input_avail; - rl_redisplay_function = readline_redisplay; - - rl_callback_handler_install("> ", got_command); -} -static void deinit_readline(void) -{ - rl_callback_handler_remove(); -} -static void resizeMain(menuC* menu) { - clear(); - menu->h = LINES-2; - menu->w = COLS/2-1; - printmenu(menu); - printTagInfoHeader(); - printTagInfo(menu); - printStatus(); - move(1,COLS/2); - vline( ACS_VLINE, LINES-2) ; -} -static void resize(void) -{ - if (LINES >= 3) { - CHECK(wresize, sep_win, 1, COLS); - CHECK(wresize, cmd_win, 1, COLS); - - CHECK(mvwin, sep_win, LINES - 2, 0); - CHECK(mvwin, cmd_win, LINES - 1, 0); - } - - // Batch refreshes and commit them with doupdate() - CHECK(wnoutrefresh, sep_win); - cmd_win_redisplay(true); - CHECK(doupdate); -} -void readline_n(void) { - curs_set(2); - resize(); - while (!should_exit) { - // Using getch() here instead would refresh stdscr, overwriting the - // initial contents of the other windows on startup - int c = wgetch(cmd_win); - - if( c == '\n') - should_exit = 1; - if (c == KEY_RESIZE) - resize(); - else if (c == '\f') { // Ctrl-L -- redraw screen. - // Makes the next refresh repaint the screen from scratch - CHECK(clearok, curscr, TRUE); - // Resize and reposition windows in case that got messed up - // somehow - resize(); - } - else - forward_to_readline(c); - } - should_exit = 0; -} void edit_rl(menuC* menu,void func(TagLib_Tag *, const char*), int c) { status[1]=c; printStatus(); @@ -650,152 +80,6 @@ void edit_rl(menuC* menu,void func(TagLib_Tag *, const char*), int c) { } } -#define MAX_ERROR_MSG 0x1000 - -/* Compile the regular expression described by "regex_text" into - "r". */ - -static int compile_regex (regex_t * r, const char * regex_text) -{ - int status = regcomp (r, regex_text, REG_EXTENDED|REG_NEWLINE); - if (status != 0) { - char error_message[MAX_ERROR_MSG]; - regerror (status, r, error_message, MAX_ERROR_MSG); - printf ("Regex error compiling '%s': %s\n", - regex_text, error_message); - return 1; - } - return 0; -} - -/* - Match the string in "to_match" against the compiled regular - expression in "r". - */ -static int match_regex (const char* rS, const char * to_match, char* m2[], int nbR) -{ - regex_t* r = malloc(sizeof *r); - compile_regex(r, rS); - - fprintf(stderr, "%d - %d\n", nbR, r->re_nsub); - - if( nbR != r->re_nsub ) - return 0; - regmatch_t m[r->re_nsub]; - - /* "P" is a pointer into the string which points to the end of the - previous match. */ - /* "N_matches" is the maximum number of matches allowed. */ - /* "M" contains the matches found. */ - int rs = regexec (r, to_match /* if only 1 match, set it to to_match */, - r->re_nsub+1 /* nbMatch Max */, m /* res */, 0); - - // regexec(&re, line, 2, rm, 0) - fprintf(stderr,"<<%s>>\n", to_match); - fprintf(stderr,"Line: <<%.*s>>\n", (int)(m[0].rm_eo - m[0].rm_so), to_match + m[0].rm_so); - for(int i = 1 ; i < (r->re_nsub +1) ; i++) { - m2[i-1] = malloc( (unsigned int)(m[i].rm_eo - m[i].rm_so) + 1); - m2[i-1][m[i].rm_eo - m[i].rm_so] = 0; - strncpy(m2[i-1], to_match+m[i].rm_so, m[i].rm_eo - m[i].rm_so); - fprintf(stderr,"Text: <<%.*s>>\n", (int)(m[i].rm_eo - m[i].rm_so), to_match + m[i].rm_so); - } - - /* - { - if( !rs ) - free(r); - } - */ - return 1; - - /* - while (1) { - int i = 0; - int nomatch = regexec (r, p, n_matches, m, 0); - if (nomatch) { - printf ("No more matches.\n"); - return nomatch; - } - for (i = 0; i < n_matches; i++) { - int start; - int finish; - if (m[i].rm_so == -1) { - break; - } - start = m[i].rm_so + (p - to_match); - finish = m[i].rm_eo + (p - to_match); - if (i == 0) { - printf ("$& is "); - } - else { - printf ("$%d is ", i); - } - printf ("'%.*s' (bytes %d:%d)\n", (finish - start), - to_match + start, start, finish); - } - p += m[0].rm_eo; - } - return 0; - */ -} -void regexSelection(menuC* menu) { - status[1]='s'; -} -void regexXtract(itemC* it) { - status[1]='x'; - - readline_n(); - clear(); - curs_set(0); - char* tmp = strrchr( msg_win_str , '/' ); - if( !msg_win_str || !*msg_win_str || !tmp) { - return; - } - - char *regexS = malloc(tmp-msg_win_str+1); - - strncpy( regexS, msg_win_str, tmp-msg_win_str); - regexS[tmp - msg_win_str] = 0; - char* const line = it->cstr; - - char** tab = NULL; - tmp++; - int nbR = strlen(tmp); - tab = malloc( sizeof *tab * (nbR+1)); - if( match_regex(regexS, line, tab, nbR ) ) { - fprintf(stderr, "Hello"); - for(int i = 0 ; i < nbR ; i++) { - fprintf(stderr, "<<%s>>\n", tab[i]); - switch(tmp[i]) { - case 't': - taglib_tag_set_title(it->info.tag, tab[i]); - break; - case 'a': - taglib_tag_set_artist(it->info.tag, tab[i]); - break; - case 'b': - taglib_tag_set_album(it->info.tag, tab[i]); - break; - case 'y': - break; - case 'n': - break; - case 'g': - taglib_tag_set_genre(it->info.tag, tab[i]); - break; - case 'c': - taglib_tag_set_comment(it->info.tag, tab[i]); - break; - default: - ; - } - taglib_file_save(it->info.file); - } - } -} -void loadMenu(itemC** menuL, int* size) { - -} int main(int argc, char* argv[]){ itemC* menuL = NULL; int size = 0; @@ -882,10 +166,13 @@ int main(int argc, char* argv[]){ menu.list[menu.hl].selected = !menu.list[menu.hl].selected; break; case 's': - regexSelection(&menu); + prepare("xs"); + regexSelection(&menu,msg_win_str); + resizeMain(&menu); break; case 'x': - regexXtract(&menu.list[menu.hl]); + prepare("x"); + regexXtract(&menu.list[menu.hl], msg_win_str); resizeMain(&menu); break; case 'q': @@ -899,6 +186,7 @@ int main(int argc, char* argv[]){ s[n-1] = ']'; cleanmenu(&menu); + freelitem(menuL, size); listdir(0,&menuL, &size); qsort(menuL, size, sizeof *menuL, sort_i); menu.list = menuL; diff --git a/item.c b/item.c new file mode 100644 index 0000000..b7dc18e --- /dev/null +++ b/item.c @@ -0,0 +1,101 @@ +#include "item.h" + + + +int sort_i(const void* A, const void* B) { + itemC* a = (itemC*)A; + itemC* b = (itemC*)B; + + if(a->opt > b->opt) + return -1; + else if(a->opt < b->opt) + return 1; + else { + return strcmp(a->cstr,b->cstr); + } +} +void freelitem( itemC* m, int s) { + for(int i = 0 ; i < s ; i++) { + free(m[i].cstr); + if( !m[i].opt) { + taglib_file_free(m[i].info.file); + } + } + + taglib_tag_free_strings(); + free(m); +} +void listdir(int option, itemC** m, int* s) { + DIR *dir; + int nbitem = 0; + itemC* menu = NULL; + struct dirent *entry; + + if (!(dir = opendir("."))) + return; + + if (!(entry = readdir(dir))) + return; + + do { + if( entry->d_name[0] == '.' && !(option & HIDDEN) && strcmp(entry->d_name, "..") ) + continue; + +// QUICK FIX OF TAGLIB + if( strchr(entry->d_name, '#') ) + continue; +#ifdef DEBUG + mvprintc(1,1,entry->d_name,COLS/2-5); +#endif + + refresh(); + +// fprintf(stderr, "%s\n", entry->d_name); + menu = realloc(menu, ++nbitem * sizeof *menu); + + if (entry->d_type == DT_DIR) { + char* tmp = malloc( strlen(entry->d_name)+3); + sprintf(tmp, "[%s]", entry->d_name); + menu[nbitem-1].cstr = tmp; + menu[nbitem-1].opt = 1; + menu[nbitem-1].id = nbitem-1; + menu[nbitem-1].selected = 0; + } + else { + TagLib_File *file; + TagLib_Tag *tag; + const TagLib_AudioProperties *properties; + + file = taglib_file_new(entry->d_name); + + if(file == NULL) { + menu = realloc(menu, --nbitem * sizeof *menu); + continue; + } + + tag = taglib_file_tag(file); + properties = taglib_file_audioproperties(file); + + if( ! properties ) { + fprintf(stderr, "ID3 file but not audio"); + taglib_tag_free_strings(); + taglib_file_free(file); + menu[nbitem-1].opt = 2; + }else{ + menu[nbitem-1].opt = 0; + } + menu[nbitem-1].cstr = strdup(entry->d_name); + menu[nbitem-1].id = nbitem-1; + menu[nbitem-1].info = (tagInfo){file, tag, properties}; + menu[nbitem-1].selected = 0; + + } + } while (entry = readdir(dir)); +end: + + closedir(dir); + *m = menu; + *s = nbitem; +} + + diff --git a/item.h b/item.h new file mode 100644 index 0000000..b27714f --- /dev/null +++ b/item.h @@ -0,0 +1,13 @@ + +#ifndef ITEM_METAG_H +#define ITEM_METAG_H + +#include "main.h" +#include + + +int sort_i(const void* A, const void* B); +void freelitem( itemC* m, int s); +void listdir(int option, itemC** m, int* s); + +#endif diff --git a/main.h b/main.h new file mode 100644 index 0000000..9f272ec --- /dev/null +++ b/main.h @@ -0,0 +1,79 @@ + +#ifndef MAIN_METAG_H +#define MAIN_METAG_H + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 // For strnlen() +#endif + + +#include +#include +#include +#include +#include +#include + + + +typedef struct tagInfo { + TagLib_File *file; + TagLib_Tag *tag; + const TagLib_AudioProperties *properties; +} tagInfo; + +typedef struct itemC { + char* cstr; + int opt; + char* suffix; + char* prefix; + tagInfo info; + int selected; + int id; +} itemC; + +typedef struct menuC { + itemC* list; + int nbElem; + int hl; + int firstElem; + int opt; // SCROLL | HL_HIDE | HL_HIDE | HL_BOLD | HL_CLIGN | HL_INV [ | BORDER ] + char* suffix; + char* prefix; + int x,y,w,h; +} menuC; + + +#define HIDDEN 1 + +void printStatus(void); + +void resize(void); + + +void fail_exit(const char *msg); + + + +// Checks errors for (most) ncurses functions. CHECK(fn, x, y, z) is a checked +// version of fn(x, y, z). +#define CHECK(fn, ...) \ + do \ + if (fn(__VA_ARGS__) == ERR) \ + fail_exit(#fn"() failed"); \ + while (false) + + +#define max(a, b) \ + ({ typeof(a) _a = a; \ + typeof(b) _b = b; \ + _a > _b ? _a : _b; }) + + + + +#endif diff --git a/readline.c b/readline.c new file mode 100644 index 0000000..176657d --- /dev/null +++ b/readline.c @@ -0,0 +1,198 @@ + +#include "readline.h" + + + +static bool input_avail = false; +static unsigned char input; +static bool should_exit = false; +char *msg_win_str = NULL; +extern WINDOW *cmd_win; + + +// Calculates the cursor column for the readline window in a way that supports +// multibyte, multi-column and combining characters. readline itself calculates +// this as part of its default redisplay function and does not export the +// cursor column. +// +// Returns the total width (in columns) of the characters in the 'n'-byte +// prefix of the null-terminated multibyte string 's'. If 'n' is larger than +// 's', returns the total width of the string. Tries to emulate how readline +// prints some special characters. +// +// 'offset' is the current horizontal offset within the line. This is used to +// get tabstops right. +// +// Makes a guess for malformed strings. +static size_t strnwidth(const char *s, size_t n, size_t offset) +{ + mbstate_t shift_state; + wchar_t wc; + size_t wc_len; + size_t width = 0; + + // Start in the initial shift state + memset(&shift_state, '\0', sizeof shift_state); + + for (size_t i = 0; i < n; i += wc_len) { + // Extract the next multibyte character + wc_len = mbrtowc(&wc, s + i, MB_CUR_MAX, &shift_state); + switch (wc_len) { + case 0: + // Reached the end of the string + goto done; + + case (size_t)-1: case (size_t)-2: + // Failed to extract character. Guess that the remaining characters + // are one byte/column wide each. + width += strnlen(s, n - i); + goto done; + } + + if (wc == '\t') + width = ((width + offset + 8) & ~7) - offset; + else + // TODO: readline also outputs ~ and the like for some + // non-printable characters + width += iswcntrl(wc) ? 2 : max(0, wcwidth(wc)); + } + +done: + return width; +} + +// Like strnwidth, but calculates the width of the entire string +static size_t strwidth(const char *s, size_t offset) +{ + return strnwidth(s, SIZE_MAX, offset); +} + + +// Not bothering with 'input_avail' and just returning 0 here seems to do the +// right thing too, but this might be safer across readline versions +static int readline_input_avail(void) +{ + return input_avail; +} + +static int readline_getc(FILE *dummy) +{ + input_avail = false; + return input; +} +static void forward_to_readline(char c) +{ + input = c; + input_avail = true; + rl_callback_read_char(); +} +static void msg_win_redisplay(bool for_resize) +{ + CHECK(mvaddstr, 0, 0, msg_win_str ? msg_win_str : ""); + + // We batch window updates when resizing +/* + if (for_resize) + CHECK(wnoutrefresh, msg_win); + else + CHECK(wrefresh, msg_win); +*/ +} +static void got_command(char *line) +{ + if( line ) + if (*line != '\0') + add_history(line); + + free(msg_win_str); + msg_win_str = line; + //msg_win_redisplay(false); + should_exit = true; +} + +void cmd_win_redisplay(bool for_resize) +{ + size_t prompt_width = strwidth(rl_display_prompt, 0); + size_t cursor_col = prompt_width + + strnwidth(rl_line_buffer, rl_point, prompt_width); + + CHECK(werase, cmd_win); + // This might write a string wider than the terminal currently, so don't + // check for errors + mvwprintw(cmd_win, 0, 0, "%s%s", rl_display_prompt, rl_line_buffer); + if (cursor_col >= COLS) + // Hide the cursor if it lies outside the window. Otherwise it'll + // appear on the very right. + curs_set(0); + else { + CHECK(wmove, cmd_win, 0, cursor_col); + //curs_set(2); + } + // We batch window updates when resizing + if (for_resize) + CHECK(wnoutrefresh, cmd_win); + else + CHECK(wrefresh, cmd_win); +} + +static void readline_redisplay(void) +{ + cmd_win_redisplay(false); +} + + +void init_readline(void) +{ + // Disable completion. TODO: Is there a more robust way to do this? + if (rl_bind_key('\t', rl_insert) != 0) + fail_exit("Invalid key passed to rl_bind_key()"); + + // Let ncurses do all terminal and signal handling + rl_catch_signals = 0; + rl_catch_sigwinch = 0; + rl_deprep_term_function = NULL; + rl_prep_term_function = NULL; + + // Prevent readline from setting the LINES and COLUMNS environment + // variables, which override dynamic size adjustments in ncurses. When + // using the alternate readline interface (as we do here), LINES and + // COLUMNS are not updated if the terminal is resized between two calls to + // rl_callback_read_char() (which is almost always the case). + rl_change_environment = 0; + + // Handle input by manually feeding characters to readline + rl_getc_function = readline_getc; + rl_input_available_hook = readline_input_avail; + rl_redisplay_function = readline_redisplay; + + rl_callback_handler_install("> ", got_command); +} + + +void readline_n(void) { + curs_set(2); + resize(); + while (!should_exit) { + // Using getch() here instead would refresh stdscr, overwriting the + // initial contents of the other windows on startup + int c = wgetch(cmd_win); + + if( c == '\n') + should_exit = 1; + if (c == KEY_RESIZE) + resize(); + else if (c == '\f') { // Ctrl-L -- redraw screen. + // Makes the next refresh repaint the screen from scratch + CHECK(clearok, curscr, TRUE); + // Resize and reposition windows in case that got messed up + // somehow + resize(); + } + else + forward_to_readline(c); + } + should_exit = 0; +} + + + diff --git a/readline.h b/readline.h new file mode 100644 index 0000000..cf96e11 --- /dev/null +++ b/readline.h @@ -0,0 +1,27 @@ + +#ifndef READLINE_METAG_H +#define READLINE_METAG_H + +#include "main.h" +#include +#include +#include +#include +#include +#include +#include + + + + + +void readline_n(void); + +void init_readline(void); + +void cmd_win_redisplay(bool for_resize); + +void deinit_readline(void); + +#endif + diff --git a/regex.c b/regex.c new file mode 100644 index 0000000..b7f19aa --- /dev/null +++ b/regex.c @@ -0,0 +1,151 @@ +#include "regex.h" + + +#define MAX_ERROR_MSG 0x1000 + +/* Compile the regular expression described by "regex_text" into + "r". */ + +static int compile_regex (regex_t * r, const char * regex_text) +{ + int status = regcomp (r, regex_text, REG_EXTENDED|REG_NEWLINE); + if (status != 0) { + char error_message[MAX_ERROR_MSG]; + regerror (status, r, error_message, MAX_ERROR_MSG); + printf ("Regex error compiling '%s': %s\n", + regex_text, error_message); + return 1; + } + return 0; +} + +/* + Match the string in "to_match" against the compiled regular + expression in "r". + */ +static int match_regex (const char* rS, const char * to_match, char* m2[], int nbR) +{ + regex_t* r = malloc(sizeof *r); + compile_regex(r, rS); + + fprintf(stderr, "%d - %d\n", nbR, r->re_nsub); + + if( nbR != r->re_nsub && nbR > 0) + return 0; + regmatch_t m[r->re_nsub+1]; + + /* "P" is a pointer into the string which points to the end of the + previous match. */ + /* "N_matches" is the maximum number of matches allowed. */ + /* "M" contains the matches found. */ + int rs = regexec (r, to_match /* if only 1 match, set it to to_match */, + nbR > 0 ? r->re_nsub+1: 0 /* nbMatch Max */, m /* res */, 0); + + // regexec(&re, line, 2, rm, 0) + if( !rs ) { + fprintf(stderr,"<<%s>>\n", to_match); +// fprintf(stderr,"Line: <<%.*s>>\n", (int)(m[0].rm_eo - m[0].rm_so), to_match + m[0].rm_so); + } + + if( m2 ) + for(int i = 1 ; i < (r->re_nsub +1) ; i++) { + m2[i-1] = malloc( (unsigned int)(m[i].rm_eo - m[i].rm_so) + 1); + m2[i-1][m[i].rm_eo - m[i].rm_so] = 0; + strncpy(m2[i-1], to_match+m[i].rm_so, m[i].rm_eo - m[i].rm_so); + fprintf(stderr,"Text: <<%.*s>>\n", (int)(m[i].rm_eo - m[i].rm_so), to_match + m[i].rm_so); + } + return !rs; + + /* + { + if( !rs ) + free(r); + } + */ + return 1; + + /* + while (1) { + int i = 0; + int nomatch = regexec (r, p, n_matches, m, 0); + if (nomatch) { + printf ("No more matches.\n"); + return nomatch; + } + for (i = 0; i < n_matches; i++) { + int start; + int finish; + if (m[i].rm_so == -1) { + break; + } + start = m[i].rm_so + (p - to_match); + finish = m[i].rm_eo + (p - to_match); + if (i == 0) { + printf ("$& is "); + } + else { + printf ("$%d is ", i); + } + printf ("'%.*s' (bytes %d:%d)\n", (finish - start), + to_match + start, start, finish); + } + p += m[0].rm_eo; + } + return 0; + */ +} +void regexSelection(menuC* menu, const char* msg) { + for(int i = 0 ; i < menu->nbElem ; i++ ) { + if( match_regex(msg, menu->list[i].cstr, NULL, -1 ) ) { + menu->list[i].selected = 1; + } + } +} +void regexXtract(itemC* it, const char* msg) { + char* tmp = strrchr( msg , '/' ); + if( !msg || !*msg || !tmp) { + return; + } + + char *regexS = malloc(tmp-msg+1); + + strncpy( regexS, msg, tmp-msg); + regexS[tmp - msg] = 0; + char* const line = it->cstr; + + char** tab = NULL; + tmp++; + int nbR = strlen(tmp); + tab = malloc( sizeof *tab * (nbR+1)); + if( match_regex(regexS, line, tab, nbR ) ) { + fprintf(stderr, "Hello"); + for(int i = 0 ; i < nbR ; i++) { + fprintf(stderr, "<<%s>>\n", tab[i]); + switch(tmp[i]) { + case 't': + taglib_tag_set_title(it->info.tag, tab[i]); + break; + case 'a': + taglib_tag_set_artist(it->info.tag, tab[i]); + break; + case 'b': + taglib_tag_set_album(it->info.tag, tab[i]); + break; + case 'y': + break; + case 'n': + break; + case 'g': + taglib_tag_set_genre(it->info.tag, tab[i]); + break; + case 'c': + taglib_tag_set_comment(it->info.tag, tab[i]); + break; + default: + ; + } + taglib_file_save(it->info.file); + } + } +} + diff --git a/regex.h b/regex.h new file mode 100644 index 0000000..752f17b --- /dev/null +++ b/regex.h @@ -0,0 +1,17 @@ + +#ifndef REGEX_METAG_H +#define REGEX_METAG_H + + +#include +#include "main.h" + +static int compile_regex (regex_t * r, const char * regex_text); +static int match_regex (const char* rS, const char * to_match, char* m2[], int nbR); + + +void regexXtract(itemC* it, const char* str); +void regexSelection(menuC* menu, const char* msg); + +#endif + diff --git a/wind.c b/wind.c new file mode 100644 index 0000000..a09fdce --- /dev/null +++ b/wind.c @@ -0,0 +1,147 @@ + +#include "wind.h" + + + +static short my_fg = COLOR_WHITE; +static short my_bg = COLOR_BLACK; +extern char status[10]; +extern WINDOW *cmd_win; +extern WINDOW *sep_win; + + +void prepare(char* s) { + refresh(); + strcpy(status, s); + printStatus(); + readline_n(); + clear(); + curs_set(0); +} + +void printTagInfoHeader() { + attrset( COLOR_PAIR( COLOR_BLUE + 1) | A_BOLD); + mvprintc(COLS/2+2, 1, "Title :",15); + mvprintc(COLS/2+2, 2, "Artist :",15); + mvprintc(COLS/2+2, 3, "Album :",15); + mvprintc(COLS/2+2, 4, "Year :",15); + mvprintc(COLS/2+2, 5, "Track :",15); + mvprintc(COLS/2+2, 6, "Genre :",15); + mvprintc(COLS/2+2, 7, "Comment :",15); + attrset( COLOR_PAIR( 0 ) | A_NORMAL); +} +void printTagInfo(menuC* menu) { + static int mustClear = 0; + if( mustClear == 1 ) { + int x = menu->x; + int y = menu->y; + int h = menu->h; + int w = menu->w; + + for(int i = 0 ; i < 8 ; i++) { + move(1+i,COLS/2+14); + printw("%*s", COLS/2-14, " "); + } + mustClear = 0; + } + itemC* it = menu->list+menu->hl; + if( !it->opt ) { + char inT[6] = ""; + mvprintc( COLS/2+14, 1, taglib_tag_title(it->info.tag), COLS/2-14); + mvprintc( COLS/2+14, 2, taglib_tag_artist(it->info.tag), COLS/2-14); + mvprintc( COLS/2+14, 3, taglib_tag_album (it->info.tag), COLS/2-14); + sprintf(inT, "%d", taglib_tag_year (it->info.tag)); + mvprintc( COLS/2+14, 4, inT, COLS/2-14); + sprintf(inT, "%d", taglib_tag_track(it->info.tag)); + mvprintc( COLS/2+14, 5, inT, COLS/2-14); + mvprintc( COLS/2+14, 6, taglib_tag_genre(it->info.tag), COLS/2-14); + mvprintc( COLS/2+14, 7, taglib_tag_comment(it->info.tag), COLS/2-14); + mustClear = 1; + } + + +} +void cleanmenu(menuC* menu) { + for(int i = menu->firstElem ; i < menu->h && i < menu->nbElem; i++) { + move(menu->y+i,menu->x); + printw("%*s", menu->w, " "); + } + refresh(); +} +void printmenu(menuC* menu) { + + int x = menu->x; + int y = menu->y; + int h = menu->h; + int w = menu->w; + itemC* it = menu->list; + int s = menu->nbElem; + + if( menu->hl < (menu->firstElem) ) { + menu->firstElem-=menu->h/2; + if( menu->firstElem < 0 ) + menu->firstElem = 0; + + cleanmenu(menu); + } + + if( menu->hl >= (menu->firstElem+menu->h ) ) { + attrset(0 | A_NORMAL ); + menu->firstElem+=menu->h/2; + if( menu-> firstElem > menu->nbElem ) + menu-> firstElem = menu->nbElem; + cleanmenu(menu); + } + + + for(int i = menu->firstElem ; i < (h+menu->firstElem) && i < s ; i++) { + int color = 0, attr = A_NORMAL; + if( it[i].opt == 1 ) + color = COLOR_BLUE+1; + if( i == menu->hl ) + attr = A_REVERSE; + if( it[i].selected ) { + attr |= A_BOLD; + } + + attrset( COLOR_PAIR(color) | attr); + move(y,x); + printw("%*s", w, " "); + mvprintc(x,y++,it[i].cstr, w); + attrset(0 | A_NORMAL); + } +} +void printStatus(void) { + mvprintc(COLS-5,LINES-3," ",4); + mvprintc(COLS-5,LINES-3,status,4); +} + + +void resize(void) +{ + if (LINES >= 3) { + CHECK(wresize, sep_win, 1, COLS); + CHECK(wresize, cmd_win, 1, COLS); + + CHECK(mvwin, sep_win, LINES - 2, 0); + CHECK(mvwin, cmd_win, LINES - 1, 0); + } + + // Batch refreshes and commit them with doupdate() + CHECK(wnoutrefresh, sep_win); + cmd_win_redisplay(true); + CHECK(doupdate); +} + +void resizeMain(menuC* menu) { + clear(); + menu->h = LINES-2; + menu->w = COLS/2-1; + printmenu(menu); + printTagInfoHeader(); + printTagInfo(menu); + printStatus(); + move(1,COLS/2); + vline( ACS_VLINE, LINES-2) ; +} + diff --git a/wind.h b/wind.h new file mode 100644 index 0000000..63fed9c --- /dev/null +++ b/wind.h @@ -0,0 +1,24 @@ + +#ifndef WIND_METAG_H +#define WIND_METAG_H + +#include "main.h" +#include "readline.h" +#include "basic_curses.h" + + +void printTagInfoHeader(); + +void printTagInfo(menuC* menu); + +void cleanmenu(menuC* menu); + +void printmenu(menuC* menu); + +void printStatus(void); + +void prepare(char* s); + +void resize(void); +void resizeMain(menuC* menu); +#endif -- cgit v1.2.3