From cdea6b0e8c1e51c9962f73e183a3bd72ed63b40f Mon Sep 17 00:00:00 2001 From: Louie S Date: Sun, 6 Nov 2022 14:53:23 -0800 Subject: Restructure repository --- src/cache.c | 77 ++++++++ src/draw.c | 542 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/entry.c | 131 ++++++++++++++ src/group.c | 240 +++++++++++++++++++++++++ src/read_cfg.c | 515 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1505 insertions(+) create mode 100644 src/cache.c create mode 100644 src/draw.c create mode 100644 src/entry.c create mode 100644 src/group.c create mode 100644 src/read_cfg.c (limited to 'src') diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..60a0fa6 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "include/cache.h" +#include "include/read_cfg.h" + +void save_to_cache(int g_hover, int e_hover, int true_hover, char *cfg_name){ + FILE *fp; + struct stat cfg_stat; //for determining last modification time of file + char *path = get_cache_path(true); + + //ensure get_cache_path() did not return NULL + if(path == NULL) return; + + //open cache file for writing + fp = fopen(path, "wb"); + if(fp == NULL){ + printf("Failed to save cache data: could not open \"%s\"\n", path); + return; + } + + //determine last modification time for cfg file + stat(cfg_name, &cfg_stat); + + //write to file + fwrite(cfg_name, sizeof(char), BUF_LEN, fp); + fwrite(&cfg_stat.st_mtime, sizeof(long int), 1, fp); + fwrite(&g_hover, sizeof(int), 1, fp); + fwrite(&e_hover, sizeof(int), 1, fp); + fwrite(&true_hover, sizeof(int), 1, fp); + + fclose(fp); + return; +} + +void load_cache(int *g_hover, int *e_hover, int *true_hover, char *new_cfg_name){ + FILE *fp; + char *path = get_cache_path(false); + char saved_cfg_name[BUF_LEN]; + long int saved_cfg_mtime; + struct stat new_cfg_stat; + + //ensure get_cache_path() did not return NULL + if(path == NULL) return; + + //open cache file for reading + fp = fopen(path, "rb"); + if(fp == NULL){ + printf("Failed to load cached data: could not open \"%s\"\n", path); + return; + } + + //check that cfg_name matches and is not newer than the cached cfg; if not, do not load from cache + fread(saved_cfg_name, sizeof(char), BUF_LEN, fp); + fread(&saved_cfg_mtime, sizeof(long int), 1, fp); + stat(new_cfg_name, &new_cfg_stat); + + if(!(strcmp(saved_cfg_name, new_cfg_name)) && saved_cfg_mtime >= new_cfg_stat.st_mtime){ + fread(g_hover, sizeof(int), 1, fp); + fread(e_hover, sizeof(int), 1, fp); + fread(true_hover, sizeof(int), 1, fp); + } + + else{ + *g_hover = 0; + *e_hover = 0; + *true_hover = 0; + } + + fclose(fp); + return; +} diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..34ac2ce --- /dev/null +++ b/src/draw.c @@ -0,0 +1,542 @@ +#include +#include +#include +#include +#include + +//Windows/Unix Compatability +#if defined _WIN32 || defined _WIN64 +#include +#else +#include +#endif + +#include "include/cache.h" +#include "include/draw.h" +#include "include/entry.h" +#include "include/group.h" +#include "include/read_cfg.h" +#define FLAG_COUNT 3 +#define GAP_SIZE 1 +#define MAX_LEN 6 +#define WIDTH (getmaxx(stdscr)) //width of the entire term +#define HEIGHT (getmaxy(stdscr)) //height of the entire term + +bool *handle_args(int argc, char **argv, char **cfg_path); +void print_help(char *exec_name); +void update_display(bool resize); +void draw_title(); +void draw_win(WINDOW *new, char *title); +void fill_col(int mode); //0 = GROUP, 1 = ENTRY +char *trim_name(char *name, char *path, int max_len); +void update_col(int mode, int hl_where, bool resize); //0 = last, 1 = first; 0 = GROUP, 1 = ENTRY, 2 = INFO +void switch_col(); +void trav_col(int new_i); +int locateChar(char input); + +WINDOW *group_win = NULL; +WINDOW *entry_win = NULL; +WINDOW *info_win = NULL; +int g_hover; +int e_hover; +int true_hover; //0 = hovering on groups, 1 = hovering on entries +GROUP **g; +ENTRY **e; +int g_count; +int e_count; +int g_offset = 0; +int e_offset = 0; + +int main(int argc, char **argv){ + bool *flags_set = NULL; + char *cfg_path = malloc(sizeof(char) * BUF_LEN); + int input; + char full_command[BUF_LEN]; //what will be executed + int prev_width; //used to check if the window was resized + int prev_height; //used to check if the window was resized + int i; + + srand(time(NULL)); + + flags_set = handle_args(argc, argv, &cfg_path); + if(flags_set[1]) return(0); //exit if help flag was passed + if(flags_set[2]) freopen("/dev/null", "w", stdout); //turn off output if quiet flag was passed + if(!flags_set[0]) strcpy(cfg_path, find_config()); //find_config if not config flag was passed + + //Fill Groups + //read the contents of the cfg file; print help message if invalid + if(!cfg_interp(cfg_path)){ + print_help(argv[0]); + return 0; + } + + //Remove Empty Groups from the Array + clean_groups(); + g = get_groups(); //retrieve results of cfg_interp + g_count = get_gcount(g); //retrieve number of groups in g (only do this after removing empty groups) + + //check that there are is at least one valid group + if(g_count == 0){ + printf("Error: No Entries!\n"); + exit(0); + } + + //load cached data + load_cache(&g_hover, &e_hover, &true_hover, cfg_path); + + //reopen stdout for drawing menu + freopen("/dev/tty", "w", stdout); + + initscr(); + cbreak(); + keypad(stdscr, true); + noecho(); + set_escdelay(1); //used to avoid delay after ESC is pressed + start_color(); + + init_pair(0, COLOR_WHITE, COLOR_BLACK); + init_pair(1, COLOR_BLACK, COLOR_WHITE); + init_pair(2, COLOR_BLACK, COLOR_YELLOW); + init_pair(3, COLOR_BLACK, COLOR_GREEN); + attron(COLOR_PAIR(0)); + + prev_width = WIDTH; + prev_height = HEIGHT; + + //update to show menu + update_display(false); + + //update highlighting for loaded location + if(true_hover){ + i = e_hover; + true_hover = 0; + trav_col(g_hover); + switch_col(); + trav_col(i); + } + else trav_col(g_hover); + update_display(true); + + //drawing is done, now run a while loop to receive input (ESC ends this loop) + while(input != 27){ + input = getch(); + + switch(input){ + case 9: //tab key + case KEY_LEFT: + case KEY_RIGHT: + //switch true_hover to look at the other column + switch_col(); + break; + + case KEY_DOWN: + trav_col((true_hover ? e_hover : g_hover)+1); + break; + + case KEY_UP: + trav_col((true_hover ? e_hover : g_hover)-1); + break; + + case KEY_PPAGE: + //case KEY_SUP: + trav_col(0); + break; + + case KEY_NPAGE: + //case KEY_SDOWN: + trav_col((true_hover ? e_count : g_count)-1); + break; + + case KEY_F(3): + //jump to random group/entry + trav_col(rand() % (true_hover ? e_count : g_count)); + break; + + case KEY_F(5): + //manual refresh (for debug purposes) + update_display(true); + break; + + case 10: //enter key + //create a green highlight over the launched entry + mvwchgat(entry_win, 1+e_hover-e_offset, 1, entry_win->_maxx-1, A_DIM, 3, NULL); + wrefresh(entry_win); + + launch(); + break; + + default: //a search char was entered, locate where to jump to + trav_col(locateChar(input)); + } + + //redraw all windows if term is resized + if(prev_width != WIDTH || prev_height != HEIGHT) update_display(true); + + //update prevs + prev_width = WIDTH; + prev_height = HEIGHT; + } + + endwin(); + + //save position data to cache + save_to_cache(g_hover, e_hover, true_hover, cfg_path); + + return 0; +} + +bool *handle_args(int argc, char **argv, char **cfg_path){ + //create bool array with set flags + // 0 -> -c|--config + // 1 -> -h|--help + // 2 -> -q|--quiet + + bool *flags_set = calloc(FLAG_COUNT, sizeof(bool)); + int i; + + for(i = 1; i < argc; ++i){ + //-c + if(!strcmp(argv[i], "-c") || !strcmp(argv[i], "--config")){ + ++i; + if(i < argc){ + strcpy(*cfg_path, argv[i]); + flags_set[0] = true; + } + continue; + } + + //-h + if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")){ + print_help(argv[0]); + flags_set[1] = true; + break; //break rather than continue; program will quit if a help message is requested + } + + //-q + if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet")){ + flags_set[2] = true; + continue; + } + } + + return flags_set; +} + +void print_help(char *exec_name){ + printf("Usage: %s [OPTION] [FILE]\n", exec_name); + printf("Draw an Ncurses Menu to Launch Media from\n\n"); + + printf(" -c, --config Specify a configuration file path\n"); + printf(" -h, --help Print this help message\n"); + printf(" -q, --quiet Suppress stdout messages\n"); +} + +void update_display(bool resize){ + if(WIDTH < 20 || HEIGHT < 6){ + endwin(); + printf("Unable to draw: Terminal Window is Too Small\n"); + exit(0); + } + + //title at the top (Terminal Media Launcher) (23 chars) + draw_title(); + + if(group_win != NULL) delwin(group_win); + if(entry_win != NULL) delwin(entry_win); + if(info_win != NULL) delwin(info_win); + + //Draw Columns + group_win = newwin(HEIGHT-(5+GAP_SIZE), WIDTH/2, 2+GAP_SIZE, 0); + entry_win = newwin(HEIGHT-(5+GAP_SIZE), WIDTH-WIDTH/2, 2+GAP_SIZE, WIDTH/2); + info_win = newwin(3, WIDTH, HEIGHT-3, 0); + draw_win(group_win, "GROUP"); + draw_win(entry_win, "ENTRY"); + draw_win(info_win, "INFO"); + update_col(0, 1, resize); + + //start with hover on the first group, draw the entries from the selected group, true_hover is over the groups (rather than the entries) (do update after first draw, only after subsequent (resize) updates) + if(resize){ + update_col(1, 1, resize); + update_col(2, 0, resize); + } + curs_set(0); //hide the cursor + //move(3, (WIDTH/4)+10); + refresh(); +} + +void draw_title(){ + WINDOW *title; + + title = newwin(2, WIDTH, 0, 0); + refresh(); + attron(A_BOLD | A_UNDERLINE); + move(0, (WIDTH-23)/2); + printw("Terminal Media Launcher"); + attroff(A_BOLD | A_UNDERLINE); + box(title, 0, 0); + wrefresh(title); + + return; +} + +void draw_win(WINDOW *new, char *title){ + int title_len = strlen(title); + + box(new, 0, 0); + attron(A_UNDERLINE); + wmove(new, 0, (new->_maxx - title_len)/2); + wprintw(new, "%s", title); + attroff(A_UNDERLINE); + wrefresh(new); + + return; +} + +void fill_col(int mode){ + //mode 0 = group + //mode 1 = entry + + int i; + WINDOW *col = (mode ? entry_win : group_win); + int count = (mode ? e_count : g_count); + int offset = (mode ? e_offset : g_offset); + int max_len = col->_maxx-1; //longest possible string length that can be displayed in the window + int ycoord = 1; + int max_y = HEIGHT-(6+GAP_SIZE); + char *name; + + for(i = 0+offset; i < count; i++){ + if(ycoord >= max_y) break; //reached the bottom of the terminal window, stop drawing + name = (mode ? get_ename(e[i]) : get_gname(g[i])); + + //the name is too long, take the group to the trimming function + if(strlen(name) > max_len) name = trim_name(name, (mode ? get_epath(e[i]) : get_gname(g[i])), max_len); + wmove(col, ycoord, 1); + wprintw(col, "%s", name); + ycoord++; + } + + wrefresh(col); + return; +} + + +char *trim_name(char *name, char *path, int max_len){ + char *relative; + + //group name and path are equivalent: special procedure + if(!(strcmp(name, path))){ + //find relative path name + relative = strrchr(name, '/'); + name = relative+1; + if(strlen(name) <= max_len) return name; + } + + name[max_len] = '\0'; + return name; +} + +void update_col(int mode, int hl_where, bool resize){ + //mode 0 = group + //mode 1 = entry + //mode 2 = info + + WINDOW *col; + char *name; + int name_len; + int y_hl; + char *execution; + + switch(mode){ + case 0: + col = group_win; + name = "GROUP"; + name_len = 5; + break; + + case 1: + col = entry_win; + name = "ENTRY"; + name_len = 5; + break; + + case 2: + col = info_win; + name = "EXECUTION"; + name_len = 9; + break; + + + default: + return; + } + + y_hl = (hl_where ? 1 : col->_maxy-1); + + //reset the column window (including reboxing and redrawing the title) + wclear(col); + box(col, 0, 0); + wmove(col, 0, (col->_maxx - name_len)/2); + wprintw(col, name); + wrefresh(col); + + //update certain info in the col only if not a resizing-related call + switch(mode){ + case 0: + fill_col(0); + if(!resize) mvwchgat(group_win, y_hl, 1, group_win->_maxx-1, A_DIM, 2, NULL); + else mvwchgat(group_win, 1+g_hover-g_offset, 1, group_win->_maxx-1, A_DIM, (true_hover ? 1 : 2), NULL); + break; + + case 1: + e_count = get_ecount(g[g_hover]); + e = get_entries(get_ghead(g[g_hover]), e_count); + fill_col(1); + if(!resize) mvwchgat(entry_win, y_hl, 1, entry_win->_maxx-1, A_DIM, 1, NULL); + else mvwchgat(entry_win, 1+e_hover-e_offset, 1, entry_win->_maxx-1, A_DIM, (true_hover ? 2 : 1), NULL); + break; + + default: + execution = get_launch(); + if(strlen(execution) >= info_win->_maxx){ + execution[info_win->_maxx - 1] = '\0'; + execution[info_win->_maxx - 2] = '.'; + execution[info_win->_maxx - 3] = '.'; + execution[info_win->_maxx - 4] = '.'; + } + mvwprintw(info_win, 1, 1, execution); + + } + + wrefresh(col); + return; +} + +void switch_col(){ + true_hover = (true_hover+1) % 2; + if(true_hover){ + mvwchgat(group_win, 1+g_hover, 1, group_win->_maxx-1, A_DIM, 1, NULL); //adjust group light + mvwchgat(entry_win, 1+e_hover, 1, entry_win->_maxx-1, A_DIM, 2, NULL); //adjust entry light + } + else{ + mvwchgat(group_win, 1+g_hover, 1, group_win->_maxx-1, A_DIM, 2, NULL); //adjust group light + mvwchgat(entry_win, 1+e_hover, 1, entry_win->_maxx-1, A_DIM, 1, NULL); //adjust entry light + } + move(3, (WIDTH/4)+10); + + wrefresh(group_win); + wrefresh(entry_win); + return; +} + +void trav_col(int new_i){ + int *focus = (true_hover ? &e_hover : &g_hover); //make it easy to know which column we are looking at + int *offset = (true_hover ? &e_offset : &g_offset); + int count = (true_hover ? e_count : g_count); + int max_hl = HEIGHT-(3+GAP_SIZE); //for some reason, this works + int min_hl = 5; + int oob_flag = 0; //0 = none, 1 = bottom, 2 = top + + //check if the traversal is valid (i.e. not at top/bottom), alter if not + if(new_i < 0) return; + if(new_i >= count) new_i = count-1; + + //reset previously highlighted entry and group, change focus + mvwchgat(entry_win, 1+e_hover-e_offset, 1, entry_win->_maxx-1, A_NORMAL, 0, NULL); + mvwchgat(group_win, 1+g_hover-g_offset, 1, group_win->_maxx-1, A_NORMAL, 0, NULL); + *focus = new_i; + + + //check offsets relating to new highlight, make sure highlight did not go oob + while(*focus-*offset+5 > max_hl){ + (*offset)++; + oob_flag = 1; + } + while(*focus-*offset+5 < min_hl){ + (*offset)--; + oob_flag = 2; + } + + if(oob_flag > 0) (true_hover ? update_col(1, oob_flag-1, false) : update_col(0, oob_flag-1, false)); + + //highlight newly hovered upon entry/group + mvwchgat(entry_win, 1+e_hover-e_offset, 1, entry_win->_maxx-1, A_DIM, (true_hover ? 2 : 1), NULL); + mvwchgat(group_win, 1+g_hover-g_offset, 1, group_win->_maxx-1, A_DIM, (true_hover ? 1 : 2), NULL); + if(!true_hover){ //a little extra work regarding group hover + e_offset = 0; + update_col(1, 1, false); + e_hover = 0; + } + + wrefresh(group_win); + wrefresh(entry_win); + update_col(2, 0, false); + return; +} + +int locateChar(char input){ + int location = (true_hover ? e_hover : g_hover); + bool fold_case = get_case_sensitivity(); + char first_char; + int i; + + if(fold_case && input >= 97 && input <= 122) input -= 32; + + if(true_hover){ //hovering on entries + for(i = location+1; i < e_count; i++){ + first_char = get_ename(e[i])[0]; + if(fold_case && first_char >= 97 && first_char <= 122) first_char -= 32; + if(input == first_char){ + location = i; + break; + } + } + } + + else{ //hovering on groups + for(i = location+1; i < g_count; i++){ + first_char = get_gname(g[i])[0]; + if(fold_case && first_char >= 97 && first_char <= 122) first_char -= 32; + if(input == get_gname(g[i])[0]){ + location = i; + break; + } + } + } + + return location; +} + +char *get_launch(){ + char *program = get_gprog(g[g_hover]); + char *flags = get_gflags(g[g_hover]); + char *path = get_epath(e[e_hover]); + bool quotes = get_gquotes(g[g_hover]); + char *full_command = malloc(sizeof(char) * BUF_LEN); + + full_command[0] = '\0'; + + //if the entry is an executable file (doesn't have a launcher) + if(!(strcmp(program, "./"))){ + strcat(full_command, "\""); + strcat(full_command, path); + strcat(full_command, "\""); + } + + else{ + if(quotes) strcat(full_command, "\""); + strcat(full_command, program); + if(quotes) strcat(full_command, "\""); + if(flags[0] !='\0'){ + strcat(full_command, " "); + strcat(full_command, flags); + } + strcat(full_command, " "); + strcat(full_command, "\""); + strcat(full_command, path); + strcat(full_command, "\""); + } + + return full_command; + +} diff --git a/src/entry.c b/src/entry.c new file mode 100644 index 0000000..6dc5699 --- /dev/null +++ b/src/entry.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "include/entry.h" +#include "include/group.h" +#include "include/read_cfg.h" + +typedef struct entry{ + char name[BUF_LEN]; + char path[BUF_LEN]; + bool path_force; + bool hidden; + struct entry *next; +} ENTRY; + +ENTRY *create_entry(char *new_name, char *new_path, bool force){ + ENTRY *new; + + new = malloc(sizeof(ENTRY)); + + strcpy(new->name, new_name); + strcpy(new->path, new_path); + new->path_force = force; + new->hidden = false; + new->next = NULL; + + return new; +} + +void entry_rm(ENTRY *e, ENTRY *prev){ + assert(e != NULL); + if(prev != NULL) prev->next = e->next; //maintain linked structure + free(e); +} + +void clear_entries(ENTRY *head){ + ENTRY *temp; + + while(head != NULL){ + temp = head; + head = head->next; + free(temp); + } + + return; +} + +//returns 0 if in the middle, 1 if new head, 2 if new tail, or 3 if both new head and tail +//TODO this is kind of a stupid way of handling things +int entry_add(ENTRY *head, ENTRY *tail, ENTRY *add){ + assert(add != NULL); + ENTRY *ahead; + + //Empty group (no need to sort) + if(head == NULL) return 3; + + //add is the new tail + if(!get_sort() || strcmp(tail->name, add->name) <= 0){ + tail->next = add; + return 2; + } + + //add is the new head + if(strcmp(add->name, head->name) <= 0){ + add->next = head; + return 1; + } + + ahead = head->next; + + while(ahead != NULL){ + if(strcmp(head->name, add->name) <= 0 && strcmp(add->name, ahead->name) <= 0) break; + head = head->next; + ahead = ahead->next; + } + + head->next = add; + add->next = ahead; + + return 0; +} + +ENTRY **get_entries(ENTRY *head, int count){ + ENTRY **arr = malloc(sizeof(ENTRY *) * count); + ENTRY *trav = head; + int i = 0; + + while(i < count){ + if(!trav->hidden){ + arr[i] = trav; + i++; + } + trav = trav->next; + } + + return arr; +} + +char *get_ename(ENTRY *e){ + assert(e != NULL); + return e->name; +} +char *get_epath(ENTRY *e){ + assert(e != NULL); + return e->path; +} + +bool get_eforce(ENTRY *e){ + assert(e != NULL); + return e->path_force; +} + +void set_hide(ENTRY *e, bool status){ + assert(e != NULL); + e->hidden = true; +} + +void entry_debug(ENTRY *trav){ + + while(trav != NULL){ + printf("%s, \n", trav->name); + trav = trav->next; + } + + return; +} diff --git a/src/group.c b/src/group.c new file mode 100644 index 0000000..0c0d5a0 --- /dev/null +++ b/src/group.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include + +#include "include/entry.h" +#include "include/group.h" +#include "include/read_cfg.h" + +typedef struct group{ + char name[BUF_LEN]; + char program[BUF_LEN]; + char flags[BUF_LEN]; + struct entry *head; + struct entry *tail; + struct group *next; + int entry_count; + bool launcher_quotes; //set by a group option whether or not the launcher should be wrapped by quotes +} GROUP; + +GROUP *groups_head; +GROUP *gp; //pointer to remember last group that was looked at +int group_count = 0; +int total_count = 0; + +GROUP *create_group(char *new_name){ + GROUP *new = malloc(sizeof(GROUP)); + + strcpy(new->name, new_name); //by default, group name is equivalent to the path + strcpy(new->program, "./"); //by default, launch an entry by executing it + new->flags[0] = '\0'; //by default, no command line flags + new->head = NULL; + new->tail = NULL; + new->next = NULL; + new->entry_count = 0; + new->launcher_quotes = true; + + group_count++; + return new; +} + +//add an entry to a group or add a new empty group +//FIXME maybe make this function part of a seperate file to handle a tree (AVL?) +//for now, simple linked list implementation +void group_add(char *gname, ENTRY *addme){ + int i; + GROUP *new; + GROUP *last = NULL; //last element in an existing group list (NULL to start) + + //only adding a new group + if(addme == NULL){ + gp = groups_head; + while(gp != NULL){ + if(!(strcmp(gp->name, gname))){ + printf("config error: %s is already a group!\n", gname); + return; + } + + last = gp; + gp = gp->next; + } + } + + //The previous group is not the same as the new group to add to + if(!(gp != NULL && (!(strcmp(gp->name, gname))))){ + gp = groups_head; + while(gp != NULL){ + //gname matches groups[i]'s name, add entry here + if(!(strcmp(gp->name, gname))) break; + + last = gp; + gp = gp->next; + } + } + + //was unable to find a matching existing group + //need to create new group to insert the entry into + if(gp == NULL){ + new = create_group(gname); + + //first group + if(last == NULL) groups_head = new; + + //add to the end of the groups + else last->next = new; + + gp = new; + } + + //add the entry to the list of entries in the group + if(addme != NULL){ + i = entry_add(gp->head, gp->tail, addme); + switch(i){ + case 1: + gp->head = addme; + break; + + case 2: + gp->tail = addme; + break; + + case 3: + gp->head = addme; + gp->tail = addme; + break; + + } + + gp->entry_count++; + total_count++; + } + + return; +} + +void group_rm(GROUP *g){ + + clear_entries(g->head); + + free(g); + group_count--; + return; +} + +void clean_groups(){ + GROUP *dummy_head; + GROUP *trav; + GROUP *hold; + + if(group_count == 0){ + printf("Error: no groups! "); + refer_to_doc(); + exit(0); + } + + else{ + dummy_head = create_group("dummy"); + dummy_head->next = groups_head; + trav = dummy_head; + + while(trav != NULL){ + //found empty group for removal + if(trav->next != NULL && trav->next->entry_count < 1){ + printf("Omitting empty group \"%s\"\n", trav->next->name); + hold = trav->next; + trav->next = trav->next->next; + group_rm(hold); + } + else trav = trav->next; + } + } + + //ensure groups->head is still correct + groups_head = dummy_head->next; + group_rm(dummy_head); + return; + +} + + +GROUP **get_groups(){ + GROUP **arr = malloc(sizeof(GROUP *) * group_count); + GROUP *trav = groups_head; + int i; + + for(i = 0; i < group_count; i++){ + arr[i] = trav; + trav = trav->next; + } + + return arr; +} + +char *get_gname(GROUP *g){ + assert(g != NULL); + return g->name; +} + +char *get_gprog(GROUP *g){ + assert(g != NULL); + return g->program; +} + +void set_gprog(GROUP *g, char *p){ + assert(g != NULL); + strcpy(g->program, p); + return; +} + +char *get_gflags(GROUP *g){ + assert(g != NULL); + return g->flags; +} + +void set_gflags(GROUP *g, char *p){ + assert(g != NULL); + strcpy(g->flags, p); +} + +ENTRY *get_ghead(GROUP *g){ + assert(g != NULL); + return g->head; +} + +int get_ecount(GROUP *g){ + assert(g != NULL); + return g->entry_count; +} + +void set_ecount(GROUP *g, int new_count){ + assert(g != NULL); + g->entry_count = new_count; +} + +void set_gquotes(GROUP *g, bool b){ + assert(g != NULL); + g->launcher_quotes = b; +} + +bool get_gquotes(GROUP *g){ + return g->launcher_quotes; +} + +int get_gcount(){ + return group_count; +} + +void group_debug(){ + GROUP *trav = groups_head; + + while(trav != NULL){ + entry_debug(trav->head); + printf("\tfrom group %s\n", trav->name); + trav = trav->next; + } + + return; +} diff --git a/src/read_cfg.c b/src/read_cfg.c new file mode 100644 index 0000000..77c4381 --- /dev/null +++ b/src/read_cfg.c @@ -0,0 +1,515 @@ +#include +#include +#include +#include +#include + +/* +#if defined _WIN32 || defined _WIN64 +#include "windows/read_cfg.h" +#else +#include "unix/read_cfg.h" +#endif +*/ + +#include "include/entry.h" +#include "include/group.h" +#include "include/read_cfg.h" +#define MAX_ARGS 5 +#define OPTION_CNT 14 + +//private +void check_line(char *buffer, char **options, int ln); +int check_option(char *arg, char **options); +char *autoAlias(char *path); + +//turn on or off sorting (A-Z); On by default +bool sort = true; + +//set to true to automatically try to create a human readable name for an entry +bool hr = false; + +//turn foldCase (insensitive case searching) on or off; On by default +bool fold_case = true; + +//return false if invalid path +bool cfg_interp(char *path){ + FILE *fp; + char buffer[BUF_LEN]; + GROUP **g; + ENTRY **e; + int count; + int e_count; + int i=0; + int j; + + fp = fopen(path, "r"); + if(fp == NULL){ + printf("Error: Invalid Configuration Path \"%s\"\n", path); + return false; + } + + //build the options array + char **options = malloc(sizeof(char *) * OPTION_CNT); + options[0] = "add"; + options[1] = "addF"; + options[2] = "addGroup"; + options[3] = "addName"; + options[4] = "addNameF"; + options[5] = "addR"; + options[6] = "autoAlias"; + options[7] = "foldCase"; + options[8] = "hide"; + options[9] = "hideFile"; + options[10] = "setFlags"; + options[11] = "setLauncher"; + options[12] = "setLauncherRaw"; + options[13] = "sort"; + + //Read each line of "config" + while(fgets(buffer, BUF_LEN, fp)){ + i++; + check_line(buffer, options, i); + } + + //cleanup + free(options); + + /* + //DEBUG: test to see if the list was added to properly + g = get_groups(); + count = get_gcount(); + for(i = 0; i < count; i++){ + printf("Looking at group %s\n", get_gname(g[i])); + e_count = get_ecount(g[i]); + e = get_entries(get_ghead(g[i]), e_count); + for(j = 0; j < e_count; j++){ + printf("\t%s\n", get_ename(e[j])); + } + } + //END DEBUG + */ + + fclose(fp); + return true; +} + +bool get_sort(){ + return sort; +} + +bool get_case_sensitivity(){ + return fold_case; +} + +void refer_to_doc(){ + printf("Refer to documentation on how to create tml config file\n"); + return; +} + +void addme(char *path, char *group, bool force, char *name){ + ENTRY *new; + char auto_name[BUF_LEN]; + + //check if a name was given as argument + if(name != NULL){ + //strip quotes from the name + name = strip_quotes(name); + new = create_entry(name, path, force); + } + + //check if autoAlias is on. If it is, go to the autoAlias function + else if(hr){ + strcpy(auto_name, autoAlias(path)); + new = create_entry(auto_name, path, force); + } + + else new = create_entry(path, path, force); + if(new != NULL) group_add(group, new); + + return; +} + +int search_ch(char *str, char c){ + int i = 0; + + while(str[i] != '\0'){ + if(str[i] == c) return i; + i++; + } + + return -1; +} + +int search_last_ch(char *str, char c){ + int i = 0; + int last_i = -1; + + while(str[i] != '\0'){ + if(str[i] == c) last_i = i; + i++; + } + + return last_i; +} + +//return 0 if match, 1 if not +//TODO only supports one wildcard per entry +int wild_cmp(char *wild, char *literal){ + int i; + + while(*wild != '\0'){ + //traverse until wildcard + if(*wild != '*'){ + if(*wild != *literal) return 1; + wild++; + literal++; + } + + //found wildcard, find the end of both names and comapre from the back + else{ + i = 0; + wild++; + while(*wild != '\0'){ + i++; + wild++; + } + while(*literal != '\0'){ + literal++; + } + + while(i > 0){ + wild--; + literal--; + if(*wild != *literal) return 1; + i--; + } + + return 0; + } + } + + return 0; +} + + +char *strip_quotes(char *str){ + char *stripped_str = malloc(sizeof(char) * BUF_LEN); + + if(str[0] == '"'){ + stripped_str = &str[1]; + stripped_str[strlen(stripped_str) - 1] = '\0'; + return stripped_str; + } + + return str; +} + +void error_mes(int ln, char *message){ + + assert(message != NULL); + + printf("Configuration File Error:\nOn line %d: %s\n\n", ln, message); + + return; +} + +//TODO add support for "addR" recursive adding (still needs work...) +//TODO add support for "alias" option +//TODO add support for "hide" option +void check_line(char *buffer, char **options, int ln){ + char *delims = " \t\n"; + char *tok = strtok(buffer, delims); + char args[MAX_ARGS][BUF_LEN]; + GROUP **g; + ENTRY **e; + char *tok_p; + char *arg_p; + int g_count; + int e_count; + int search_res; + int i, j; + char *error_p; //helper for complex error messages + + //ensure line is not blank or commented out + if(tok != NULL && tok[0] != '#' && tok[0] != '\0'){ + //initialize args to 0 + for(i = 0; i < MAX_ARGS; i++){ + args[i][0] = '\0'; + } + + i = 0; + //record all arguments in the line + while(tok != NULL){ + if(i >= MAX_ARGS){ + error_mes(ln, "Too many arguments"); + return; + } + strcpy(args[i], tok); + //handle if an argument has spaces and is wrapped in quotes + if(tok[0] == '"'){ + arg_p = &args[i][0]; + tok_p = &tok[1]; + + while(*tok_p != '"'){ + switch(*tok_p){ + + + case '\0': + tok = strtok(NULL, delims); + tok_p = &tok[0]; + *arg_p = ' '; + arg_p++; + break; + + case '\\': + tok_p++; + + default: + *arg_p = *tok_p; + tok_p++; + arg_p++; + + } + } + + *arg_p = '\0'; + + } + + tok = strtok(NULL, delims); + i++; + } + + //optimally check which option was specified + search_res = check_option(args[0], options); + + switch(search_res){ + + case 0: //add + //add entry(ies) to a group: first arg is the file(s), second arg is the group to add to + //TODO add sorting functionality + handle_fname(args[1], args[2], 0, 0, NULL, ln); + break; + + case 1: //addF + //force add entry to a group: first arg is the file(s), second arg is the group to add to + handle_fname(args[1], args[2], 0, 1, NULL, ln); + break; + + case 2: //addGroup + //create a new group + group_add(strip_quotes(args[1]), NULL); + break; + + case 3: //addName + //add entry to a group: first arg is the name, second arg is the file, and third arg is the group to add to + handle_fname(args[2], args[3], 0, 0, args[1], ln); + break; + + case 4: //addNameF + //same as addName, but with force on + handle_fname(args[2], args[3], 0, 1, args[1], ln); + break; + + case 5: //addR + //recursively add: that is, also search directories in the given path + //NOTE: experimental + handle_fname(args[1], args[2], 1, 0, NULL, ln); + break; + + case 6: //autoAlias + if(!(strcmp(args[1], "on"))) hr = true; + else if(!(strcmp(args[1], "off"))) hr = false; + break; + + case 7: //foldCase (case insensitive) + if(!(strcmp(args[1], "on"))) fold_case = true; + else if(!(strcmp(args[1], "off"))) fold_case = false; + break; + + //TODO consider having this call handle_fname instead so that '*' can be used + case 8: //hide + case 9: //hideFile + //args[2] is referring to a group + g = get_groups(); + g_count = get_gcount(); + + //look for matching existing group + for(i = 0; i < g_count; i++){ + if(!(strcmp(get_gname(g[i]), args[2]))) break; + } + + if(i < g_count){ + e_count = get_ecount(g[i]); + e = get_entries(get_ghead(g[i]), e_count); + + for(j = 0; j < e_count; j++){ + if(!strcmp((search_res == 8 ? get_ename(e[j]) : get_epath(e[j])), strip_quotes(args[1]))) break; + } + + if(j < e_count){ + set_hide(e[j], true); + set_ecount(g[i], get_ecount(g[i])-1); + } + else{ + error_p = malloc(sizeof(char) * 1024); + sprintf(error_p, "Entry \"%s\" does not exist", args[1]); + error_mes(ln, error_p); + free(error_p); + } + } + + else{ + error_p = malloc(sizeof(char) * 1024); + sprintf(error_p, "Group \"%s\" does not exist", args[2]); + error_mes(ln, error_p); + free(error_p); + } + break; + + case 10: //setFlags + //args[1] is referring to a group + g = get_groups(); + g_count = get_gcount(); + + //look for matching existing group + for(i = 0; i < g_count; i++){ + if(!(strcmp(get_gname(g[i]), args[1]))) break; + } + + //set a group's launcher flags (like ./program -f file for fullscreen) + //assert that a matching group was found + if(i < g_count) set_gflags(g[i], strip_quotes(args[2])); + else{ + error_p = malloc(sizeof(char) * 1024); + sprintf(error_p, "Group \"%s\" does not exist", args[1]); + error_mes(ln, error_p); + free(error_p); + } + break; + + case 11: //setLauncher + case 12: //setLauncherRaw + //args[1] is referring to a group + g = get_groups(); + g_count = get_gcount(); + + //look for matching existing group + for(i = 0; i < g_count; i++){ + if(!(strcmp(get_gname(g[i]), args[1]))) break; + } + + //set a group's launcher (this requires pulling down the existing groups and finding the one that args[1] mentions) + //assert that a matching group was found + if(i < g_count){ + set_gprog(g[i], strip_quotes(args[2])); + if(search_res == 12) set_gquotes(g[i], false); //FIXME don't forget to change this line if adding more options!!! + } + else{ + error_p = malloc(sizeof(char) * 1024); + sprintf(error_p, "Group \"%s\" does not exist", args[1]); + error_mes(ln, error_p); + free(error_p); + } + break; + + case 13: //sort + if(!(strcmp(args[1], "on"))) sort = true; + else if(!(strcmp(args[1], "off"))) sort = false; + break; + + default: + error_p = malloc(sizeof(char) * 1024); + sprintf(error_p, "Unknown config option \"%s\"", args[0]); + error_mes(ln, error_p); + free(error_p); + + } + + } + + return; +} + +int check_option(char *arg, char **options){ + int min = 0; + int max = OPTION_CNT-1; + int hover; + int comp_res; + + while(max - min > 1){ + hover = min + (max-min)/2; + comp_res = strcmp(arg, options[hover]); + + if(comp_res > 0) min = hover; + else if(comp_res < 0) max = hover; + else return hover; + } + + if(max == OPTION_CNT-1 && strcmp(arg, options[max]) == 0) return max; + else if(min == 0 && strcmp(arg, options[min]) == 0) return min; + + return -1; +} + + +char *autoAlias(char *path){ + char *hr_name = malloc(sizeof(char) * BUF_LEN); + char *p = hr_name; + char *rpath; //necessary so as not to touch the actual path + char *last_dot = NULL; //used to trim the file extension (if there is one) + bool stop = false; //stop when you don't want to add a series of chars to the output + + //get to the relative path name + rpath = strrchr(path, sep); + if(rpath == NULL) rpath = path; + else rpath++; + + while(*rpath != '\0'){ + switch(*rpath){ + case '(': + stop = true; + break; + + case ')': + stop = false; + break; + + case '-': + case '_': + if(*(p-1) != ' ' && !stop){ + *p = ' '; + *p++; + } + break; + + case ' ': + if(*(p-1) != ' ' && !stop){ + *p = *rpath; + *p++; + } + break; + + case '.': + last_dot = p; + + default: + if(!stop){ + *p = *rpath; + *p++; + } + } + *rpath++; + } + + //close the name + if(last_dot != NULL) *last_dot = '\0'; + else if(*path == '"') *(p-1) = '\0'; //close early to avoid including closing quote + else *p = '\0'; + + return hr_name; +} + + + -- cgit