diff options
-rw-r--r-- | basic_curses.c | 132 | ||||
-rw-r--r-- | basic_curses.h | 15 | ||||
-rwxr-xr-x | h.c | 760 | ||||
-rw-r--r-- | item.c | 101 | ||||
-rw-r--r-- | item.h | 13 | ||||
-rw-r--r-- | main.h | 79 | ||||
-rw-r--r-- | readline.c | 198 | ||||
-rw-r--r-- | readline.h | 27 | ||||
-rw-r--r-- | regex.c | 151 | ||||
-rw-r--r-- | regex.h | 17 | ||||
-rw-r--r-- | wind.c | 147 | ||||
-rw-r--r-- | wind.h | 24 |
12 files changed, 928 insertions, 736 deletions
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 @@ -1,21 +1,18 @@ -#define _DEFAULT_SOURCE -#define _XOPEN_SOURCE 700 // For strnlen() -#include<stdio.h> -#include<stdlib.h> -#include<string.h> -#include<ctype.h> -#include <ncurses.h> + + +#include "main.h" +#include "regex.h" +#include "readline.h" + #include <dirent.h> -#include <locale.h> -#include <taglib/tag_c.h> -#include <readline/history.h> -#include <readline/readline.h> #include <stdnoreturn.h> #include <wchar.h> #include <wctype.h> -#include <regex.h> #include <unistd.h> +#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 ~<letter> 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; @@ -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; +} + + @@ -0,0 +1,13 @@ + +#ifndef ITEM_METAG_H +#define ITEM_METAG_H + +#include "main.h" +#include <dirent.h> + + +int sort_i(const void* A, const void* B); +void freelitem( itemC* m, int s); +void listdir(int option, itemC** m, int* s); + +#endif @@ -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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <ncurses.h> +#include <taglib/tag_c.h> + + + +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 ~<letter> 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 <stdio.h> +#include <wchar.h> +#include <locale.h> +#include <wctype.h> +#include <unistd.h> +#include <readline/history.h> +#include <readline/readline.h> + + + + + +void readline_n(void); + +void init_readline(void); + +void cmd_win_redisplay(bool for_resize); + +void deinit_readline(void); + +#endif + @@ -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); + } + } +} + @@ -0,0 +1,17 @@ + +#ifndef REGEX_METAG_H +#define REGEX_METAG_H + + +#include <regex.h> +#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 + @@ -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) ; +} + @@ -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 |