tmus

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

commit 43282adc30aadefb563ef26bea1233d4cbdb6007
parent 7902429e5d69bb6aa57c6f1411b163d3e037e0e1
Author: Louis Burda <quent.burda@gmail.com>
Date:   Fri,  7 Jan 2022 17:45:20 +0100

Added MPRIS control

Diffstat:
M.gitignore | 2++
MMakefile | 6+++---
Acompile_commands.json | 21+++++++++++++++++++++
Asrc/log.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/log.h | 9+++++++++
Msrc/main.c | 96++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Asrc/mpris.c | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mpris.h | 12++++++++++++
Msrc/player.c | 1+
9 files changed, 296 insertions(+), 46 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -5,3 +5,5 @@ env.sh todo build *.gch +.cache +log diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ -CFLAGS = -I src -g -LDLIBS = -lcurses -lreadline -lmpdclient -DEPFLAGS = -MT $@ -MMD -MP -MF build/$*.d +CFLAGS = -I src -g $(shell pkg-config --cflags glib-2.0 dbus-1) +LDLIBS = -lcurses -lmpdclient $(shell pkg-config --libs glib-2.0 dbus-1) +DEPFLAGS = -MT $@ -MMD -MP -MF build/$*.d SRCS = $(wildcard src/*.c) OBJS = $(SRCS:src/%.c=build/%.o) diff --git a/compile_commands.json b/compile_commands.json @@ -0,0 +1,20 @@ +[ + { + "arguments": [ + "cc", + "-c", + "-I", + "src", + "-g", + "-I/usr/include/glib-2.0", + "-I/usr/lib/glib-2.0/include", + "-I/usr/include/dbus-1.0", + "-I/usr/lib/dbus-1.0/include", + "-o", + "build/main.o", + "src/main.c" + ], + "directory": "/snxdata/lib/dev/tmus", + "file": "src/main.c" + } +] +\ No newline at end of file diff --git a/src/log.c b/src/log.c @@ -0,0 +1,48 @@ +#include "log.h" +#include "util.h" + +#include <stdbool.h> +#include <stdlib.h> + +int log_active; +FILE *log_file; + +void +log_init(void) +{ + const char *envstr; + + log_active = false; + + envstr = getenv("TMUS_LOG"); + if (!envstr) return; + + log_file = fopen(envstr, "w+"); + if (!log_file) PANIC("Failed to open log file\n"); + + log_active = true; +} + +void +log_info(const char *fmtstr, ...) +{ + va_list ap; + + if (!log_active) return; + + va_start(ap, fmtstr); + vfprintf(log_file, fmtstr, ap); + va_end(ap); + + 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 @@ -0,0 +1,9 @@ +#include <stdarg.h> +#include <stdio.h> + +void log_init(void); +void log_info(const char *fmtstr, ...); +void log_end(void); + +extern int log_active; +extern FILE *log_file; diff --git a/src/main.c b/src/main.c @@ -3,6 +3,8 @@ #include "util.h" #include "list.h" +#include "log.h" +#include "mpris.h" #include "history.h" #include "tag.h" #include "pane.h" @@ -107,11 +109,10 @@ 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 ncurses_init(void); - static void data_load(void); static void data_save(void); static void data_free(void); @@ -152,35 +153,43 @@ void init(void) { setlocale(LC_ALL, ""); + srand(time(NULL)); quit = 0; - signal(SIGINT, exit); - on_exit(cleanup, NULL); - history_init(&search_history); history_init(&command_history); history = &command_history; + log_init(); + data_load(); player_init(); tui_init(); + dbus_init(); + listnav_init(&tag_nav); listnav_init(&track_nav); + + on_exit(cleanup, NULL); + signal(SIGINT, exit); } void -cleanup(int code, void* arg) +cleanup(int exitcode, void* arg) { tui_end(); - if (code == EXIT_SUCCESS) - data_save(); + if (!exitcode) data_save(); player_free(); + dbus_end(); + + log_end(); + history_free(&search_history); history_free(&command_history); } @@ -188,7 +197,7 @@ cleanup(int code, void* arg) void tui_init(void) { - ncurses_init(); + tui_ncurses_init(); memset(style_attrs, 0, sizeof(style_attrs)); style_init(STYLE_DEFAULT, COLOR_WHITE, COLOR_BLACK, 0); @@ -208,6 +217,32 @@ tui_init(void) } 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; @@ -245,32 +280,6 @@ tui_end(void) } void -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 data_load(void) { struct dirent *ent; @@ -567,6 +576,7 @@ track_pane_input(wint_t c) listnav_update_sel(&track_nav, track_nav.sel + 1); return 1; case KEY_ENTER: + if (list_empty(&player->playlist)) return 1; link = link_iter(player->playlist.next, track_nav.sel); ASSERT(link != NULL); track = UPCAST(link, struct ref)->data; @@ -805,14 +815,6 @@ cmd_pane_vis(struct pane *pane, int sel) list_len(&player->queue)); } - if (player->autoplay) { - line += swprintf(line, end - line, L" | AUTOPLAY"); - } - - if (player->shuffle) { - line += swprintf(line, end - line, L" | SHUFFLE"); - } - ATTR_ON(pane->win, A_REVERSE); pane_clearln(pane, 1); mvwaddwstr(pane->win, 1, 0, linebuf); @@ -828,6 +830,13 @@ cmd_pane_vis(struct pane *pane, int sel) 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 - 4, "[ ]"); + 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; @@ -984,6 +993,7 @@ main(int argc, const char **argv) c = KEY_RESIZE; do { + dbus_update(); player_update(); if (c == KEY_RESIZE) { diff --git a/src/mpris.c b/src/mpris.c @@ -0,0 +1,147 @@ +#include "log.h" +#include "mpris.h" +#include "player.h" +#include "util.h" + +#include <stdbool.h> + +int dbus_active; +DBusConnection *dbus_conn; + +static const char *const dbus_mpris_caps[] = { + "CanPlay", + "CanPause", + "CanGoPrevious", + "CanGoNext", + "CanControl" +}; + +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; + DBusError err; + DBusMessageIter iter, aiter, eiter, viter; + const char *interface; + dbus_bool_t can; + int i, ok; + + dbus_error_init(&err); + + ok = dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &interface); + if (ok && strcmp(interface, "org.mpris.MediaPlayer2.Player")) { + dbus_error_free(&err); + return; + } + + reply = dbus_message_new_method_return(msg); + + /* TODO: change to more sane api like gio and gdbus (?) */ + + /* connect argument iter to message */ + dbus_message_iter_init_append(reply, &iter); + + /* add array of dict entries { string : variant } */ + ASSERT(dbus_message_iter_open_container(&iter, 'a', "{sv}", &aiter)); + + for (i = 0; i < ARRLEN(dbus_mpris_caps); i++) { + /* add dict entry */ + ASSERT(dbus_message_iter_open_container(&aiter, + 'e', NULL, &eiter)); + + /* string key */ + ASSERT(dbus_message_iter_append_basic(&eiter, + 's', &dbus_mpris_caps[i])); + + /* variant container */ + ASSERT(dbus_message_iter_open_container(&eiter, + 'v', "b", &viter)); + + can = true; /* bool value */ + ASSERT(dbus_message_iter_append_basic(&viter, 'b', &can)); + + dbus_message_iter_close_container(&eiter, &viter); + dbus_message_iter_close_container(&aiter, &eiter); + } + + dbus_message_iter_close_container(&iter, &aiter); + + dbus_connection_send(dbus_conn, reply, NULL); + + dbus_message_unref(reply); + + dbus_error_free(&err); +} + +void +dbus_update(void) +{ + DBusMessage *msg; + const char *interface; + const char *method; + + if (!dbus_active) return; + + dbus_connection_read_write(dbus_conn, 0); + msg = dbus_connection_pop_message(dbus_conn); + if (msg == NULL) return; + + method = dbus_message_get_member(msg); + interface = dbus_message_get_interface(msg); + if (!strcmp(interface, "org.freedesktop.DBus.Properties")) { + log_info("DBus: Properties requested\n"); + if (!strcmp(method, "GetAll")) + dbus_handle_getall(msg); + } else if (!strcmp(interface, "org.mpris.MediaPlayer2.Player")) { + log_info("MPRIS: Method %s\n", method); + if (!strcmp(method, "PlayPause")) { + player_toggle_pause(); + } else if (!strcmp(method, "Next")) { + player_next(); + } else if (!strcmp(method, "Previous")) { + player_prev(); + } + } + + 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 @@ -0,0 +1,12 @@ +#include "util.h" + +#include "dbus-1.0/dbus/dbus-glib.h" +#include "dbus-1.0/dbus/dbus.h" + +void dbus_init(void); +void dbus_update(void); +void dbus_end(void); + +extern int dbus_active; +extern DBusConnection *dbus_conn; + diff --git a/src/player.c b/src/player.c @@ -109,6 +109,7 @@ player_play_next(struct track *prev) if (list_empty(&player->playlist)) return; + iter = NULL; if (player->shuffle) { /* TODO better algorithm for random sequence */ index = rand() % list_len(&player->playlist);