summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLouie S <louie@example.com>2022-11-06 14:53:23 -0800
committerLouie S <louie@example.com>2022-11-06 14:53:23 -0800
commitcdea6b0e8c1e51c9962f73e183a3bd72ed63b40f (patch)
tree1e9f4261acfe48c0ef37dd64f2a589d1e13b89b1 /src
parente58f35f4580ad4377c3ba5dcaee5bbbd938713c6 (diff)
Restructure repository
Diffstat (limited to 'src')
-rw-r--r--src/cache.c77
-rw-r--r--src/draw.c542
-rw-r--r--src/entry.c131
-rw-r--r--src/group.c240
-rw-r--r--src/read_cfg.c515
5 files changed, 1505 insertions, 0 deletions
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 <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+//Windows/Unix Compatability
+#if defined _WIN32 || defined _WIN64
+#include <ncurses/ncurses.h>
+#else
+#include <ncurses.h>
+#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 <assert.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+#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;
+}
+
+
+