tmus

TUI Music Player
git clone https://git.sinitax.com/sinitax/tmus
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

commit 72de33c4f15144e7c597fad850510dd7da88a0f2
parent 12b6c9dfd009352e0bb7f8395abd3678e3bd6265
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 14 Feb 2022 00:28:11 +0100

Refactored main.c into many files

Diffstat:
Asrc/cmd.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cmd.h | 18++++++++++++++++++
Asrc/data.c | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/data.h | 24++++++++++++++++++++++++
Msrc/log.c | 22+++++++++++-----------
Msrc/log.h | 8++++----
Msrc/main.c | 1384+------------------------------------------------------------------------------
Msrc/mpris.c | 80++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/mpris.h | 9++++++---
Msrc/pane.h | 4+++-
Msrc/player.c | 65+++++++++++++++++++++++++++++++++--------------------------------
Msrc/player.h | 3++-
Asrc/style.c | 39+++++++++++++++++++++++++++++++++++++++
Asrc/style.h | 27+++++++++++++++++++++++++++
Msrc/track.c | 2+-
Msrc/track.h | 2+-
Asrc/tui.c | 937+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tui.h | 25+++++++++++++++++++++++++
Msrc/util.c | 11+++++++----
19 files changed, 1597 insertions(+), 1468 deletions(-)

diff --git a/src/cmd.c b/src/cmd.c @@ -0,0 +1,99 @@ +#include "cmd.h" + +#include "data.h" +#include "list.h" +#include "player.h" +#include "ref.h" +#include "track.h" +#include "tui.h" +#include "util.h" + +#include <stdbool.h> + +#define CMD_ERROR(...) do { \ + free(cmd_status); \ + cmd_status = awprintf(__VA_ARGS__); \ + return false; \ + } while (0) + +static bool cmd_save(const wchar_t *args); +static bool cmd_move(const wchar_t *args); +static bool cmd_add(const wchar_t *args); + +const struct cmd commands[] = { + { L"save", cmd_save }, + { L"move", cmd_move }, + { L"add", cmd_add }, +}; + +const size_t command_count = ARRLEN(commands); + +wchar_t *cmd_status; + +void +cmd_init(void) +{ + cmd_status = NULL; +} + +bool +cmd_save(const wchar_t *args) +{ + data_save(); + return 0; +} + +bool +cmd_move(const wchar_t *name) +{ + struct link *link; + struct track *track; + struct tag *tag; + char *newpath; + + tag = tag_find(name); + if (!tag) CMD_ERROR(L"Tag not found"); + + link = list_at(tracks_vis, track_nav.sel); + if (!link) CMD_ERROR(L"No track selected"); + track = UPCAST(link, struct ref)->data; + + newpath = aprintf("%s/%s", tag->fpath, track->fname); + ASSERT(newpath != NULL); + move_file(track->fpath, newpath); + free(track->fpath); + track->fpath = newpath; + + link_pop(link); + list_push_back(&tag->tracks, link); + + return 1; +} + +bool +cmd_add(const wchar_t *name) +{ + struct link *link; + struct track *track; + struct ref *ref; + struct tag *tag; + char *newpath; + + tag = tag_find(name); + if (!tag) return 0; + + link = list_at(&player->playlist, track_nav.sel); + if (!link) return 0; + track = UPCAST(link, struct ref)->data; + + newpath = aprintf("%s/%s", tag->fpath, track->fname); + ASSERT(newpath != NULL); + copy_file(track->fpath, newpath); + track->fpath = newpath; + + track = track_alloc(tag->fpath, track->fname, get_fid(tag->fpath)); + ref = ref_init(track); + list_push_back(&tag->tracks, &ref->link); + + return 1; +} diff --git a/src/cmd.h b/src/cmd.h @@ -0,0 +1,18 @@ +#pragma once + +#include <wchar.h> + +#include <stdbool.h> + +typedef bool (*cmd_func)(const wchar_t *args); + +struct cmd { + const wchar_t *name; + cmd_func func; +}; + +void cmd_init(void); + +extern const struct cmd commands[]; +extern const size_t command_count; +extern wchar_t *cmd_status; diff --git a/src/data.c b/src/data.c @@ -0,0 +1,306 @@ +#include "data.h" + +#include "list.h" +#include "log.h" +#include "ref.h" +#include "track.h" +#include "tag.h" + +#include <dirent.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <stdbool.h> +#include <string.h> + +const char *datadir; + +struct list tracks; +struct list tags; +struct list tags_sel; + +void +data_load(void) +{ + struct dirent *ent; + struct tag *tag; + struct stat st; + char *path; + DIR *dir; + + list_init(&tracks); + list_init(&tags); + list_init(&tags_sel); + + datadir = getenv("TMUS_DATA"); + ASSERT(datadir != NULL); + + dir = opendir(datadir); + ASSERT(dir != NULL); + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".")) + continue; + if (!strcmp(ent->d_name, "..")) + continue; + + path = aprintf("%s/%s", datadir, ent->d_name); + ASSERT(path != NULL); + + if (!stat(path, &st) && S_ISDIR(st.st_mode)) { + tag = tag_init(datadir, ent->d_name); + tracks_load(tag); + list_push_back(&tags, LINK(tag)); + } + + free(path); + } + closedir(dir); + + ASSERT(!list_empty(&tags)); +} + +void +data_save(void) +{ + struct link *iter; + struct tag *tag; + + for (LIST_ITER(&tags, iter)) { + tag = UPCAST(iter, struct tag); + tracks_save(tag); + } +} + +void +data_free(void) +{ + struct link *track_link; + struct link *tag_link; + struct tag *tag; + + log_info("MAIN: data_free()\n"); + + refs_free(&tracks); + refs_free(&tags_sel); + + while (!list_empty(&tags)) { + tag_link = list_pop_front(&tags); + + tag = UPCAST(tag_link, struct tag); + while (!list_empty(&tag->tracks)) { + track_link = list_pop_front(&tag->tracks); + track_free(UPCAST(track_link, struct ref)->data); + ref_free(UPCAST(track_link, struct ref)); + } + tag_free(tag); + } +} + +int +get_fid(const char *path) +{ + struct stat st; + return stat(path, &st) ? -1 : st.st_ino; +} + +void +index_update(struct tag *tag) +{ + struct track *track, *track_iter; + struct dirent *ent; + struct link *iter; + struct ref *ref; + struct stat st; + char *path; + FILE *file; + DIR *dir; + int fid; + + path = aprintf("%s/index", tag->fpath); + ASSERT(path != NULL); + + file = fopen(path, "w+"); + ASSERT(file != NULL); + free(path); + + dir = opendir(tag->fpath); + ASSERT(dir != NULL); + + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".")) + continue; + if (!strcmp(ent->d_name, "..")) + continue; + if (!strcmp(ent->d_name, "index")) + continue; + + /* skip files without extension */ + if (!strchr(ent->d_name + 1, '.')) + continue; + + path = aprintf("%s/%s", tag->fpath, ent->d_name); + ASSERT(path != NULL); + fid = get_fid(path); + free(path); + + fprintf(file, "%i:%s\n", fid, ent->d_name); + } + + closedir(dir); + fclose(file); +} + +void +tracks_load(struct tag *tag) +{ + char linebuf[1024]; + struct link *link; + struct track *track; + struct track *track2; + struct ref *ref; + char *index_path; + char *track_name, *sep; + int track_fid; + bool new_track; + FILE *file; + + printf("Loading files from %s", tag->fpath); + fflush(stdout); + + index_path = aprintf("%s/index", tag->fpath); + ASSERT(index_path != NULL); + + file = fopen(index_path, "r"); + if (file == NULL) { + index_update(tag); + file = fopen(index_path, "r"); + ASSERT(file != NULL); + } + + while (fgets(linebuf, sizeof(linebuf), file)) { + sep = strchr(linebuf, '\n'); + if (sep) *sep = '\0'; + + sep = strchr(linebuf, ':'); + ASSERT(sep != NULL); + *sep = '\0'; + + track_fid = atoi(linebuf); + track_name = sep + 1; + track = track_alloc(tag->fpath, track_name, track_fid); + + ref = ref_init(tag); + ASSERT(ref != NULL); + list_push_back(&track->tags, LINK(ref)); + + ref = ref_init(track); + ASSERT(ref != NULL); + list_push_back(&tag->tracks, LINK(ref)); + + new_track = true; + for (LIST_ITER(&tracks, link)) { + track2 = UPCAST(link, struct ref)->data; + if (track->fid > 0 && track->fid == track2->fid) + new_track = false; + } + + if (new_track) { + ref = ref_init(track); + ASSERT(ref != NULL); + list_push_back(&tracks, LINK(ref)); + } + } + + /* clear line and reset cursor */ + printf("\x1b[0K\r"); + + fclose(file); + free(index_path); +} + +void +tracks_save(struct tag *tag) +{ + struct track *track; + struct link *link; + char *index_path; + FILE *file; + + /* write playlist back to index file */ + + printf("Saving tracks to %s", tag->fpath); + + index_path = aprintf("%s/index", tag->fpath); + ASSERT(index != NULL); + + file = fopen(index_path, "w+"); + ASSERT(file != NULL); + + for (LIST_ITER(&tag->tracks, link)) { + track = UPCAST(link, struct ref)->data; + fprintf(file, "%i:%s\n", track->fid, track->fname); + } + + /* clear line and reset cursor */ + printf("\x1b[0K\r"); + + fclose(file); + free(index_path); +} + +void +rm_file(const char *path) +{ + ASSERT(unlink(path) == 0); +} + +void +copy_file(const char *src, const char *dst) +{ + FILE *in, *out; + char buf[4096]; + int len, nread; + + in = fopen(src, "r"); + if (in == NULL) + PANIC("Failed to open file %s", src); + + out = fopen(dst, "w+"); + if (out == NULL) + PANIC("Failed to open file %s", dst); + + while ((nread = fread(buf, 1, sizeof(buf), in)) > 0) { + fwrite(buf, 1, nread, out); + } + + if (nread < 0) + PANIC("Copy failed!", src, dst); + + fclose(in); + fclose(out); +} + +void +move_file(const char *src, const char *dst) +{ + copy_file(src, dst); + rm_file(src); +} + +struct tag * +tag_find(const wchar_t *query) +{ + struct link *iter; + struct tag *tag; + + for (LIST_ITER(&tags, iter)) { + tag = UPCAST(iter, struct tag); + if (!wcscmp(tag->name, query)) { + return tag; + } + } + + return NULL; +} + + diff --git a/src/data.h b/src/data.h @@ -0,0 +1,24 @@ +#pragma once + +#include "tag.h" + +void data_load(void); +void data_save(void); +void data_free(void); + +int get_fid(const char *path); +void index_update(struct tag *tag); +void tracks_load(struct tag *tag); +void tracks_save(struct tag *tag); + +void rm_file(const char *path); +void copy_file(const char *dst, const char *src); +void move_file(const char *dst, const char *src); + +struct tag *tag_find(const wchar_t *query); + +extern const char *datadir; + +extern struct list tracks; +extern struct list tags; +extern struct list tags_sel; diff --git a/src/log.c b/src/log.c @@ -4,8 +4,8 @@ #include <stdbool.h> #include <stdlib.h> -int log_active; -FILE *log_file; +static int log_active; +static FILE *log_file; void log_init(void) @@ -24,6 +24,15 @@ log_init(void) } void +log_deinit(void) +{ + if (!log_active) return; + + fclose(log_file); + log_active = 0; +} + +void log_info(const char *fmtstr, ...) { va_list ap; @@ -37,12 +46,3 @@ log_info(const char *fmtstr, ...) fflush(log_file); } -void -log_end(void) -{ - if (!log_active) return; - - fclose(log_file); - log_active = 0; -} - diff --git a/src/log.h b/src/log.h @@ -1,9 +1,9 @@ +#pragma once + #include <stdarg.h> #include <stdio.h> void log_init(void); -void log_info(const char *fmtstr, ...); -void log_end(void); +void log_deinit(void); -extern int log_active; -extern FILE *log_file; +void log_info(const char *fmtstr, ...); diff --git a/src/main.c b/src/main.c @@ -1,203 +1,27 @@ -#define _XOPEN_SOURCE 600 -#define _DEFAULT_SOURCE - -#include "util.h" +#include "data.h" +#include "history.h" #include "list.h" +#include "listnav.h" #include "log.h" #include "mpris.h" -#include "history.h" -#include "tag.h" #include "pane.h" #include "player.h" -#include "listnav.h" #include "ref.h" +#include "style.h" +#include "tag.h" +#include "tui.h" +#include "util.h" -#include "mpd/player.h" -#include "curses.h" - -#include <dirent.h> -#include <fcntl.h> #include <locale.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/ioctl.h> -#include <sys/wait.h> -#include <time.h> -#include <unistd.h> -#include <wchar.h> -#include <wctype.h> - -#undef KEY_ENTER -#define KEY_ENTER '\n' -#define KEY_SPACE ' ' -#define KEY_ESC '\x1b' -#define KEY_TAB '\t' -#define KEY_CTRL(c) ((c) & ~0x60) - -#define ATTR_ON(win, attr) wattr_on(win, attr, NULL) -#define ATTR_OFF(win, attr) wattr_off(win, attr, NULL) - -#define CMD_ERROR(...) do { \ - free(cmd_status); \ - cmd_status = awprintf(__VA_ARGS__); \ - return false; \ - } while (0) - -enum { - STYLE_DEFAULT, - STYLE_TITLE, - STYLE_PANE_SEP, - STYLE_ITEM_SEL, - STYLE_ITEM_HOVER, - STYLE_ITEM_HOVER_SEL, - STYLE_PREV, - STYLE_ERROR, - STYLE_COUNT -}; - -enum { - IMODE_EXECUTE, - IMODE_TRACK_SEARCH, - IMODE_TAG_SEARCH, - IMODE_COUNT -}; - -typedef wchar_t *(*completion_gen)(const wchar_t *text, int fwd, int state); -typedef int (*cmd_handler)(const wchar_t *args); - -struct cmd { - const wchar_t *name; - cmd_handler func; -}; - -static const char player_state_chars[] = { - [PLAYER_STATE_PAUSED] = '|', - [PLAYER_STATE_PLAYING] = '>', - [PLAYER_STATE_STOPPED] = '#' -}; - -static int style_attrs[STYLE_COUNT]; - -static const char *datadir; -static int scrw, scrh; -static int quit; - -static struct pane *pane_sel, *pane_top_sel; -static struct pane pane_left, pane_right, pane_bot; -static struct pane *const panes[] = { - &pane_left, - &pane_right, - &pane_bot -}; - -/* 'tracks' holds all *unique* tracks */ -static struct list tracks; -static struct list tags; -static struct list tags_sel; -static struct list *tracks_vis; -static int track_show_playlist; - -/* bottom 'cmd' pane for search / exec */ -static struct pane *cmd_pane; -static struct history command_history; -static struct history track_search_history; -static struct history tag_search_history; -static struct history *history; -static int cmd_show, cmd_mode; -static struct inputln completion_query; -static int completion_reset; -static completion_gen completion; -static wchar_t *cmd_status; - -/* left pane for tags */ -static struct pane *tag_pane; -static struct listnav tag_nav; - -/* right pane for tracks */ -static struct pane *track_pane; -static struct listnav track_nav; static void init(void); static void cleanup(int code, void *arg); -static void tui_init(void); -static void tui_ncurses_init(void); -static void tui_resize(void); -static void tui_end(void); - -static void data_load(void); -static void data_save(void); -static void data_free(void); - -static int get_fid(const char *path); -static void index_update(struct tag *tag); -static void tracks_load(struct tag *tag); -static void tracks_save(struct tag *tag); - -static void copy_file(const char *dst, const char *src); -static void move_file(const char *dst, const char *src); - -static struct tag *tag_find(const wchar_t *query); -static void pane_title(struct pane *pane, const char *title, int highlight); - -static void style_init(int style, int fg, int bg, int attr); -static void style_on(WINDOW *win, int style); -static void style_off(WINDOW *win, int style); - -static wchar_t *command_name_gen(const wchar_t *text, int fwd, int state); -static wchar_t *track_name_gen(const wchar_t *text, int fwd, int state); - -static void toggle_current_tag(void); -static int tag_pane_input(wint_t c); -static void tag_pane_vis(struct pane *pane, int sel); - -static int track_pane_input(wint_t c); -static void track_pane_vis(struct pane *pane, int sel); - -static int play_track(const wchar_t *name); -static int select_tag(const wchar_t *name); -static int cmd_pane_input(wint_t c); -static void cmd_pane_vis(struct pane *pane, int sel); - -static void queue_hover(void); -static void update_track_playlist(void); -static void main_input(wint_t c); -static void main_vis(void); - -static int cmd_save(const wchar_t *args); -static int cmd_move(const wchar_t *args); -static int cmd_add(const wchar_t *args); - -const char imode_prefix[IMODE_COUNT] = { - [IMODE_EXECUTE] = ':', - [IMODE_TRACK_SEARCH] = '/', - [IMODE_TAG_SEARCH] = '?', -}; - -const struct cmd cmds[] = { - { L"save", cmd_save }, - { L"move", cmd_move }, - { L"add", cmd_move }, -}; - void init(void) { setlocale(LC_ALL, ""); srand(time(NULL)); - quit = 0; - - inputln_init(&completion_query); - completion_reset = 1; - - history_init(&track_search_history); - history_init(&tag_search_history); - history_init(&command_history); - history = &command_history; log_init(); @@ -209,14 +33,6 @@ init(void) dbus_init(); - listnav_init(&tag_nav); - listnav_init(&track_nav); - - track_show_playlist = 0; - update_track_playlist(); - - cmd_status = NULL; - on_exit(cleanup, NULL); signal(SIGINT, exit); } @@ -224,1202 +40,28 @@ init(void) void cleanup(int exitcode, void* arg) { - if (!exitcode) { - tui_end(); - } else { - curs_set(1); - } + tui_restore(); data_save(); data_free(); - player_free(); - - dbus_end(); - - log_end(); - - history_free(&track_search_history); - history_free(&tag_search_history); - history_free(&command_history); -} - -void -tui_init(void) -{ - tui_ncurses_init(); - - memset(style_attrs, 0, sizeof(style_attrs)); - style_init(STYLE_DEFAULT, COLOR_WHITE, COLOR_BLACK, 0); - style_init(STYLE_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD); - style_init(STYLE_PANE_SEP, COLOR_BLUE, COLOR_BLACK, 0); - style_init(STYLE_ITEM_SEL, COLOR_YELLOW, COLOR_BLACK, A_BOLD); - style_init(STYLE_ITEM_HOVER, COLOR_WHITE, COLOR_BLUE, 0); - style_init(STYLE_ITEM_HOVER_SEL, COLOR_YELLOW, COLOR_BLUE, A_BOLD); - style_init(STYLE_ERROR, COLOR_RED, COLOR_BLACK, 0); - style_init(STYLE_PREV, COLOR_WHITE, COLOR_BLACK, A_DIM); - - pane_init((tag_pane = &pane_left), tag_pane_input, tag_pane_vis); - pane_init((track_pane = &pane_right), track_pane_input, track_pane_vis); - pane_init((cmd_pane = &pane_bot), cmd_pane_input, cmd_pane_vis); - - pane_sel = &pane_left; - pane_top_sel = pane_sel; -} - -void -tui_ncurses_init(void) -{ - initscr(); - - /* do most of the handling ourselves, - * enable special keys */ - raw(); - noecho(); - keypad(stdscr, TRUE); - - /* update screen occasionally for things like - * time even when no input was received */ - halfdelay(1); - - /* inits COLOR and COLOR_PAIRS used by styles */ - start_color(); - - /* dont show cursor */ - curs_set(0); - - /* we use ESC deselecting the current pane - * and not for escape sequences, so dont wait */ - ESCDELAY = 0; -} - -void -tui_resize(void) -{ - struct link *iter; - struct tag *tag; - int i, leftw; - - getmaxyx(stdscr, scrh, scrw); - - /* guarantee a minimum terminal size */ - while (scrw < 10 || scrh < 4) { - clear(); - refresh(); - usleep(10000); - getmaxyx(stdscr, scrh, scrw); - } - - /* adjust tag pane width to name lengths */ - leftw = 0; - for (LIST_ITER(&tags, iter)) { - tag = UPCAST(iter, struct tag); - leftw = MAX(leftw, wcslen(tag->name)); - } - leftw = MAX(leftw + 1, 0.2f * scrw); - - pane_resize(&pane_left, 0, 0, leftw, scrh - 3); - pane_resize(&pane_right, pane_left.ex + 1, 0, scrw, scrh - 3); - pane_resize(&pane_bot, 0, scrh - 3, scrw, scrh); -} - -void -tui_end(void) -{ - pane_free(&pane_left); - pane_free(&pane_right); - pane_free(&pane_bot); - - if (!isendwin()) endwin(); -} - -void -data_load(void) -{ - struct dirent *ent; - struct tag *tag; - struct stat st; - char *path; - DIR *dir; - - list_init(&tracks); - list_init(&tags); - list_init(&tags_sel); - - datadir = getenv("TMUS_DATA"); - ASSERT(datadir != NULL); - - dir = opendir(datadir); - ASSERT(dir != NULL); - while ((ent = readdir(dir))) { - if (!strcmp(ent->d_name, ".")) - continue; - if (!strcmp(ent->d_name, "..")) - continue; - - path = aprintf("%s/%s", datadir, ent->d_name); - ASSERT(path != NULL); - - if (!stat(path, &st) && S_ISDIR(st.st_mode)) { - tag = tag_init(datadir, ent->d_name); - tracks_load(tag); - list_push_back(&tags, LINK(tag)); - } - - free(path); - } - closedir(dir); - - ASSERT(!list_empty(&tags)); -} - -void -data_save(void) -{ - struct link *iter; - struct tag *tag; - - for (LIST_ITER(&tags, iter)) { - tag = UPCAST(iter, struct tag); - tracks_save(tag); - } -} - -void -data_free(void) -{ - struct link *track_link; - struct link *tag_link; - struct tag *tag; - - log_info("MAIN: data_free()\n"); - - refs_free(&tracks); - refs_free(&tags_sel); - - while (!list_empty(&tags)) { - tag_link = list_pop_front(&tags); - - tag = UPCAST(tag_link, struct tag); - while (!list_empty(&tag->tracks)) { - track_link = list_pop_front(&tag->tracks); - track_free(UPCAST(track_link, struct ref)->data); - ref_free(UPCAST(track_link, struct ref)); - } - tag_free(tag); - } -} - -int -get_fid(const char *path) -{ - struct stat st; - return stat(path, &st) ? -1 : st.st_ino; -} - -void -index_update(struct tag *tag) -{ - struct track *track, *track_iter; - struct dirent *ent; - struct link *iter; - struct ref *ref; - struct stat st; - char *path; - FILE *file; - DIR *dir; - int fid; - - path = aprintf("%s/index", tag->fpath); - ASSERT(path != NULL); - - file = fopen(path, "w+"); - ASSERT(file != NULL); - free(path); - - dir = opendir(tag->fpath); - ASSERT(dir != NULL); - - while ((ent = readdir(dir))) { - if (!strcmp(ent->d_name, ".")) - continue; - if (!strcmp(ent->d_name, "..")) - continue; - if (!strcmp(ent->d_name, "index")) - continue; - - /* skip files without extension */ - if (!strchr(ent->d_name + 1, '.')) - continue; - - path = aprintf("%s/%s", tag->fpath, ent->d_name); - ASSERT(path != NULL); - fid = get_fid(path); - free(path); - - fprintf(file, "%i:%s\n", fid, ent->d_name); - } - - closedir(dir); - fclose(file); -} - -void -tracks_load(struct tag *tag) -{ - char linebuf[1024]; - struct link *link; - struct track *track; - struct track *track2; - struct ref *ref; - char *index_path; - char *track_name, *sep; - int track_fid; - bool new_track; - FILE *file; - - printf("Loading files from %s", tag->fpath); - fflush(stdout); - - index_path = aprintf("%s/index", tag->fpath); - ASSERT(index_path != NULL); - - file = fopen(index_path, "r"); - if (file == NULL) { - index_update(tag); - file = fopen(index_path, "r"); - ASSERT(file != NULL); - } - - while (fgets(linebuf, sizeof(linebuf), file)) { - sep = strchr(linebuf, '\n'); - if (sep) *sep = '\0'; - - sep = strchr(linebuf, ':'); - ASSERT(sep != NULL); - *sep = '\0'; - - track_fid = atoi(linebuf); - track_name = sep + 1; - track = track_init(tag->fpath, track_name, track_fid); - - ref = ref_init(tag); - ASSERT(ref != NULL); - list_push_back(&track->tags, LINK(ref)); - - ref = ref_init(track); - ASSERT(ref != NULL); - list_push_back(&tag->tracks, LINK(ref)); - - new_track = true; - for (LIST_ITER(&tracks, link)) { - track2 = UPCAST(link, struct ref)->data; - if (track->fid > 0 && track->fid == track2->fid) - new_track = false; - } - - if (new_track) { - ref = ref_init(track); - ASSERT(ref != NULL); - list_push_back(&tracks, LINK(ref)); - } - } - - /* clear line and reset cursor */ - printf("\x1b[0K\r"); - - fclose(file); - free(index_path); -} - -void -tracks_save(struct tag *tag) -{ - struct track *track; - struct link *link; - char *index_path; - FILE *file; - - /* write playlist back to index file */ - - printf("Saving tracks to %s", tag->fpath); - - index_path = aprintf("%s/index", tag->fpath); - ASSERT(index != NULL); - - file = fopen(index_path, "w+"); - ASSERT(file != NULL); - - for (LIST_ITER(&tag->tracks, link)) { - track = UPCAST(link, struct ref)->data; - fprintf(file, "%i:%s\n", track->fid, track->fname); - } - - /* clear line and reset cursor */ - printf("\x1b[0K\r"); - - fclose(file); - free(index_path); -} - -void -rm_file(const char *path) -{ - ASSERT(unlink(path) == 0); -} - -void -copy_file(const char *src, const char *dst) -{ - FILE *in, *out; - char buf[4096]; - int len, nread; - - in = fopen(src, "r"); - if (in == NULL) - PANIC("Failed to open file %s", src); - - out = fopen(dst, "w+"); - if (out == NULL) - PANIC("Failed to open file %s", dst); - - while ((nread = fread(buf, 1, sizeof(buf), in)) > 0) { - fwrite(buf, 1, nread, out); - } - - if (nread < 0) - PANIC("Copy failed!", src, dst); - - fclose(in); - fclose(out); -} - -void -move_file(const char *src, const char *dst) -{ - copy_file(src, dst); - rm_file(src); -} - -struct tag * -tag_find(const wchar_t *query) -{ - struct link *iter; - struct tag *tag; - - for (LIST_ITER(&tags, iter)) { - tag = UPCAST(iter, struct tag); - if (!wcscmp(tag->name, query)) { - return tag; - } - } - - return NULL; -} - -void -pane_title(struct pane *pane, const char *title, int highlight) -{ - wmove(pane->win, 0, 0); - - style_on(pane->win, STYLE_TITLE); - if (highlight) ATTR_ON(pane->win, A_STANDOUT); - - wprintw(pane->win, " %-*.*s", pane->w - 1, pane->w - 1, title); + player_deinit(); - if (highlight) ATTR_OFF(pane->win, A_STANDOUT); - style_off(pane->win, STYLE_TITLE); -} - -void -style_init(int style, int fg, int bg, int attr) -{ - style_attrs[style] = attr; - init_pair(style, fg, bg); -} - -void -style_on(WINDOW *win, int style) -{ - ATTR_ON(win, COLOR_PAIR(style) | style_attrs[style]); -} - -void -style_off(WINDOW *win, int style) -{ - ATTR_OFF(win, COLOR_PAIR(style) | style_attrs[style]); -} - -wchar_t * -command_name_gen(const wchar_t *text, int fwd, int reset) -{ - static int index, len; - int dir; - - dir = fwd ? 1 : -1; - - if (reset) { - index = 0; - len = wcslen(text); - } else if (index >= -1 && index <= ARRLEN(cmds)) { - index += dir; - } - - while (index >= 0 && index < ARRLEN(cmds)) { - if (!wcsncmp(cmds[index].name, text, len)) - return wcsdup(cmds[index].name); - index += dir; - } - - return NULL; -} - -wchar_t * -track_name_gen(const wchar_t *text, int fwd, int reset) -{ - static struct link *cur; - struct link *iter; - struct track *track; - - if (reset) { - cur = tracks.head.next; - iter = cur; - } else { - iter = fwd ? cur->next : cur->prev; - } - - while (iter && LIST_INNER(&tracks, iter)) { - track = UPCAST(iter, struct ref)->data; - if (wcsstr(track->name, text)) { - cur = iter; - return wcsdup(track->name); - } - iter = fwd ? iter->next : iter->prev; - } - - return NULL; -} - -wchar_t * -tag_name_gen(const wchar_t *text, int fwd, int reset) -{ - static struct link *cur; - struct link *iter; - struct tag *tag; - - if (reset) { - cur = tags.head.next; - iter = cur; - } else { - iter = fwd ? cur->next : cur->prev; - } - - while (iter && LIST_INNER(&tags, iter)) { - tag = UPCAST(iter, struct tag); - if (wcsstr(tag->name, text)) { - cur = iter; - return wcsdup(tag->name); - } - iter = fwd ? iter->next : iter->prev; - } - - return NULL; -} - -void -toggle_current_tag(void) -{ - struct link *link, *iter; - struct track *track; - struct tag *tag; - struct ref *ref; - int in_tags, in_playlist; - - if (list_empty(&tags)) return; - - link = list_at(&tags, tag_nav.sel); - ASSERT(link != NULL); - tag = UPCAST(link, struct tag); - - /* toggle tag in tags_sel */ - if (refs_incl(&tags_sel, tag)) { - refs_rm(&tags_sel, tag); - } else { - ref = ref_init(tag); - list_push_back(&tags_sel, LINK(ref)); - } - - /* rebuild the full playlist */ - refs_free(&player->playlist); - for (LIST_ITER(&tags_sel, link)) { - tag = UPCAST(link, struct ref)->data; - for (LIST_ITER(&tag->tracks, iter)) { - ref = ref_init(UPCAST(iter, struct ref)->data); - ASSERT(ref != NULL); - list_push_back(&player->playlist, LINK(ref)); - } - } -} - -int -tag_pane_input(wint_t c) -{ - switch (c) { - case KEY_UP: - listnav_update_sel(&tag_nav, tag_nav.sel - 1); - return 1; - case KEY_DOWN: - listnav_update_sel(&tag_nav, tag_nav.sel + 1); - return 1; - case KEY_SPACE: - toggle_current_tag(); - return 1; - case KEY_ENTER: - refs_free(&tags_sel); - toggle_current_tag(); - return 1; - case KEY_PPAGE: - listnav_update_sel(&tag_nav, tag_nav.sel - tag_nav.wlen / 2); - return 1; - case KEY_NPAGE: - listnav_update_sel(&tag_nav, tag_nav.sel + tag_nav.wlen / 2); - return 1; - } + dbus_deinit(); - return 0; -} - -void -tag_pane_vis(struct pane *pane, int sel) -{ - struct tag *tag; - struct link *iter; - int index, tagsel; - - werase(pane->win); - pane_title(pane, "Tags", sel); - - listnav_update_bounds(&tag_nav, 0, list_len(&tags)); - listnav_update_wlen(&tag_nav, pane->h - 1); + tui_deinit(); - index = -1; - for (LIST_ITER(&tags, iter)) { - tag = UPCAST(iter, struct tag); - tagsel = refs_incl(&tags_sel, tag); - - index += 1; - if (index < tag_nav.wmin) continue; - if (index >= tag_nav.wmax) break; - - if (sel && tagsel && index == tag_nav.sel) - style_on(pane->win, STYLE_ITEM_HOVER_SEL); - else if (sel && index == tag_nav.sel) - style_on(pane->win, STYLE_ITEM_HOVER); - else if (tagsel) - style_on(pane->win, STYLE_ITEM_SEL); - else if (index == tag_nav.sel) - style_on(pane->win, STYLE_PREV); - - wmove(pane->win, 1 + index - tag_nav.wmin, 0); - wprintw(pane->win, "%-*.*ls", pane->w, pane->w, tag->name); - - if (sel && tagsel && index == tag_nav.sel) - style_off(pane->win, STYLE_ITEM_HOVER_SEL); - else if (sel && index == tag_nav.sel) - style_off(pane->win, STYLE_ITEM_HOVER); - else if (tagsel) - style_off(pane->win, STYLE_ITEM_SEL); - else if (index == tag_nav.sel) - style_off(pane->win, STYLE_PREV); - } -} - -int -track_pane_input(wint_t c) -{ - struct link *link; - struct track *track; - - switch (c) { - case KEY_UP: - listnav_update_sel(&track_nav, track_nav.sel - 1); - return 1; - case KEY_DOWN: - listnav_update_sel(&track_nav, track_nav.sel + 1); - return 1; - case KEY_ENTER: - link = list_at(tracks_vis, track_nav.sel); - if (!link) return 1; - track = UPCAST(link, struct ref)->data; - player_play_track(track); - return 1; - case KEY_PPAGE: - listnav_update_sel(&track_nav, - track_nav.sel - track_nav.wlen / 2); - return 1; - case KEY_NPAGE: - listnav_update_sel(&track_nav, - track_nav.sel + track_nav.wlen / 2); - return 1; - } - - return 0; -} - -void -track_pane_vis(struct pane *pane, int sel) -{ - struct track *track; - struct link *iter; - struct tag *tag; - int index; - - werase(pane->win); - pane_title(pane, "Tracks", sel); - - listnav_update_bounds(&track_nav, 0, list_len(tracks_vis)); - listnav_update_wlen(&track_nav, pane->h - 1); - - index = -1; - for (LIST_ITER(tracks_vis, iter)) { - track = UPCAST(iter, struct ref)->data; - - index += 1; - if (index < track_nav.wmin) continue; - if (index >= track_nav.wmax) break; - - if (sel && index == track_nav.sel && track == player->track) - style_on(pane->win, STYLE_ITEM_HOVER_SEL); - else if (sel && index == track_nav.sel) - style_on(pane->win, STYLE_ITEM_HOVER); - else if (track == player->track) - style_on(pane->win, STYLE_ITEM_SEL); - else if (index == track_nav.sel) - style_on(pane->win, STYLE_PREV); - - wmove(pane->win, 1 + index - track_nav.wmin, 0); - wprintw(pane->win, "%-*.*ls", pane->w, pane->w, track->name); - - if (sel && index == track_nav.sel && track == player->track) - style_off(pane->win, STYLE_ITEM_HOVER_SEL); - else if (sel && index == track_nav.sel) - style_off(pane->win, STYLE_ITEM_HOVER); - else if (track == player->track) - style_off(pane->win, STYLE_ITEM_SEL); - else if (index == track_nav.sel) - style_off(pane->win, STYLE_PREV); - } -} - -int -run_cmd(const wchar_t *query) -{ - const wchar_t *sep; - int i, cmdlen; - - sep = wcschr(query, L' '); - cmdlen = sep ? sep - query : wcslen(query); - for (i = 0; i < ARRLEN(cmds); i++) { - if (!wcsncmp(cmds[i].name, query, cmdlen)) { - if (!cmds[i].func(sep ? sep + 1 : NULL)) { - free(cmd_status); - cmd_status = wcsdup(L"Command Failed!\n"); - ASSERT(cmd_status != NULL); - } - return 1; - } - } - - return 0; -} - -int -play_track(const wchar_t *query) -{ - struct track *track; - struct link *iter; - - for (LIST_ITER(&tracks, iter)) { - track = UPCAST(iter, struct ref)->data; - if (wcsstr(track->name, query)) { - player_play_track(track); - return 1; - } - } - - return 0; -} - -int -select_tag(const wchar_t *query) -{ - struct tag *tag; - struct link *iter; - int index; - - index = -1; - for (LIST_ITER(&tags, iter)) { - index += 1; - tag = UPCAST(iter, struct tag); - if (wcsstr(tag->name, query)) { - listnav_update_sel(&tag_nav, index); - return 1; - } - } - - return 0; -} - -int -cmd_pane_input(wint_t c) -{ - wchar_t *res; - int match; - - switch (c) { - case KEY_ESC: - match = wcscmp(completion_query.buf, history->input->buf); - if (!completion_reset && match) - inputln_copy(history->input, &completion_query); - else if (history->sel == history->input) - pane_sel = pane_top_sel; - else - history->sel = history->input; - break; - case KEY_LEFT: - inputln_left(history->sel); - break; - case KEY_RIGHT: - inputln_right(history->sel); - break; - case KEY_CTRL('w'): - inputln_del(history->sel, history->sel->cur); - break; - case KEY_UP: - history_next(history); - break; - case KEY_DOWN: - history_prev(history); - break; - case KEY_ENTER: - if (!*history->sel->buf) { - pane_sel = pane_top_sel; - break; - } - - if (cmd_mode == IMODE_EXECUTE) { - run_cmd(history->sel->buf); - } else if (cmd_mode == IMODE_TRACK_SEARCH) { - play_track(history->sel->buf); - } else if (cmd_mode == IMODE_TAG_SEARCH) { - select_tag(history->sel->buf); - } - - history_submit(history); - pane_sel = pane_top_sel; - break; - case KEY_TAB: - case KEY_BTAB: - if (history->sel != history->input) { - inputln_copy(history->input, history->sel); - history->sel = history->input; - } - - if (completion_reset) - inputln_copy(&completion_query, history->input); - - res = completion(completion_query.buf, - c == KEY_TAB, completion_reset); - if (res) inputln_replace(history->input, res); - free(res); - - completion_reset = 0; - break; - case KEY_BACKSPACE: - if (history->sel->cur == 0) { - pane_sel = pane_top_sel; - break; - } - inputln_del(history->sel, 1); - completion_reset = 1; - break; - default: - if (!iswprint(c)) return 0; - inputln_addch(history->sel, c); - completion_reset = 1; - break; - } - - return 1; /* grab everything */ -} - -void -cmd_pane_vis(struct pane *pane, int sel) -{ - static wchar_t *linebuf = NULL; - static int linecap = 0; - struct inputln *cmd; - struct link *iter; - wchar_t *line, *end; - int index, offset; - - werase(pane->win); - - /* TODO add clear_line func to fill with spaces instead of - * filling buffer */ - - /* static line buffer for perf */ - if (pane->w > linecap) { - linecap = MAX(linecap, pane->w); - linebuf = realloc(linebuf, linecap * sizeof(wchar_t)); - } - end = linebuf + linecap; - - /* track name */ - style_on(pane->win, STYLE_TITLE); - pane_clearln(pane, 0); - if (player->track) { - swprintf(linebuf, linecap, L"%ls", player->track->name); - mvwaddwstr(pane->win, 0, 1, linebuf); - } - style_off(pane->win, STYLE_TITLE); - - if (player->loaded) { - /* status line */ - line = linebuf; - line += swprintf(line, end - line, L"%c ", - player_state_chars[player->state]); - line += swprintf(line, end - line, L"%s / ", - timestr(player->time_pos)); - line += swprintf(line, end - line, L"%s", - timestr(player->time_end)); - - if (player->volume >= 0) { - line += swprintf(line, end - line, L" - vol: %u%%", - player->volume); - } - - if (player->msg) { - line += swprintf(line, end - line, L" | [PLAYER] %s", - player->msg); - } - - if (!list_empty(&player->queue)) { - line += swprintf(line, end - line, - L" | [QUEUE] %i tracks", - list_len(&player->queue)); - } - - ATTR_ON(pane->win, A_REVERSE); - pane_clearln(pane, 1); - mvwaddwstr(pane->win, 1, 0, linebuf); - ATTR_OFF(pane->win, A_REVERSE); - } else if (player->msg) { - /* player message */ - line = linebuf; - line += swprintf(line, linecap, L"[PLAYER] %s", player->msg); - line += swprintf(line, end - line, L"%*.*s", - pane->w, pane->w, L" "); - - pane_clearln(pane, 1); - mvwaddwstr(pane->win, 1, 0, linebuf); - } - - /* status bits on right of status line */ - if (player->loaded) ATTR_ON(pane->win, A_REVERSE); - mvwaddstr(pane->win, 1, pane->w - 5, "[ ]"); - if (track_show_playlist) mvwaddstr(pane->win, 1, pane->w - 4, "P"); - if (player->autoplay) mvwaddstr(pane->win, 1, pane->w - 3, "A"); - if (player->shuffle) mvwaddstr(pane->win, 1, pane->w - 2, "S"); - if (player->loaded) ATTR_OFF(pane->win, A_REVERSE); - - if (sel || cmd_show) { - /* cmd and search input */ - line = linebuf; - - free(cmd_status); - cmd_status = NULL; - - cmd = history->sel; - if (cmd != history->input) { - index = 0; - for (LIST_ITER(&history->list, iter)) { - if (UPCAST(iter, struct inputln) == cmd) - break; - } - line += swprintf(line, end - line, L"[%i] ", - iter ? index : -1); - } else { - line += swprintf(line, end - line, L"%c", - imode_prefix[cmd_mode]); - } - offset = wcslen(linebuf); - - line += swprintf(line, end - line, L"%ls", cmd->buf); - - pane_clearln(pane, 2); - mvwaddwstr(pane->win, 2, 0, linebuf); - - if (sel) { /* show cursor in text */ - ATTR_ON(pane->win, A_REVERSE); - wmove(pane->win, 2, offset + cmd->cur); - waddch(pane->win, cmd->cur < cmd->len - ? cmd->buf[cmd->cur] : L' '); - ATTR_OFF(pane->win, A_REVERSE); - } - } else if (cmd_status) { - pane_clearln(pane, 2); - style_on(pane->win, STYLE_ERROR); - mvwaddwstr(pane->win, 2, 1, cmd_status); - style_off(pane->win, STYLE_ERROR); - } -} - -void -queue_hover(void) -{ - struct link *link; - - link = list_at(&player->playlist, track_nav.sel); - if (!link) return; - player_queue_append(UPCAST(link, struct ref)->data); -} - -void -update_track_playlist(void) -{ - struct link *link; - struct tag *tag; - - if (track_show_playlist) { - tracks_vis = &player->playlist; - } else { - link = list_at(&tags, tag_nav.sel); - if (!link) return; - tag = UPCAST(link, struct tag); - tracks_vis = &tag->tracks; - } -} - -void -main_input(wint_t c) -{ - switch (c) { - case KEY_CTRL('r'): - redrawwin(stdscr); - break; - case KEY_TAB: - pane_sel = pane_sel == &pane_left - ? &pane_right : &pane_left; - pane_top_sel = pane_sel; - break; - case KEY_ESC: - pane_sel = pane_top_sel; - break; - case KEY_LEFT: - if (!player->loaded) break; - player_seek(MAX(player->time_pos - 10, 0)); - break; - case KEY_RIGHT: - if (!player->loaded) break; - player_seek(MIN(player->time_pos + 10, player->time_end)); - break; - case L'y': - queue_hover(); - break; - case L'o': - player_queue_clear(); - break; - case L'c': - player_toggle_pause(); - break; - case L'n': - case L'>': - player_next(); - break; - case L'p': - case L'<': - player_prev(); - break; - case L'P': - if (track_show_playlist) { - pane_sel = tag_pane; - track_show_playlist = 0; - } else { - pane_sel = track_pane; - track_show_playlist = 1; - } - break; - case L'A': - player->autoplay ^= 1; - break; - case L'S': - player->shuffle ^= 1; - break; - case L'b': - player_seek(0); - break; - case L'x': - if (player->state == PLAYER_STATE_PLAYING) { - player_stop(); - } else { - player_play(); - } - break; - case L':': - cmd_mode = IMODE_EXECUTE; - pane_sel = &pane_bot; - completion_reset = 1; - history = &command_history; - completion = command_name_gen; - break; - case L'/': - cmd_mode = IMODE_TRACK_SEARCH; - pane_sel = &pane_bot; - completion_reset = 1; - history = &track_search_history; - completion = track_name_gen; - break; - case L'?': - cmd_mode = IMODE_TAG_SEARCH; - pane_sel = &pane_bot; - completion_reset = 1; - history = &tag_search_history; - completion = tag_name_gen; - break; - case L'+': - player_set_volume(MIN(100, player->volume + 5)); - break; - case L'-': - player_set_volume(MAX(0, player->volume - 5)); - break; - case L'q': - quit = 1; - break; - } -} - -void -main_vis(void) -{ - int i; - - /* add missing title tile at the top */ - style_on(stdscr, STYLE_TITLE); - move(0, pane_left.ex); - addch(' '); - style_off(stdscr, STYLE_TITLE); - - /* draw left-right separator line */ - style_on(stdscr, STYLE_PANE_SEP); - for (i = pane_left.sy + 1; i < pane_left.ey; i++) { - move(i, pane_left.ex); - addch(ACS_VLINE); - } - style_off(stdscr, STYLE_PANE_SEP); -} - -int -cmd_save(const wchar_t *args) -{ - data_save(); - return 0; -} - -int -cmd_move(const wchar_t *name) -{ - struct link *link; - struct track *track; - struct tag *tag; - char *newpath; - - tag = tag_find(name); - if (!tag) CMD_ERROR(L"Tag not found"); - - link = list_at(tracks_vis, track_nav.sel); - if (!link) CMD_ERROR(L"No track selected"); - track = UPCAST(link, struct ref)->data; - - newpath = aprintf("%s/%s", tag->fpath, track->fname); - ASSERT(newpath != NULL); - move_file(track->fpath, newpath); - free(track->fpath); - track->fpath = newpath; - - link_pop(link); - list_push_back(&tag->tracks, link); - - return 1; -} - -int -cmd_add(const wchar_t *name) -{ - struct link *link; - struct track *track; - struct ref *ref; - struct tag *tag; - char *newpath; - - tag = tag_find(name); - if (!tag) return 0; - - link = list_at(&player->playlist, track_nav.sel); - if (!link) return 0; - track = UPCAST(link, struct ref)->data; - - newpath = aprintf("%s/%s", tag->fpath, track->fname); - ASSERT(newpath != NULL); - copy_file(track->fpath, newpath); - track->fpath = newpath; - - track = track_init(tag->fpath, track->fname, get_fid(tag->fpath)); - ref = ref_init(track); - list_push_back(&tag->tracks, &ref->link); - - return 1; + log_deinit(); } int main(int argc, const char **argv) { - int i, handled; - wint_t c; - init(); - c = KEY_RESIZE; do { dbus_update(); player_update(); - - if (c == KEY_RESIZE) { - tui_resize(); - } else if (c != ERR) { - handled = 0; - if (pane_sel && pane_sel->active) - handled = pane_sel->handle(c); - - /* fallback if char not handled by pane */ - if (!handled) main_input(c); - } - - update_track_playlist(); - - refresh(); - for (i = 0; i < ARRLEN(panes); i++) { - /* only update ui for panes that are visible */ - if (!panes[i]->active) continue; - - panes[i]->update(panes[i], pane_sel == panes[i]); - wnoutrefresh(panes[i]->win); - } - - main_vis(); - doupdate(); - - get_wch(&c); - } while (!quit); + } while (tui_update()); } diff --git a/src/mpris.c b/src/mpris.c @@ -5,6 +5,8 @@ #include <stdbool.h> +static void dbus_handle_getall(DBusMessage *msg); + int dbus_active; DBusConnection *dbus_conn; @@ -17,37 +19,6 @@ static const char *const dbus_mpris_caps[] = { }; void -dbus_init(void) -{ - DBusError err; - int ret; - - dbus_active = 0; - - dbus_error_init(&err); - - /* dont fail if dbus not available, not everyone has - * it or needs it to play music */ - dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); - if (dbus_error_is_set(&err) || !dbus_conn) { - dbus_error_free(&err); - return; - } - - /* register as MPRIS compliant player for events */ - ret = dbus_bus_request_name(dbus_conn, "org.mpris.MediaPlayer2.tmus", - DBUS_NAME_FLAG_REPLACE_EXISTING, &err); - if (dbus_error_is_set(&err)) - PANIC("Failed to register as MPRIS service\n"); - - log_info("DBus active!\n"); - - dbus_active = 1; - - dbus_error_free(&err); -} - -void dbus_handle_getall(DBusMessage *msg) { DBusMessage *reply; @@ -105,6 +76,45 @@ dbus_handle_getall(DBusMessage *msg) } void +dbus_init(void) +{ + DBusError err; + int ret; + + dbus_active = 0; + + dbus_error_init(&err); + + /* dont fail if dbus not available, not everyone has + * it or needs it to play music */ + dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err) || !dbus_conn) { + dbus_error_free(&err); + return; + } + + /* register as MPRIS compliant player for events */ + ret = dbus_bus_request_name(dbus_conn, "org.mpris.MediaPlayer2.tmus", + DBUS_NAME_FLAG_REPLACE_EXISTING, &err); + if (dbus_error_is_set(&err)) + PANIC("Failed to register as MPRIS service\n"); + + log_info("DBus active!\n"); + + dbus_active = 1; + + dbus_error_free(&err); +} + +void +dbus_deinit(void) +{ + if (!dbus_active) return; + + dbus_connection_unref(dbus_conn); +} + +void dbus_update(void) { DBusMessage *msg; @@ -137,11 +147,3 @@ dbus_update(void) dbus_message_unref(msg); } -void -dbus_end(void) -{ - if (!dbus_active) return; - - dbus_connection_unref(dbus_conn); -} - diff --git a/src/mpris.h b/src/mpris.h @@ -1,11 +1,14 @@ +#pragma once + #include "util.h" -#include "dbus-1.0/dbus/dbus-glib.h" -#include "dbus-1.0/dbus/dbus.h" +#include <dbus-1.0/dbus/dbus-glib.h> +#include <dbus-1.0/dbus/dbus.h> void dbus_init(void); +void dbus_deinit(void); + void dbus_update(void); -void dbus_end(void); extern int dbus_active; extern DBusConnection *dbus_conn; diff --git a/src/pane.h b/src/pane.h @@ -1,6 +1,8 @@ #pragma once -#include "ncurses.h" +#define NCURSES_WIDECHAR 1 + +#include <ncurses.h> #include <wchar.h> diff --git a/src/player.c b/src/player.c @@ -24,6 +24,7 @@ struct player *player; static void player_clear_msg(void); static int handle_mpd_status(int status); +static void player_play_next(struct track *track); static void player_clear_msg(void) @@ -53,6 +54,37 @@ handle_mpd_status(int status) } void +player_play_next(struct track *prev) +{ + struct link *iter; + struct track *track; + int index; + + if (list_empty(&player->playlist)) + return; + + iter = NULL; + if (player->shuffle) { + /* TODO better algorithm for random sequence */ + index = rand() % list_len(&player->playlist); + iter = list_at(&player->playlist, index); + ASSERT(iter != NULL); + } else if (player->loaded) { + for (LIST_ITER(&player->playlist, iter)) { + track = UPCAST(iter, struct ref)->data; + if (track == prev) + break; + } + if (iter) iter = iter->next; + } + + if (!iter) iter = list_at(&player->playlist, 0); + track = UPCAST(iter, struct ref)->data; + + player_play_track(track); +} + +void player_init(void) { player = malloc(sizeof(struct player)); @@ -87,7 +119,7 @@ player_init(void) } void -player_free(void) +player_deinit(void) { struct link *iter; @@ -105,37 +137,6 @@ player_free(void) } void -player_play_next(struct track *prev) -{ - struct link *iter; - struct track *track; - int index; - - if (list_empty(&player->playlist)) - return; - - iter = NULL; - if (player->shuffle) { - /* TODO better algorithm for random sequence */ - index = rand() % list_len(&player->playlist); - iter = list_at(&player->playlist, index); - ASSERT(iter != NULL); - } else if (player->loaded) { - for (LIST_ITER(&player->playlist, iter)) { - track = UPCAST(iter, struct ref)->data; - if (track == prev) - break; - } - if (iter) iter = iter->next; - } - - if (!iter) iter = list_at(&player->playlist, 0); - track = UPCAST(iter, struct ref)->data; - - player_play_track(track); -} - -void player_update(void) { struct mpd_status *status; diff --git a/src/player.h b/src/player.h @@ -75,7 +75,8 @@ struct player { }; void player_init(void); -void player_free(void); +void player_deinit(void); + void player_update(void); void player_queue_clear(void); diff --git a/src/style.c b/src/style.c @@ -0,0 +1,39 @@ +#include "style.h" + +#include <string.h> + +static int style_attrs[STYLE_COUNT]; + +void +style_init(void) +{ + memset(style_attrs, 0, sizeof(style_attrs)); + style_add(STYLE_DEFAULT, COLOR_WHITE, COLOR_BLACK, 0); + style_add(STYLE_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD); + style_add(STYLE_PANE_SEP, COLOR_BLUE, COLOR_BLACK, 0); + style_add(STYLE_ITEM_SEL, COLOR_YELLOW, COLOR_BLACK, A_BOLD); + style_add(STYLE_ITEM_HOVER, COLOR_WHITE, COLOR_BLUE, 0); + style_add(STYLE_ITEM_HOVER_SEL, COLOR_YELLOW, COLOR_BLUE, A_BOLD); + style_add(STYLE_ERROR, COLOR_RED, COLOR_BLACK, 0); + style_add(STYLE_PREV, COLOR_WHITE, COLOR_BLACK, A_DIM); +} + +void +style_add(int style, int fg, int bg, int attr) +{ + style_attrs[style] = attr; + init_pair(style, fg, bg); +} + +void +style_on(WINDOW *win, int style) +{ + ATTR_ON(win, COLOR_PAIR(style) | style_attrs[style]); +} + +void +style_off(WINDOW *win, int style) +{ + ATTR_OFF(win, COLOR_PAIR(style) | style_attrs[style]); +} + diff --git a/src/style.h b/src/style.h @@ -0,0 +1,27 @@ +#pragma once + +#define NCURSES_WIDECHAR 1 + +#include <ncurses.h> + +#define ATTR_ON(win, attr) wattr_on(win, attr, NULL) +#define ATTR_OFF(win, attr) wattr_off(win, attr, NULL) + +enum { + STYLE_DEFAULT, + STYLE_TITLE, + STYLE_PANE_SEP, + STYLE_ITEM_SEL, + STYLE_ITEM_HOVER, + STYLE_ITEM_HOVER_SEL, + STYLE_PREV, + STYLE_ERROR, + STYLE_COUNT +}; + +void style_init(void); + +void style_add(int style, int fg, int bg, int attr); +void style_on(WINDOW *win, int style); +void style_off(WINDOW *win, int style); + diff --git a/src/track.c b/src/track.c @@ -6,7 +6,7 @@ #include <sys/stat.h> struct track * -track_init(const char *dir, const char *fname, int fid) +track_alloc(const char *dir, const char *fname, int fid) { struct track *track; int len; diff --git a/src/track.h b/src/track.h @@ -10,5 +10,5 @@ struct track { int fid; }; -struct track *track_init(const char *dir, const char *file, int fid); +struct track *track_alloc(const char *dir, const char *file, int fid); void track_free(struct track *t); diff --git a/src/tui.c b/src/tui.c @@ -0,0 +1,937 @@ +#define NCURSES_WIDECHAR 1 + +#include "tui.h" + +#include "cmd.h" +#include "data.h" +#include "history.h" +#include "pane.h" +#include "player.h" +#include "list.h" +#include "listnav.h" +#include "ref.h" +#include "style.h" +#include "util.h" + +#include <ncurses.h> + +#include <unistd.h> + +#include <wchar.h> +#include <wctype.h> + +#undef KEY_ENTER +#define KEY_ENTER '\n' +#define KEY_SPACE ' ' +#define KEY_ESC '\x1b' +#define KEY_TAB '\t' +#define KEY_CTRL(c) ((c) & ~0x60) + +enum { + IMODE_EXECUTE, + IMODE_TRACK_SEARCH, + IMODE_TAG_SEARCH, + IMODE_COUNT +}; + +typedef wchar_t *(*completion_gen)(const wchar_t *text, int fwd, int state); + +static void pane_title(struct pane *pane, const char *title, int highlight); + +static wchar_t *command_name_gen(const wchar_t *text, int fwd, int state); +static wchar_t *track_name_gen(const wchar_t *text, int fwd, int state); +static wchar_t *tag_name_gen(const wchar_t *text, int fwd, int state); + +static void toggle_current_tag(void); +static int tag_pane_input(wint_t c); +static void tag_pane_vis(struct pane *pane, int sel); + +static int track_pane_input(wint_t c); +static void track_pane_vis(struct pane *pane, int sel); + +static int play_track(const wchar_t *name); +static int select_tag(const wchar_t *name); + +static int cmd_pane_input(wint_t c); +static void cmd_pane_vis(struct pane *pane, int sel); + +static void queue_hover(void); +static void update_track_playlist(void); +static void main_input(wint_t c); +static void main_vis(void); + +static void tui_curses_init(void); +static void tui_resize(void); + +static int scrw, scrh; +static int quit; + +static struct pane pane_left, pane_right, pane_bot; +static struct pane *const panes[] = { + &pane_left, + &pane_right, + &pane_bot +}; + +static struct history command_history; +static struct history track_search_history; +static struct history tag_search_history; +static struct history *history; + +static int cmd_input_mode; +static int cmd_show; + +static struct inputln completion_query; +static int completion_reset; +static completion_gen completion; + +struct pane *cmd_pane, *tag_pane, *track_pane; +struct pane *pane_sel, *pane_top_sel; + +struct list *tracks_vis; +int track_show_playlist; +struct listnav tag_nav; +struct listnav track_nav; + +const char imode_prefix[IMODE_COUNT] = { + [IMODE_EXECUTE] = ':', + [IMODE_TRACK_SEARCH] = '/', + [IMODE_TAG_SEARCH] = '?', +}; + +static const char player_state_chars[] = { + [PLAYER_STATE_PAUSED] = '|', + [PLAYER_STATE_PLAYING] = '>', + [PLAYER_STATE_STOPPED] = '#' +}; + +void +pane_title(struct pane *pane, const char *title, int highlight) +{ + wmove(pane->win, 0, 0); + + style_on(pane->win, STYLE_TITLE); + if (highlight) ATTR_ON(pane->win, A_STANDOUT); + + wprintw(pane->win, " %-*.*s", pane->w - 1, pane->w - 1, title); + + if (highlight) ATTR_OFF(pane->win, A_STANDOUT); + style_off(pane->win, STYLE_TITLE); +} + +wchar_t * +command_name_gen(const wchar_t *text, int fwd, int reset) +{ + static int index, len; + int dir; + + dir = fwd ? 1 : -1; + + if (reset) { + index = 0; + len = wcslen(text); + } else if (index >= -1 && index <= command_count) { + index += dir; + } + + while (index >= 0 && index < command_count) { + if (!wcsncmp(commands[index].name, text, len)) + return wcsdup(commands[index].name); + index += dir; + } + + return NULL; +} + +wchar_t * +track_name_gen(const wchar_t *text, int fwd, int reset) +{ + static struct link *cur; + struct link *iter; + struct track *track; + + if (reset) { + cur = tracks.head.next; + iter = cur; + } else { + iter = fwd ? cur->next : cur->prev; + } + + while (iter && LIST_INNER(&tracks, iter)) { + track = UPCAST(iter, struct ref)->data; + if (wcsstr(track->name, text)) { + cur = iter; + return wcsdup(track->name); + } + iter = fwd ? iter->next : iter->prev; + } + + return NULL; +} + +wchar_t * +tag_name_gen(const wchar_t *text, int fwd, int reset) +{ + static struct link *cur; + struct link *iter; + struct tag *tag; + + if (reset) { + cur = tags.head.next; + iter = cur; + } else { + iter = fwd ? cur->next : cur->prev; + } + + while (iter && LIST_INNER(&tags, iter)) { + tag = UPCAST(iter, struct tag); + if (wcsstr(tag->name, text)) { + cur = iter; + return wcsdup(tag->name); + } + iter = fwd ? iter->next : iter->prev; + } + + return NULL; +} + +void +toggle_current_tag(void) +{ + struct link *link, *iter; + struct track *track; + struct tag *tag; + struct ref *ref; + int in_tags, in_playlist; + + if (list_empty(&tags)) return; + + link = list_at(&tags, tag_nav.sel); + ASSERT(link != NULL); + tag = UPCAST(link, struct tag); + + /* toggle tag in tags_sel */ + if (refs_incl(&tags_sel, tag)) { + refs_rm(&tags_sel, tag); + } else { + ref = ref_init(tag); + list_push_back(&tags_sel, LINK(ref)); + } + + /* rebuild the full playlist */ + refs_free(&player->playlist); + for (LIST_ITER(&tags_sel, link)) { + tag = UPCAST(link, struct ref)->data; + for (LIST_ITER(&tag->tracks, iter)) { + ref = ref_init(UPCAST(iter, struct ref)->data); + ASSERT(ref != NULL); + list_push_back(&player->playlist, LINK(ref)); + } + } +} + +int +tag_pane_input(wint_t c) +{ + switch (c) { + case KEY_UP: + listnav_update_sel(&tag_nav, tag_nav.sel - 1); + return 1; + case KEY_DOWN: + listnav_update_sel(&tag_nav, tag_nav.sel + 1); + return 1; + case KEY_SPACE: + toggle_current_tag(); + return 1; + case KEY_ENTER: + refs_free(&tags_sel); + toggle_current_tag(); + return 1; + case KEY_PPAGE: + listnav_update_sel(&tag_nav, tag_nav.sel - tag_nav.wlen / 2); + return 1; + case KEY_NPAGE: + listnav_update_sel(&tag_nav, tag_nav.sel + tag_nav.wlen / 2); + return 1; + } + + return 0; +} + +void +tag_pane_vis(struct pane *pane, int sel) +{ + struct tag *tag; + struct link *iter; + int index, tagsel; + + werase(pane->win); + pane_title(pane, "Tags", sel); + + listnav_update_bounds(&tag_nav, 0, list_len(&tags)); + listnav_update_wlen(&tag_nav, pane->h - 1); + + index = -1; + for (LIST_ITER(&tags, iter)) { + tag = UPCAST(iter, struct tag); + tagsel = refs_incl(&tags_sel, tag); + + index += 1; + if (index < tag_nav.wmin) continue; + if (index >= tag_nav.wmax) break; + + if (sel && tagsel && index == tag_nav.sel) + style_on(pane->win, STYLE_ITEM_HOVER_SEL); + else if (sel && index == tag_nav.sel) + style_on(pane->win, STYLE_ITEM_HOVER); + else if (tagsel) + style_on(pane->win, STYLE_ITEM_SEL); + else if (index == tag_nav.sel) + style_on(pane->win, STYLE_PREV); + + wmove(pane->win, 1 + index - tag_nav.wmin, 0); + wprintw(pane->win, "%-*.*ls", pane->w, pane->w, tag->name); + + if (sel && tagsel && index == tag_nav.sel) + style_off(pane->win, STYLE_ITEM_HOVER_SEL); + else if (sel && index == tag_nav.sel) + style_off(pane->win, STYLE_ITEM_HOVER); + else if (tagsel) + style_off(pane->win, STYLE_ITEM_SEL); + else if (index == tag_nav.sel) + style_off(pane->win, STYLE_PREV); + } +} + +int +track_pane_input(wint_t c) +{ + struct link *link; + struct track *track; + + switch (c) { + case KEY_UP: + listnav_update_sel(&track_nav, track_nav.sel - 1); + return 1; + case KEY_DOWN: + listnav_update_sel(&track_nav, track_nav.sel + 1); + return 1; + case KEY_ENTER: + link = list_at(tracks_vis, track_nav.sel); + if (!link) return 1; + track = UPCAST(link, struct ref)->data; + player_play_track(track); + return 1; + case KEY_PPAGE: + listnav_update_sel(&track_nav, + track_nav.sel - track_nav.wlen / 2); + return 1; + case KEY_NPAGE: + listnav_update_sel(&track_nav, + track_nav.sel + track_nav.wlen / 2); + return 1; + } + + return 0; +} + +void +track_pane_vis(struct pane *pane, int sel) +{ + struct track *track; + struct link *iter; + struct tag *tag; + int index; + + werase(pane->win); + pane_title(pane, "Tracks", sel); + + listnav_update_bounds(&track_nav, 0, list_len(tracks_vis)); + listnav_update_wlen(&track_nav, pane->h - 1); + + index = -1; + for (LIST_ITER(tracks_vis, iter)) { + track = UPCAST(iter, struct ref)->data; + + index += 1; + if (index < track_nav.wmin) continue; + if (index >= track_nav.wmax) break; + + if (sel && index == track_nav.sel && track == player->track) + style_on(pane->win, STYLE_ITEM_HOVER_SEL); + else if (sel && index == track_nav.sel) + style_on(pane->win, STYLE_ITEM_HOVER); + else if (track == player->track) + style_on(pane->win, STYLE_ITEM_SEL); + else if (index == track_nav.sel) + style_on(pane->win, STYLE_PREV); + + wmove(pane->win, 1 + index - track_nav.wmin, 0); + wprintw(pane->win, "%-*.*ls", pane->w, pane->w, track->name); + + if (sel && index == track_nav.sel && track == player->track) + style_off(pane->win, STYLE_ITEM_HOVER_SEL); + else if (sel && index == track_nav.sel) + style_off(pane->win, STYLE_ITEM_HOVER); + else if (track == player->track) + style_off(pane->win, STYLE_ITEM_SEL); + else if (index == track_nav.sel) + style_off(pane->win, STYLE_PREV); + } +} + +int +run_cmd(const wchar_t *query) +{ + const wchar_t *sep; + int i, cmdlen; + bool success; + + sep = wcschr(query, L' '); + cmdlen = sep ? sep - query : wcslen(query); + for (i = 0; i < command_count; i++) { + if (!wcsncmp(commands[i].name, query, cmdlen)) { + success = commands[i].func(sep ? sep + 1 : NULL); + if (!success && !cmd_status) { + free(cmd_status); + cmd_status = wcsdup(L"Command Failed!\n"); + ASSERT(cmd_status != NULL); + } + return 1; + } + } + + return 0; +} + +int +play_track(const wchar_t *query) +{ + struct track *track; + struct link *iter; + + for (LIST_ITER(&tracks, iter)) { + track = UPCAST(iter, struct ref)->data; + if (wcsstr(track->name, query)) { + player_play_track(track); + return 1; + } + } + + return 0; +} + +int +select_tag(const wchar_t *query) +{ + struct tag *tag; + struct link *iter; + int index; + + index = -1; + for (LIST_ITER(&tags, iter)) { + index += 1; + tag = UPCAST(iter, struct tag); + if (wcsstr(tag->name, query)) { + listnav_update_sel(&tag_nav, index); + return 1; + } + } + + return 0; +} + +int +cmd_pane_input(wint_t c) +{ + wchar_t *res; + int match; + + switch (c) { + case KEY_ESC: + match = wcscmp(completion_query.buf, history->input->buf); + if (!completion_reset && match) + inputln_copy(history->input, &completion_query); + else if (history->sel == history->input) + pane_sel = pane_top_sel; + else + history->sel = history->input; + break; + case KEY_LEFT: + inputln_left(history->sel); + break; + case KEY_RIGHT: + inputln_right(history->sel); + break; + case KEY_CTRL('w'): + inputln_del(history->sel, history->sel->cur); + break; + case KEY_UP: + history_next(history); + break; + case KEY_DOWN: + history_prev(history); + break; + case KEY_ENTER: + if (!*history->sel->buf) { + pane_sel = pane_top_sel; + break; + } + + if (cmd_input_mode == IMODE_EXECUTE) { + run_cmd(history->sel->buf); + } else if (cmd_input_mode == IMODE_TRACK_SEARCH) { + play_track(history->sel->buf); + } else if (cmd_input_mode == IMODE_TAG_SEARCH) { + select_tag(history->sel->buf); + } + + history_submit(history); + pane_sel = pane_top_sel; + break; + case KEY_TAB: + case KEY_BTAB: + if (history->sel != history->input) { + inputln_copy(history->input, history->sel); + history->sel = history->input; + } + + if (completion_reset) + inputln_copy(&completion_query, history->input); + + res = completion(completion_query.buf, + c == KEY_TAB, completion_reset); + if (res) inputln_replace(history->input, res); + free(res); + + completion_reset = 0; + break; + case KEY_BACKSPACE: + if (history->sel->cur == 0) { + pane_sel = pane_top_sel; + break; + } + inputln_del(history->sel, 1); + completion_reset = 1; + break; + default: + if (!iswprint(c)) return 0; + inputln_addch(history->sel, c); + completion_reset = 1; + break; + } + + return 1; /* grab everything */ +} + +void +cmd_pane_vis(struct pane *pane, int sel) +{ + static wchar_t *linebuf = NULL; + static int linecap = 0; + struct inputln *cmd; + struct link *iter; + wchar_t *line, *end; + int index, offset; + + werase(pane->win); + + /* static line buffer for perf */ + if (pane->w > linecap) { + linecap = MAX(linecap, pane->w); + linebuf = realloc(linebuf, linecap * sizeof(wchar_t)); + } + end = linebuf + linecap; + + /* track name */ + style_on(pane->win, STYLE_TITLE); + pane_clearln(pane, 0); + if (player->track) { + swprintf(linebuf, linecap, L"%ls", player->track->name); + mvwaddwstr(pane->win, 0, 1, linebuf); + } + style_off(pane->win, STYLE_TITLE); + + if (player->loaded) { + /* status line */ + line = linebuf; + line += swprintf(line, end - line, L"%c ", + player_state_chars[player->state]); + line += swprintf(line, end - line, L"%s / ", + timestr(player->time_pos)); + line += swprintf(line, end - line, L"%s", + timestr(player->time_end)); + + if (player->volume >= 0) { + line += swprintf(line, end - line, L" - vol: %u%%", + player->volume); + } + + if (player->msg) { + line += swprintf(line, end - line, L" | [PLAYER] %s", + player->msg); + } + + if (!list_empty(&player->queue)) { + line += swprintf(line, end - line, + L" | [QUEUE] %i tracks", + list_len(&player->queue)); + } + + ATTR_ON(pane->win, A_REVERSE); + pane_clearln(pane, 1); + mvwaddwstr(pane->win, 1, 0, linebuf); + ATTR_OFF(pane->win, A_REVERSE); + } else if (player->msg) { + /* player message */ + line = linebuf; + line += swprintf(line, linecap, L"[PLAYER] %s", player->msg); + line += swprintf(line, end - line, L"%*.*s", + pane->w, pane->w, L" "); + + pane_clearln(pane, 1); + mvwaddwstr(pane->win, 1, 0, linebuf); + } + + /* status bits on right of status line */ + if (player->loaded) ATTR_ON(pane->win, A_REVERSE); + mvwaddstr(pane->win, 1, pane->w - 5, "[ ]"); + if (track_show_playlist) mvwaddstr(pane->win, 1, pane->w - 4, "P"); + if (player->autoplay) mvwaddstr(pane->win, 1, pane->w - 3, "A"); + if (player->shuffle) mvwaddstr(pane->win, 1, pane->w - 2, "S"); + if (player->loaded) ATTR_OFF(pane->win, A_REVERSE); + + if (sel || cmd_show) { + /* cmd and search input */ + line = linebuf; + + free(cmd_status); + cmd_status = NULL; + + cmd = history->sel; + if (cmd != history->input) { + index = 0; + for (LIST_ITER(&history->list, iter)) { + if (UPCAST(iter, struct inputln) == cmd) + break; + } + line += swprintf(line, end - line, L"[%i] ", + iter ? index : -1); + } else { + line += swprintf(line, end - line, L"%c", + imode_prefix[cmd_input_mode]); + } + offset = wcslen(linebuf); + + line += swprintf(line, end - line, L"%ls", cmd->buf); + + pane_clearln(pane, 2); + mvwaddwstr(pane->win, 2, 0, linebuf); + + if (sel) { /* show cursor in text */ + ATTR_ON(pane->win, A_REVERSE); + wmove(pane->win, 2, offset + cmd->cur); + waddch(pane->win, cmd->cur < cmd->len + ? cmd->buf[cmd->cur] : L' '); + ATTR_OFF(pane->win, A_REVERSE); + } + } else if (cmd_status) { + pane_clearln(pane, 2); + style_on(pane->win, STYLE_ERROR); + mvwaddwstr(pane->win, 2, 1, cmd_status); + style_off(pane->win, STYLE_ERROR); + } +} + +void +queue_hover(void) +{ + struct link *link; + + link = list_at(&player->playlist, track_nav.sel); + if (!link) return; + player_queue_append(UPCAST(link, struct ref)->data); +} + +void +update_track_playlist(void) +{ + struct link *link; + struct tag *tag; + + if (track_show_playlist) { + tracks_vis = &player->playlist; + } else { + link = list_at(&tags, tag_nav.sel); + if (!link) return; + tag = UPCAST(link, struct tag); + tracks_vis = &tag->tracks; + } +} + +void +main_input(wint_t c) +{ + switch (c) { + case KEY_CTRL('r'): + redrawwin(stdscr); + break; + case KEY_TAB: + pane_sel = pane_sel == &pane_left + ? &pane_right : &pane_left; + pane_top_sel = pane_sel; + break; + case KEY_ESC: + pane_sel = pane_top_sel; + break; + case KEY_LEFT: + if (!player->loaded) break; + player_seek(MAX(player->time_pos - 10, 0)); + break; + case KEY_RIGHT: + if (!player->loaded) break; + player_seek(MIN(player->time_pos + 10, player->time_end)); + break; + case L'y': + queue_hover(); + break; + case L'o': + player_queue_clear(); + break; + case L'c': + player_toggle_pause(); + break; + case L'n': + case L'>': + player_next(); + break; + case L'p': + case L'<': + player_prev(); + break; + case L'P': + if (track_show_playlist) { + pane_sel = tag_pane; + track_show_playlist = 0; + } else { + pane_sel = track_pane; + track_show_playlist = 1; + } + break; + case L'A': + player->autoplay ^= 1; + break; + case L'S': + player->shuffle ^= 1; + break; + case L'b': + player_seek(0); + break; + case L'x': + if (player->state == PLAYER_STATE_PLAYING) { + player_stop(); + } else { + player_play(); + } + break; + case L':': + cmd_input_mode = IMODE_EXECUTE; + pane_sel = &pane_bot; + completion_reset = 1; + history = &command_history; + completion = command_name_gen; + break; + case L'/': + cmd_input_mode = IMODE_TRACK_SEARCH; + pane_sel = &pane_bot; + completion_reset = 1; + history = &track_search_history; + completion = track_name_gen; + break; + case L'?': + cmd_input_mode = IMODE_TAG_SEARCH; + pane_sel = &pane_bot; + completion_reset = 1; + history = &tag_search_history; + completion = tag_name_gen; + break; + case L'+': + player_set_volume(MIN(100, player->volume + 5)); + break; + case L'-': + player_set_volume(MAX(0, player->volume - 5)); + break; + case L'q': + quit = 1; + break; + } +} + +void +main_vis(void) +{ + int i; + + /* add missing title tile at the top */ + style_on(stdscr, STYLE_TITLE); + move(0, pane_left.ex); + addch(' '); + style_off(stdscr, STYLE_TITLE); + + /* draw left-right separator line */ + style_on(stdscr, STYLE_PANE_SEP); + for (i = pane_left.sy + 1; i < pane_left.ey; i++) { + move(i, pane_left.ex); + addch(ACS_VLINE); + } + style_off(stdscr, STYLE_PANE_SEP); +} + +void +tui_curses_init(void) +{ + initscr(); + + /* do most of the handling ourselves, + * enable special keys */ + raw(); + noecho(); + keypad(stdscr, TRUE); + + /* update screen occasionally for things like + * time even when no input was received */ + halfdelay(1); + + /* inits COLOR and COLOR_PAIRS used by styles */ + start_color(); + + /* dont show cursor */ + curs_set(0); + + /* we use ESC deselecting the current pane + * and not for escape sequences, so dont wait */ + ESCDELAY = 0; +} + +void +tui_resize(void) +{ + struct link *iter; + struct tag *tag; + int i, leftw; + + getmaxyx(stdscr, scrh, scrw); + + /* guarantee a minimum terminal size */ + while (scrw < 10 || scrh < 4) { + clear(); + refresh(); + usleep(10000); + getmaxyx(stdscr, scrh, scrw); + } + + /* adjust tag pane width to name lengths */ + leftw = 0; + for (LIST_ITER(&tags, iter)) { + tag = UPCAST(iter, struct tag); + leftw = MAX(leftw, wcslen(tag->name)); + } + leftw = MAX(leftw + 1, 0.2f * scrw); + + pane_resize(&pane_left, 0, 0, leftw, scrh - 3); + pane_resize(&pane_right, pane_left.ex + 1, 0, scrw, scrh - 3); + pane_resize(&pane_bot, 0, scrh - 3, scrw, scrh); +} +void +tui_init(void) +{ + quit = 0; + cmd_input_mode = IMODE_TRACK_SEARCH; + + inputln_init(&completion_query); + completion_reset = 1; + + history_init(&track_search_history); + history_init(&tag_search_history); + history_init(&command_history); + history = &command_history; + + tui_curses_init(); + + style_init(); + + pane_init((tag_pane = &pane_left), tag_pane_input, tag_pane_vis); + pane_init((track_pane = &pane_right), track_pane_input, track_pane_vis); + pane_init((cmd_pane = &pane_bot), cmd_pane_input, cmd_pane_vis); + + pane_sel = &pane_left; + pane_top_sel = pane_sel; + + listnav_init(&tag_nav); + listnav_init(&track_nav); + + track_show_playlist = 0; + update_track_playlist(); + + tui_resize(); +} + +void +tui_deinit(void) +{ + pane_free(&pane_left); + pane_free(&pane_right); + pane_free(&pane_bot); + + history_free(&track_search_history); + history_free(&tag_search_history); + history_free(&command_history); + + if (!isendwin()) endwin(); +} + +bool +tui_update(void) +{ + bool handled; + wint_t c; + int i; + + get_wch(&c); + + if (c == KEY_RESIZE) { + tui_resize(); + } else if (c != ERR) { + handled = 0; + if (pane_sel && pane_sel->active) + handled = pane_sel->handle(c); + + /* fallback if char not handled by pane */ + if (!handled) main_input(c); + } + + update_track_playlist(); + + refresh(); + for (i = 0; i < ARRLEN(panes); i++) { + /* only update ui for panes that are visible */ + if (!panes[i]->active) continue; + + panes[i]->update(panes[i], pane_sel == panes[i]); + wnoutrefresh(panes[i]->win); + } + + main_vis(); + doupdate(); + + return !quit; +} + +void +tui_restore(void) +{ + if (!isendwin()) + endwin(); +} + + diff --git a/src/tui.h b/src/tui.h @@ -0,0 +1,25 @@ +#pragma once + +#include "list.h" +#include "listnav.h" +#include "pane.h" + +#include <stdbool.h> +#include <wchar.h> + +void tui_init(void); +void tui_deinit(void); + +bool tui_update(void); +void tui_restore(void); + +extern struct pane *cmd_pane, *tag_pane, *track_pane; +extern struct pane *pane_sel, *pane_top_sel; + +extern struct list *tracks_vis; +extern int track_show_playlist; + +extern struct listnav tag_nav; +extern struct listnav track_nav; + + diff --git a/src/util.c b/src/util.c @@ -1,9 +1,9 @@ #define _XOPEN_SOURCE 600 #include "util.h" +#include "tui.h" -#include "execinfo.h" -#include "ncurses.h" +#include <execinfo.h> #include <stdarg.h> #include <stdlib.h> @@ -45,7 +45,8 @@ panic(const char *file, int line, const char *msg, ...) { va_list ap; - endwin(); + tui_restore(); + fprintf(stderr, "Panic at %s:%i (", file, line); va_start(ap, msg); vfprintf(stderr, msg, ap); @@ -60,8 +61,10 @@ assert(int cond, const char *file, int line, const char *condstr) { if (cond) return; - endwin(); + tui_restore(); + fprintf(stderr, "Assertion failed %s:%i (%s)\n", file, line, condstr); + exit(1); }