X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=ursaryd.c;h=e28cebfa4cd2060c8764c053e45c6e53be3d1a62;hb=9462e20b38064dc32852b1f60ad590d65e943f60;hp=c8a87a2ed3fda4d207ff47470214a6ea51d9a15e;hpb=4d0bcfb647d3a22df51a1c15df105db69a3bf2d5;p=ursary.git diff --git a/ursaryd.c b/ursaryd.c index c8a87a2..e28cebf 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -1,21 +1,48 @@ /* * The Ursary Audio Controls * - * (c) 2014 Martin Mares + * (c) 2014--2020 Martin Mares */ #define LOCAL_DEBUG #include #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 desk brightness - desk lights on + * 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 light color temperature + */ + +#define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo" /*** Sink controls ***/ @@ -51,6 +78,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 +120,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 void calc_clients(void) +static struct group_state group_state[NUM_GROUPS]; + +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 +190,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 +267,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,49 +293,415 @@ 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_GROUPS; i++) + if (group_config[i].enabled && noct_rotary_touched[i]) + { + if (touched >= 0) + return -1; + touched = i; + } + 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_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; + if (!sink) + sink = ss; + else if (strcmp(sink, ss)) + sink = "?"; + } + return sink ? : "?"; +} + +static void update_default_sink(void) +{ + int i = find_touched_client(); + const char *sink; + if (i >= 0) + sink = get_client_sink(i); + else + sink = pulse_default_sink_name ? : "?"; + + if (!strcmp(sink, "ursarium")) + { + 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); + } +} + +static void update_default_sink_from_button(int button, int on) +{ + if (!on) + return; + + int i = find_touched_client(); + const char *sink; + if (i >= 0) + sink = get_client_sink(i); + else + sink = pulse_default_sink_name ? : "?"; + + const char *switch_to = NULL; + if (button == 8) + { + if (!strcmp(sink, "ursarium")) + switch_to = "burrow"; + else + switch_to = "ursarium"; + } + else if (button == 9) + { + if (!strcmp(sink, "catarium")) + switch_to = "burrow"; + else + switch_to = "catarium"; + } + else if (button == 10) + { + if (!strcmp(sink, "compress")) + switch_to = "burrow"; + else + switch_to = "compress"; + } + + if (!switch_to) + return; + + if (i >= 0) + { + struct pulse_sink *sk = pulse_sink_by_name(switch_to); + if (!sk) + return; + + CLIST_FOR_EACH(struct pulse_sink_input *, s, pulse_sink_input_list) + 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); + } + } + else + { + DBG("Switching default sink to %s", switch_to); + pulse_server_set_default_sink(switch_to); + } +} + +#endif + +/*** MPD controls ***/ + +static bool mpd_flash_state; + +static void mpd_flash_timeout(struct main_timer *t) +{ + mpd_flash_state ^= 1; + noct_set_button(12, mpd_flash_state); + timer_add_rel(t, 500); +} + +static struct main_timer mpd_flash_timer = { + .handler = mpd_flash_timeout, +}; + +static void update_mpd(void) +{ + const char *state = mpd_get_player_state(); + if (!strcmp(state, "play")) + { + noct_set_button(12, 1); + timer_del(&mpd_flash_timer); + } + else if (!strcmp(state, "pause")) + { + if (!timer_is_active(&mpd_flash_timer)) + { + mpd_flash_state = 1; + mpd_flash_timeout(&mpd_flash_timer); + } + } + else + { + noct_set_button(12, 0); + timer_del(&mpd_flash_timer); + } +} + +static void mpd_button_timeout(struct main_timer *t) +{ + DBG("MPD stop"); + timer_del(t); + mpd_stop(); +} + +static void update_mpd_from_button(int button UNUSED, int on) +{ + static struct main_timer mpd_button_timer = { + .handler = mpd_button_timeout, + }; + + const char *state = mpd_get_player_state(); + + if (!on) + { + if (timer_is_active(&mpd_button_timer)) + { + timer_del(&mpd_button_timer); + if (!strcmp(state, "play")) + { + DBG("MPD pause"); + mpd_pause(1); + } + else if (!strcmp(state, "pause")) + { + DBG("MPD resume"); + mpd_pause(0); + } + } + return; + } + + if (!strcmp(state, "stop")) + { + DBG("MPD play"); + mpd_play(); + } + else + { + DBG("MPD starting button timer"); + timer_add_rel(&mpd_button_timer, 1000); + } +} + +/*** Lights ***/ + +static bool lights_on[2]; +static double lights_brightness[2]; +static double lights_temperature[2]; + +static void update_lights(void) +{ + if (!dmx_is_ready()) + { + noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f); + noct_set_button(11, 0); + return; + } + + for (uint i=0; i<1; i++) + { + uint warm, cold; + if (lights_on[i]) + { + noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127); + noct_set_button(11, 1); + double r = 2; + double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1); + double t = lights_temperature[i]; + double w = 2*x*(1-t); + double c = 2*x*t; + warm = CLAMP((int)(255*w), 0, 255); + cold = CLAMP((int)(255*c), 0, 255); + } + else + { + noct_set_ring(3, RING_MODE_LEFT, 0); + noct_set_button(11, 0); + warm = cold = 0; + } + DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold); + dmx_set_pwm(2*i, warm); + dmx_set_pwm(2*i+1, cold); + } +} + +static void update_lights_from_rotary(int ch, int delta) +{ + if (lights_on[ch]) + lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.); + update_lights(); +} + +static void update_lights_from_slider(int value) +{ + lights_temperature[0] = value / 127.; + update_lights(); +} + +static void lights_button_timeout(struct main_timer *t) +{ + int ch = (uintptr_t) t->data; + DBG("Lights[%d]: Full throttle!", ch); + timer_del(t); + lights_on[ch] = 1; + lights_brightness[ch] = 1; + update_lights(); +} + +static void update_lights_from_button(int ch, int on) +{ + static struct main_timer lights_button_timer[2] = {{ + .handler = lights_button_timeout, + .data = (void *)(uintptr_t) 0, + },{ + .handler = lights_button_timeout, + .data = (void *)(uintptr_t) 1, + }}; + + if (on) + { + lights_on[ch] = !lights_on[ch]; + update_lights(); + timer_add_rel(&lights_button_timer[ch], 1000); + } + else + timer_del(&lights_button_timer[ch]); +} + /*** 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 + + // 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); + } + } - update_ring_from_sink(0, "ursarium"); - update_ring_from_sink(1, "catarium"); - update_clients(); + // 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(); + update_lights(); } void schedule_update(void) @@ -275,65 +709,165 @@ 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"); + update_sink_from_rotary(delta, PCH_SINK); break; - case 1: - update_sink_from_rotary(delta, "catarium"); + case 3: + update_lights_from_rotary(0, delta); break; - case 8: - update_sink_from_rotary(delta, "ursarium"); - update_sink_from_rotary(delta, "catarium"); + case 9: + update_lights_from_slider(delta); 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"); + update_sink_mute_from_button(on, PCH_SINK); break; - case 1: - update_sink_mute_from_button(on, "catarium"); + 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 11: + update_lights_from_button(0, on); + break; + 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 UNUSED, int on UNUSED) +{ + if (!prepare_notify()) + return; + +#if 0 + // Rotary touches switch meaning of LEDs, this is handled inside display updates + if (rotary >= 4 && rotary < 8) + schedule_update(); +#endif +} + /*** 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(); + dmx_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; }