commit 43282adc30aadefb563ef26bea1233d4cbdb6007
parent 7902429e5d69bb6aa57c6f1411b163d3e037e0e1
Author: Louis Burda <quent.burda@gmail.com>
Date: Fri, 7 Jan 2022 17:45:20 +0100
Added MPRIS control
Diffstat:
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);