X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=ursaryd.c;h=8df65df31a8493eee81ea6cb22a0889c6e453ede;hb=ed7db1e07ba9ca31a7baab970f42d476b2c03021;hp=16e4066a2b2b886c35e8c864e7ae59c5e34d6b75;hpb=7760f3a50295d377b2b9fc89740765d94dc118dc;p=ursary.git diff --git a/ursaryd.c b/ursaryd.c index 16e4066..8df65df 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -1,21 +1,46 @@ /* * The Ursary Audio Controls * - * (c) 2014 Martin Mares + * (c) 2014--2020 Martin Mares */ -#define LOCAL_DEBUG +#undef LOCAL_DEBUG #include #include +#include +#include #include +#include #include +#include #include #include #include +#include #include "ursaryd.h" +#include "usb.h" + +/* + * Map of all controls + * + * rotary red button green button + * 0 sink PCH mute use headphones + * 1 - - - + * 2 - - - + * 3 - - - + * 4 MPD mute MPD play/pause + * 5 Albireo MPV mute MPD stop + * 6 Albireo other mute MPD prev + * 7 other machines mute MPD next + * + * center - + * slider - + */ + +#define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo" /*** Sink controls ***/ @@ -51,6 +76,18 @@ static void update_ring_from_sink(int ring, const char *sink_name) noct_set_button(ring, 0); } +static void update_button_from_port(int button, const char *sink_name, const char *port_name) +{ + struct pulse_sink *s = pulse_sink_by_name(sink_name); + if (!s) + { + noct_set_button(button, 0); + return; + } + + noct_set_button(button, !strcmp(s->active_port, port_name)); +} + static void update_sink_from_rotary(int delta, const char *sink_name) { struct pulse_sink *s = pulse_sink_by_name(sink_name); @@ -81,47 +118,68 @@ static void update_sink_mute_from_button(int on, const char *sink_name) pulse_sink_set_mute(s->idx, !s->mute); } +static void update_port_from_button(int on, const char *sink_name, const char *port1, const char *port2) +{ + if (!on) + return; + + struct pulse_sink *s = pulse_sink_by_name(sink_name); + if (!s) + return; + + const char *port = port1; + if (!strcmp(s->active_port, port1)) + port = port2; + + DBG("## Setting port of sink %s to %s", s->name, port); + pulse_sink_set_port(s->idx, port); +} + /*** Client controls ***/ struct client_map { - int rotary; + int group; const char *client; const char *host; - double range; }; static struct client_map client_map[] = { - { 4, "Music Player Daemon", "albireo", 1 }, - { 5, "MPlayer", NULL, 1 }, - { 6, NULL, "ogion", 1 }, - { 7, NULL, "ursula", 1 }, + { 4, "Music Player Daemon", "albireo", }, + { 5, "mpv", "albireo", }, + { 6, NULL, "albireo", }, + { 7, NULL, NULL, }, }; -#define NUM_CLIENTS ARRAY_SIZE(client_map) +#define CLIENT_MAP_SIZE ARRAY_SIZE(client_map) + +struct group_config { + bool enabled; + double range; +}; -struct client_state { +struct group_state { double volume; bool have_muted[2]; }; -static struct client_state client_state[NUM_CLIENTS]; +#define NUM_GROUPS 9 -static int find_client_by_rotary(int rotary) -{ - uns i; - for (i=0; i < NUM_CLIENTS; i++) - if (client_map[i].rotary == rotary) - return i; - return -1; -} +static struct group_config group_config[NUM_GROUPS] = { + [4] = { .enabled = 1, .range = 1.5 }, + [5] = { .enabled = 1, .range = 1.5 }, + [6] = { .enabled = 1, .range = 1.5 }, + [7] = { .enabled = 1, .range = 1.5 }, +}; + +static struct group_state group_state[NUM_GROUPS]; -static void calc_clients(void) +static void calc_groups(void) { - bzero(client_state, sizeof(client_state)); + bzero(group_state, sizeof(group_state)); CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { - s->noct_client_idx = -1; + s->noct_group_idx = -1; if (s->client_idx < 0 || s->sink_idx < 0) continue; @@ -130,67 +188,74 @@ static void calc_clients(void) if (!c) continue; - for (uns i=0; i < NUM_CLIENTS; i++) + for (uns i=0; i < CLIENT_MAP_SIZE; i++) { struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; if ((!cm->client || !strcmp(cm->client, c->name)) && (!cm->host || !strcmp(cm->host, c->host))) { - // DBG("@@ Client #%d, sink input #%d -> rotary %d", s->client_idx, s->idx, cm->rotary); - s->noct_client_idx = i; - cs->volume = MAX(cs->volume, s->volume); - cs->have_muted[!!s->mute] = 1; + int g = cm->group; + struct group_state *gs = &group_state[g]; + DBG("@@ Client #%d, sink input #%d -> group %d", s->client_idx, s->idx, g); + s->noct_group_idx = g; + gs->volume = MAX(gs->volume, s->volume); + gs->have_muted[!!s->mute] = 1; break; } } } } -static void update_clients(void) +static void update_groups(void) { - calc_clients(); + calc_groups(); - for (uns i=0; i < NUM_CLIENTS; i++) + for (uns i=0; i < NUM_GROUPS; i++) { - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - if (!cs->have_muted[0] && !cs->have_muted[1]) + struct group_config *gc = &group_config[i]; + struct group_state *gs = &group_state[i]; + if (!gc->enabled) + continue; + + DBG("@@ Group #%d: mute=%d/%d volume=%.3f/%.3f", i, gs->have_muted[0], gs->have_muted[1], volume_from_pa(gs->volume), gc->range); + + if (!gs->have_muted[0] && !gs->have_muted[1]) { - noct_set_ring(cm->rotary, RING_MODE_LEFT, 0); - noct_set_button(cm->rotary, 0); + noct_set_ring(i, RING_MODE_LEFT, 0); + noct_set_button(i, 0); } - else if (!cs->have_muted[0]) + else if (!gs->have_muted[0]) { - noct_set_ring(cm->rotary, RING_MODE_SINGLE_ON, 0x7f); - noct_set_button(cm->rotary, 1); + noct_set_ring(i, RING_MODE_SINGLE_ON, 0x7f); + noct_set_button(i, 1); } else { - double vol = CLAMP(volume_from_pa(cs->volume), 0, cm->range); - int val = 0x7f * vol / cm->range; + double vol = CLAMP(volume_from_pa(gs->volume), 0, gc->range); + int val = 0x7f * vol / gc->range; val = CLAMP(val, 12, 0x7f); - noct_set_ring(cm->rotary, RING_MODE_LEFT, val); - noct_set_button(cm->rotary, 0); + noct_set_ring(i, RING_MODE_LEFT, val); + noct_set_button(i, 0); } } } -static void update_client_from_rotary(int rotary, int delta) +static void update_group_from_rotary(int i, int delta) { - int i = find_client_by_rotary(rotary); - if (i < 0) + if (i >= NUM_GROUPS) + return; + struct group_config *gc = &group_config[i]; + struct group_state *gs = &group_state[i]; + if (!gc->enabled) return; - struct client_map *cm = &client_map[i]; - struct client_state *cs = &client_state[i]; - calc_clients(); - double vol = volume_from_pa(cs->volume) + delta*0.02; - pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, cm->range)); + calc_groups(); + double vol = volume_from_pa(gs->volume) + delta*0.02; + pa_volume_t pavol = volume_to_pa(CLAMP(vol, 0, gc->range)); CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { - if (s->noct_client_idx == i && s->volume != pavol) + if (s->noct_group_idx == i && s->volume != pavol) { DBG("@@ Client #%d, sink input #%d: setting volume=%u", s->client_idx, s->idx, pavol); pa_cvolume cvol; @@ -200,24 +265,25 @@ static void update_client_from_rotary(int rotary, int delta) } } -static void update_client_from_button(int button, int on) +static void update_group_from_button(int i, int on) { - if (button >= 8 || !on) + if (!on) return; - - int i = find_client_by_rotary(button); - if (i < 0) + if (i >= NUM_GROUPS) + return; + struct group_config *gc = &group_config[i]; + struct group_state *gs = &group_state[i]; + if (!gc->enabled) return; - struct client_state *cs = &client_state[i]; - calc_clients(); - if (!cs->have_muted[0] && !cs->have_muted[1]) + calc_groups(); + if (!gs->have_muted[0] && !gs->have_muted[1]) return; - uns mute = !cs->have_muted[1]; + uns mute = !gs->have_muted[1]; CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) { - if (s->noct_client_idx == i) + if (s->noct_group_idx == i) { DBG("@@ Client #%d, sink input #%d: setting mute=%u", s->client_idx, s->idx, mute); pulse_sink_input_set_mute(s->idx, mute); @@ -225,12 +291,14 @@ static void update_client_from_button(int button, int on) } } +#if 0 // Not used at the moment + static int find_touched_client(void) { int touched = -1; - for (uns i=0; i < NUM_CLIENTS; i++) - if (noct_rotary_touched[client_map[i].rotary]) + for (uns i=0; i < NUM_GROUPS; i++) + if (group_config[i].enabled && noct_rotary_touched[i]) { if (touched >= 0) return -1; @@ -239,14 +307,18 @@ static int find_touched_client(void) return touched; } +#endif + /*** Default sink controls ***/ +#if 0 // Not mapped to any button at the moment + static const char *get_client_sink(int i) { const char *sink = NULL; CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) - if (s->noct_client_idx == i) + if (s->noct_group_idx == i) { struct pulse_sink *sk = (s->sink_idx >= 0) ? pulse_sink_by_idx(s->sink_idx) : NULL; const char *ss = sk ? sk->name : NULL; @@ -271,16 +343,25 @@ static void update_default_sink(void) { noct_set_button(8, 1); noct_set_button(9, 0); + noct_set_button(10, 0); } else if (!strcmp(sink, "catarium")) { noct_set_button(8, 0); noct_set_button(9, 1); + noct_set_button(10, 0); + } + else if (!strcmp(sink, "compress")) + { + noct_set_button(8, 0); + noct_set_button(9, 0); + noct_set_button(10, 1); } else { noct_set_button(8, 0); noct_set_button(9, 0); + noct_set_button(10, 0); } } @@ -311,6 +392,13 @@ static void update_default_sink_from_button(int button, int on) else switch_to = "catarium"; } + else if (button == 10) + { + if (!strcmp(sink, "compress")) + switch_to = "burrow"; + else + switch_to = "compress"; + } if (!switch_to) return; @@ -322,7 +410,7 @@ static void update_default_sink_from_button(int button, int on) return; CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) - if (s->noct_client_idx == i) + if (s->noct_group_idx == i) { DBG("Moving input #%d to sink #%d", s->idx, sk->idx); pulse_sink_input_move(s->idx, sk->idx); @@ -335,6 +423,8 @@ static void update_default_sink_from_button(int button, int on) } } +#endif + /*** MPD controls ***/ static bool mpd_flash_state; @@ -422,49 +512,107 @@ static void update_mpd_from_button(int button UNUSED, int on) /*** Main update routines ***/ static struct main_timer update_timer; +static timestamp_t last_touch_time; + +enum update_state { + US_OFFLINE, + US_ONLINE, + US_SLEEPING, + US_PULSE_DEAD, +}; + +static enum update_state update_state; + +static bool want_sleep_p(void) +{ + CLIST_FOR_EACH(struct pulse_sink *, s, pulse_sink_list) + if (!s->suspended) + return 0; + return 1; +} static void do_update(struct main_timer *t) { + DBG("## UPDATE in state %u", update_state); timer_del(t); + + // Nocturn dead? if (!noct_is_ready()) { DBG("## UPDATE: Nocturn is not ready"); + update_state = US_OFFLINE; return; } + else if (update_state == US_OFFLINE) + { + DBG("## UPDATE: Going online"); + update_state = US_ONLINE; + } - static bool dead; - if (pulse_state != PS_ONLINE) + // Pulse dead? + if (!pulse_is_ready()) { DBG("## UPDATE: Pulse is not online"); - for (int i=0; i<=8; i++) - noct_set_ring(i, RING_MODE_LEFT, 0); - for (int i=0; i<8; i++) + if (update_state != US_PULSE_DEAD) { - noct_set_button(i, 1); - noct_set_button(i+8, 0); + update_state = US_PULSE_DEAD; + noct_clear(); + for (int i=0; i<8; i++) + noct_set_button(i, 1); } - dead = 1; return; } - if (dead) + else if (update_state == US_PULSE_DEAD) { DBG("## UPDATE: Waking up from the dead"); - for (int i=0; i<=8; i++) - noct_set_ring(i, RING_MODE_LEFT, 0); - for (int i=0; i<16; i++) - noct_set_button(i, 0); - dead = 0; + update_state = US_ONLINE; + noct_clear(); } - DBG("## UPDATE"); #ifdef LOCAL_DEBUG pulse_dump(); #endif - update_ring_from_sink(0, "ursarium"); - update_ring_from_sink(1, "catarium"); - update_clients(); + // Sleeping? + bool want_sleep = want_sleep_p(); + if (!want_sleep) + last_touch_time = main_get_now(); + timestamp_t since_touch = last_touch_time ? main_get_now() - last_touch_time : 0; + timestamp_t sleep_in = 5000; + if (since_touch >= sleep_in) + { + DBG("UPDATE: Sleeping"); + if (update_state == US_ONLINE) + { + update_state = US_SLEEPING; + noct_clear(); + noct_set_ring(8, RING_MODE_LEFT, 127); + } + return; + } + else + { + if (update_state == US_SLEEPING) + { + DBG("UPDATE: Waking up"); + update_state = US_ONLINE; + noct_clear(); + } + if (want_sleep) + { + timestamp_t t = sleep_in - since_touch + 10; + DBG("UPDATE: Scheduling sleep in %d ms", (int) t); + timer_add_rel(&update_timer, t); + } + } + + // Everything normal + update_ring_from_sink(0, PCH_SINK); + update_button_from_port(8, PCH_SINK, "analog-output-headphones"); + update_groups(); +#if 0 update_default_sink(); +#endif update_mpd(); } @@ -473,66 +621,82 @@ void schedule_update(void) timer_add_rel(&update_timer, 10); } -void notify_rotary(int rotary, int delta) +static bool prepare_notify(void) { - if (pulse_state != PS_ONLINE) + if (!pulse_is_ready()) { DBG("## NOTIFY: Pulse is not online"); - return; + return 0; + } + + last_touch_time = main_get_now(); + if (update_state == US_SLEEPING) + { + DBG("## NOTIFY: Scheduling wakeup"); + schedule_update(); } + return 1; +} + +void notify_rotary(int rotary, int delta) +{ + if (!prepare_notify()) + return; + switch (rotary) { case 0: - update_sink_from_rotary(delta, "ursarium"); - break; - case 1: - update_sink_from_rotary(delta, "catarium"); - break; - case 8: - update_sink_from_rotary(delta, "ursarium"); - update_sink_from_rotary(delta, "catarium"); + update_sink_from_rotary(delta, PCH_SINK); break; default: - update_client_from_rotary(rotary, delta); + update_group_from_rotary(rotary, delta); } } void notify_button(int button, int on) { - if (pulse_state != PS_ONLINE) - { - DBG("## NOTIFY: Pulse is not online"); - return; - } + if (!prepare_notify()) + return; switch (button) { case 0: - update_sink_mute_from_button(on, "ursarium"); - break; - case 1: - update_sink_mute_from_button(on, "catarium"); + update_sink_mute_from_button(on, PCH_SINK); break; case 8: + update_port_from_button(on, PCH_SINK, "analog-output-lineout", "analog-output-headphones"); + break; +#if 0 case 9: + case 10: update_default_sink_from_button(button, on); break; +#endif case 12: update_mpd_from_button(button, on); break; + case 13: + if (on) + mpd_stop(); + break; + case 14: + if (on) + mpd_prev(); + break; + case 15: + if (on) + mpd_next(); + break; default: - update_client_from_button(button, on); + update_group_from_button(button, on); } } void notify_touch(int rotary, int on UNUSED) { - if (pulse_state != PS_ONLINE) - { - DBG("## NOTIFY: Pulse is not online"); - return; - } + if (!prepare_notify()) + return; // Rotary touches switch meaning of LEDs, this is handled inside display updates if (rotary >= 4 && rotary < 8) @@ -541,18 +705,69 @@ void notify_touch(int rotary, int on UNUSED) /*** Main entry point ***/ -int main(int argc UNUSED, char **argv) +static int debug; +static int no_fork; + +static struct opt_section options = { + OPT_ITEMS { + OPT_HELP("Control console for the Ursary"), + OPT_HELP(""), + OPT_HELP("Options:"), + OPT_HELP_OPTION, + OPT_BOOL('d', "debug", debug, 0, "\tEnable debugging mode (no fork etc.)"), + OPT_BOOL(0, "no-fork", no_fork, 0, "\tDo not fork\n"), + OPT_END + } +}; + +static void sigterm_handler(struct main_signal *ms UNUSED) +{ + main_shut_down(); +} + +static void daemon_body(struct daemon_params *dp) { - log_init(argv[0]); main_init(); update_timer.handler = do_update; + usb_init(); noct_init(); pulse_init(); mpd_init(); - msg(L_DEBUG, "Entering main loop"); + static struct main_signal term_sig = { + .signum = SIGTERM, + .handler = sigterm_handler, + }; + signal_add(&term_sig); + + msg(L_INFO, "Ursary daemon starting"); main_loop(); + msg(L_INFO, "Ursary daemon shut down"); + daemon_exit(dp); +} + +int main(int argc UNUSED, char **argv) +{ + opt_parse(&options, argv+1); + unsetenv("DISPLAY"); + unsetenv("HOME"); + + struct daemon_params dp = { + .flags = ((debug || no_fork) ? DAEMON_FLAG_SIMULATE : 0), + .pid_file = "/run/ursaryd.pid", + .run_as_user = "ursary", + }; + daemon_init(&dp); + + log_init(argv[0]); + if (!debug) + { + struct log_stream *ls = log_new_syslog("daemon", LOG_PID); + ls->levels = ~(1U << L_DEBUG); + log_set_default_stream(ls); + } + daemon_run(&dp, daemon_body); return 0; }