From 4907155317262df70a2b4e7e81c1f4894b7e9793 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Thu, 8 May 2014 15:29:46 +0200 Subject: [PATCH] ctl: add sinkctl miracle-sinkctl is a very basic Miracast-Sink implementation. It is meant for debugging and as proof-of-concept. Signed-off-by: David Herrmann --- .gitignore | 1 + Makefile.am | 21 ++ src/ctl/ctl-sink.c | 462 +++++++++++++++++++++++++++++++++++ src/ctl/ctl.h | 19 ++ src/ctl/sinkctl.c | 590 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1093 insertions(+) create mode 100644 src/ctl/ctl-sink.c create mode 100644 src/ctl/sinkctl.c diff --git a/.gitignore b/.gitignore index 7c49b34..9802ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ configure libtool m4/ miracle-dhcp +miracle-sinkctl miracle-wifictl miracle-wifid miraclectl diff --git a/Makefile.am b/Makefile.am index 68675a9..8d35b78 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,6 +132,27 @@ miracle_wifictl_LDADD = \ $(DEPS_LIBS) miracle_wifictl_LDFLAGS = $(AM_LDFLAGS) +# +# miracle-sinkctl +# + +bin_PROGRAMS += miracle-sinkctl + +miracle_sinkctl_SOURCES = \ + src/ctl/ctl.h \ + src/ctl/ctl-cli.c \ + src/ctl/ctl-sink.c \ + src/ctl/ctl-wifi.c \ + src/ctl/sinkctl.c +miracle_sinkctl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) +miracle_sinkctl_LDADD = \ + libmiracle-shared.la \ + -lreadline \ + $(DEPS_LIBS) +miracle_sinkctl_LDFLAGS = $(AM_LDFLAGS) + # # miracle-dhcp # diff --git a/src/ctl/ctl-sink.c b/src/ctl/ctl-sink.c new file mode 100644 index 0000000..a031e9c --- /dev/null +++ b/src/ctl/ctl-sink.c @@ -0,0 +1,462 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" +#include "rtsp.h" +#include "shl_macro.h" +#include "shl_util.h" + +struct ctl_sink { + sd_event *event; + + char *target; + struct sockaddr_storage addr; + size_t addr_size; + int fd; + sd_event_source *fd_source; + + struct rtsp *rtsp; + + bool connected : 1; + bool hup : 1; +}; + +/* + * RTSP Session + */ + +static int sink_req_fn(struct rtsp *bus, struct rtsp_message *m, void *data) +{ + cli_debug("INCOMING: %s\n", rtsp_message_get_raw(m)); + return 0; +} + +static void sink_handle_options(struct ctl_sink *s, + struct rtsp_message *m) +{ + _rtsp_message_unref_ struct rtsp_message *rep = NULL; + int r; + + r = rtsp_message_new_reply_for(m, &rep, 200, NULL); + if (r < 0) + return cli_vERR(r); + + r = rtsp_message_append(rep, "", + "Public", + "org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER"); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_send(s->rtsp, rep); + if (r < 0) + return cli_vERR(r); + + rtsp_message_unref(rep); + rep = NULL; + + r = rtsp_message_new_request(s->rtsp, + &rep, + "OPTIONS", + "*"); + if (r < 0) + return cli_vERR(r); + + r = rtsp_message_append(rep, "", + "Require", + "org.wfa.wfd1.0"); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_call_async(s->rtsp, rep, sink_req_fn, NULL, 0, NULL); + if (r < 0) + return cli_vERR(r); +} + +static void sink_handle_get_parameter(struct ctl_sink *s, + struct rtsp_message *m) +{ + _rtsp_message_unref_ struct rtsp_message *rep = NULL; + int r; + + r = rtsp_message_new_reply_for(m, &rep, 200, NULL); + if (r < 0) + return cli_vERR(r); + + r = rtsp_message_append(rep, "{&&&&}", + "wfd_content_protection: none", + "wfd_video_formats: 00 00 01 01 0000007f 003fffff 00000000 00 0000 0000 00 none none", + "wfd_audio_codecs: LPCM 00000003 00", + "wfd_client_rtp_ports: RTP/AVP/UDP;unicast 1991 0 mode=play"); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_send(s->rtsp, rep); + if (r < 0) + return cli_vERR(r); +} + +static void sink_handle_set_parameter(struct ctl_sink *s, + struct rtsp_message *m) +{ + _rtsp_message_unref_ struct rtsp_message *rep = NULL; + const char *trigger; + int r; + + r = rtsp_message_new_reply_for(m, &rep, 200, NULL); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_send(s->rtsp, rep); + if (r < 0) + return cli_vERR(r); + + rtsp_message_unref(rep); + rep = NULL; + + r = rtsp_message_read(m, "{}", "wfd_trigger_method", &trigger); + if (r < 0) + return; + + if (!strcmp(trigger, "SETUP")) { + r = rtsp_message_new_request(s->rtsp, + &rep, + "SETUP", + "rtsp://localhost/wfd1.0/streamid=0"); + if (r < 0) + return cli_vERR(r); + + r = rtsp_message_append(rep, "", + "Transport", + "RTP/AVP/UDP;unicast;client_port=1991"); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_call_async(s->rtsp, rep, sink_req_fn, NULL, 0, NULL); + if (r < 0) + return cli_vERR(r); + + rtsp_message_unref(rep); + rep = NULL; + + r = rtsp_message_new_request(s->rtsp, + &rep, + "PLAY", + "rtsp://localhost/wfd1.0/streamid=0"); + if (r < 0) + return cli_vERR(r); + + rtsp_message_seal(rep); + cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); + + r = rtsp_call_async(s->rtsp, rep, sink_req_fn, NULL, 0, NULL); + if (r < 0) + return cli_vERR(r); + } +} + +static void sink_handle(struct ctl_sink *s, + struct rtsp_message *m) +{ + const char *method; + + cli_debug("INCOMING: %s\n", rtsp_message_get_raw(m)); + + method = rtsp_message_get_method(m); + if (!method) + return; + + if (!strcmp(method, "OPTIONS")) { + sink_handle_options(s, m); + } else if (!strcmp(method, "GET_PARAMETER")) { + sink_handle_get_parameter(s, m); + } else if (!strcmp(method, "SET_PARAMETER")) { + sink_handle_set_parameter(s, m); + } +} + +static int sink_rtsp_fn(struct rtsp *bus, + struct rtsp_message *m, + void *data) +{ + struct ctl_sink *s = data; + + if (!m) + s->hup = true; + else + sink_handle(s, m); + + if (s->hup) { + ctl_sink_close(s); + ctl_fn_sink_disconnected(s); + } + + return 0; +} + +/* + * Sink I/O + */ + +static void sink_connected(struct ctl_sink *s) +{ + int r, val; + socklen_t len; + + if (s->connected || s->hup) + return; + + sd_event_source_set_enabled(s->fd_source, SD_EVENT_OFF); + + len = sizeof(val); + r = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &val, &len); + if (r < 0) { + s->hup = true; + cli_vERRNO(); + return; + } else if (val) { + s->hup = true; + errno = val; + cli_error("cannot connect to remote host (%d): %m", + errno); + return; + } + + cli_debug("connection established"); + + r = rtsp_open(&s->rtsp, s->fd); + if (r < 0) + goto error; + + r = rtsp_attach_event(s->rtsp, s->event, 0); + if (r < 0) + goto error; + + r = rtsp_add_match(s->rtsp, sink_rtsp_fn, s); + if (r < 0) + goto error; + + s->connected = true; + ctl_fn_sink_connected(s); + return; + +error: + s->hup = true; + cli_vERR(r); +} + +static void sink_io(struct ctl_sink *s, uint32_t mask) +{ + if (mask & EPOLLOUT) + sink_connected(s); + + if (mask & (EPOLLHUP | EPOLLERR)) { + cli_notice("HUP/ERR on socket"); + s->hup = true; + } + + if (s->hup) { + ctl_sink_close(s); + ctl_fn_sink_disconnected(s); + } +} + +static int sink_io_fn(sd_event_source *source, + int fd, + uint32_t mask, + void *data) +{ + sink_io(data, mask); + return 0; +} + +static int sink_connect(struct ctl_sink *s) +{ + int fd, r; + + if (!s) + return cli_EINVAL(); + if (s->fd >= 0) + return 0; + if (!s->addr.ss_family || !s->addr_size) + return cli_EINVAL(); + + fd = socket(s->addr.ss_family, + SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0); + if (fd < 0) + return cli_ERRNO(); + + r = connect(fd, (struct sockaddr*)&s->addr, s->addr_size); + if (r < 0) { + r = -errno; + if (r != -EINPROGRESS) { + cli_vERR(r); + goto err_close; + } + } + + r = sd_event_add_io(s->event, + &s->fd_source, + fd, + EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET, + sink_io_fn, + s); + if (r < 0) { + cli_vERR(r); + goto err_close; + } + + s->fd = fd; + return 0; + +err_close: + close(fd); + return r; +} + +static void sink_close(struct ctl_sink *s) +{ + if (!s || s->fd < 0) + return; + + rtsp_remove_match(s->rtsp, sink_rtsp_fn, s); + rtsp_detach_event(s->rtsp); + rtsp_unref(s->rtsp); + s->rtsp = NULL; + sd_event_source_unref(s->fd_source); + s->fd_source = NULL; + close(s->fd); + s->fd = -1; + s->connected = false; + s->hup = false; +} + +/* + * Sink Management + */ + +int ctl_sink_new(struct ctl_sink **out, + sd_event *event) +{ + struct ctl_sink *s; + + if (!out || !event) + return cli_EINVAL(); + + s = calloc(1, sizeof(*s)); + if (!s) + return cli_ENOMEM(); + + s->event = sd_event_ref(event); + s->fd = -1; + + *out = s; + return 0; +} + +void ctl_sink_free(struct ctl_sink *s) +{ + if (!s) + return; + + ctl_sink_close(s); + free(s->target); + sd_event_unref(s->event); + free(s); +} + +int ctl_sink_connect(struct ctl_sink *s, const char *target) +{ + struct sockaddr_in addr = { }; + char *t; + int r; + + if (!s || !target || s->fd >= 0) + return cli_EINVAL(); + + addr.sin_family = AF_INET; + addr.sin_port = htons(7236); + r = inet_pton(AF_INET, target, &addr.sin_addr); + if (r != 1) + return cli_EINVAL(); + + t = strdup(target); + if (!t) + return cli_ENOMEM(); + + free(s->target); + s->target = t; + + memcpy(&s->addr, &addr, sizeof(addr)); + s->addr_size = sizeof(addr); + + return sink_connect(s); +} + +void ctl_sink_close(struct ctl_sink *s) +{ + if (!s) + return; + + sink_close(s); +} + +bool ctl_sink_is_connecting(struct ctl_sink *s) +{ + return s && s->fd >= 0 && !s->connected; +} + +bool ctl_sink_is_connected(struct ctl_sink *s) +{ + return s && s->connected; +} + +bool ctl_sink_is_closed(struct ctl_sink *s) +{ + return !s || s->fd < 0; +} diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index 4becca7..3d5356b 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include "shl_dlist.h" @@ -100,6 +102,20 @@ struct ctl_peer *ctl_wifi_find_peer(struct ctl_wifi *w, struct ctl_peer *ctl_wifi_search_peer(struct ctl_wifi *w, const char *real_label); +/* sink handling */ + +struct ctl_sink; + +int ctl_sink_new(struct ctl_sink **out, + sd_event *event); +void ctl_sink_free(struct ctl_sink *s); + +int ctl_sink_connect(struct ctl_sink *s, const char *target); +void ctl_sink_close(struct ctl_sink *s); +bool ctl_sink_is_connecting(struct ctl_sink *s); +bool ctl_sink_is_connected(struct ctl_sink *s); +bool ctl_sink_is_closed(struct ctl_sink *s); + /* CLI handling */ extern int cli_max_sev; @@ -240,6 +256,9 @@ void ctl_fn_peer_disconnected(struct ctl_peer *p); void ctl_fn_link_new(struct ctl_link *l); void ctl_fn_link_free(struct ctl_link *l); +void ctl_fn_sink_connected(struct ctl_sink *s); +void ctl_fn_sink_disconnected(struct ctl_sink *s); + void cli_fn_help(void); #endif /* CTL_CTL_H */ diff --git a/src/ctl/sinkctl.c b/src/ctl/sinkctl.c new file mode 100644 index 0000000..ef27c7a --- /dev/null +++ b/src/ctl/sinkctl.c @@ -0,0 +1,590 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" +#include "shl_macro.h" +#include "shl_util.h" + +static sd_bus *bus; +static struct ctl_wifi *wifi; +static struct ctl_sink *sink; +static sd_event_source *scan_timeout; +static sd_event_source *sink_timeout; +static unsigned int sink_timeout_time; +static bool sink_connected; + +static char *bound_link; +static struct ctl_link *running_link; +static struct ctl_peer *running_peer; + +/* + * cmd list + */ + +static int cmd_list(char **args, unsigned int n) +{ + size_t link_cnt = 0, peer_cnt = 0; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + + /* list links */ + + cli_printf("%6s %-24s %-30s\n", + "LINK", "INTERFACE", "FRIENDLY-NAME"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + ++link_cnt; + + cli_printf("%6s %-24s %-30s\n", + l->label, + shl_isempty(l->ifname) ? + "" : l->ifname, + shl_isempty(l->friendly_name) ? + "" : l->friendly_name); + } + + cli_printf("\n"); + + /* list peers */ + + cli_printf("%6s %-24s %-30s %-10s\n", + "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + + shl_dlist_for_each(j, &l->peers) { + p = peer_from_dlist(j); + ++peer_cnt; + + cli_printf("%6s %-24s %-30s %-10s\n", + p->l->label, + p->label, + shl_isempty(p->friendly_name) ? + "" : p->friendly_name, + p->connected ? "yes" : "no"); + } + } + + cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); + + return 0; +} + +/* + * cmd: show + */ + +static int cmd_show(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + struct ctl_peer *p = NULL; + + if (n > 0) { + if (!(l = ctl_wifi_find_link(wifi, args[0])) && + !(p = ctl_wifi_find_peer(wifi, args[0])) && + !(l = ctl_wifi_search_link(wifi, args[0])) && + !(p = ctl_wifi_search_peer(wifi, args[0]))) { + cli_error("unknown link or peer %s", args[0]); + return 0; + } + } + + if (l) { + cli_printf("Link=%s\n", l->label); + if (l->ifindex > 0) + cli_printf("InterfaceIndex=%u\n", l->ifindex); + if (l->ifname && *l->ifname) + cli_printf("InterfaceName=%s\n", l->ifname); + if (l->friendly_name && *l->friendly_name) + cli_printf("FriendlyName=%s\n", l->friendly_name); + cli_printf("P2PScanning=%d\n", l->p2p_scanning); + if (l->wfd_subelements && *l->wfd_subelements) + cli_printf("WfdSubelements=%s\n", l->wfd_subelements); + } else if (p) { + cli_printf("Peer=%s\n", p->label); + if (p->p2p_mac && *p->p2p_mac) + cli_printf("P2PMac=%s\n", p->p2p_mac); + if (p->friendly_name && *p->friendly_name) + cli_printf("FriendlyName=%s\n", p->friendly_name); + cli_printf("Connected=%d\n", p->connected); + if (p->interface && *p->interface) + cli_printf("Interface=%s\n", p->interface); + if (p->local_address && *p->local_address) + cli_printf("LocalAddress=%s\n", p->local_address); + if (p->remote_address && *p->remote_address) + cli_printf("RemoteAddress=%s\n", p->remote_address); + if (p->wfd_subelements && *p->wfd_subelements) + cli_printf("WfdSubelements=%s\n", p->wfd_subelements); + } else { + cli_printf("Show what?\n"); + return 0; + } + + return 0; +} + +/* + * cmd: run + */ + +static void run_on(struct ctl_link *l) +{ + if (running_link) + return; + + running_link = l; + ctl_link_set_wfd_subelements(l, "000600111c4400c8"); + ctl_link_set_p2p_scanning(l, true); + cli_printf("now running on link %s\n", running_link->label); +} + +static int cmd_run(char **args, unsigned int n) +{ + struct ctl_link *l; + + if (running_link) { + cli_error("already running on %s", running_link->label); + return 0; + } + + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + run_on(l); + + return 0; +} + +/* + * cmd: bind + */ + +static int cmd_bind(char **args, unsigned int n) +{ + struct ctl_link *l; + char *t; + + if (running_link) { + cli_error("already running on %s", running_link->label); + return 0; + } + + t = strdup(args[0]); + if (!t) + cli_vENOMEM(); + + free(bound_link); + bound_link = t; + + l = ctl_wifi_search_link(wifi, bound_link); + if (!l) + return 0; + + run_on(l); + + return 0; +} + +/* + * cmd: quit/exit + */ + +static int cmd_quit(char **args, unsigned int n) +{ + cli_exit(); + return 0; +} + +/* + * main + */ + +static void schedule_timeout(sd_event_source **out, + uint64_t rel_usec, + sd_event_time_handler_t timeout_fn, + void *data) +{ + int r; + + rel_usec += shl_now(CLOCK_MONOTONIC); + + if (*out) { + r = sd_event_source_set_time(*out, rel_usec); + if (r < 0) + cli_vERR(r); + } else { + r = sd_event_add_monotonic(cli_event, + out, + rel_usec, + 0, + timeout_fn, + data); + if (r < 0) + cli_vERR(r); + } +} + +static void stop_timeout(sd_event_source **out) +{ + if (*out) { + sd_event_source_set_enabled(*out, SD_EVENT_OFF); + sd_event_source_unref(*out); + *out = NULL; + } +} + +static int scan_timeout_fn(sd_event_source *s, uint64_t usec, void *data) +{ + stop_timeout(&scan_timeout); + + if (running_link) + ctl_link_set_p2p_scanning(running_link, true); + + return 0; +} + +static int sink_timeout_fn(sd_event_source *s, uint64_t usec, void *data) +{ + int r; + + stop_timeout(&sink_timeout); + + if (running_peer && + running_peer->connected && + ctl_sink_is_closed(sink)) { + r = ctl_sink_connect(sink, running_peer->remote_address); + if (r < 0) { + if (sink_timeout_time++ >= 3) + cli_vERR(r); + else + schedule_timeout(&sink_timeout, + sink_timeout_time * 1000ULL * 1000ULL, + sink_timeout_fn, + NULL); + } + } + + return 0; +} + +static const struct cli_cmd cli_cmds[] = { + { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, + { "show", "", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, + { "run", "", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link" }, + { "bind", "", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged" }, + { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, + { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, + { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, + { }, +}; + +void ctl_fn_sink_connected(struct ctl_sink *s) +{ + cli_notice("SINK connected"); + sink_connected = true; +} + +void ctl_fn_sink_disconnected(struct ctl_sink *s) +{ + if (!sink_connected) { + /* treat HUP as timeout */ + sink_timeout_fn(sink_timeout, 0, NULL); + } else { + cli_notice("SINK disconnected"); + sink_connected = false; + } +} + +void ctl_fn_peer_new(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_free(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (p == running_peer) { + cli_printf("no longer running on peer %s\n", + running_peer->label); + stop_timeout(&sink_timeout); + ctl_sink_close(sink); + running_peer = NULL; + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } + + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_provision_discovery(struct ctl_peer *p, + const char *prov, + const char *pin) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_YELLOW "PROV" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n", + p->label, prov, pin); + + if (!running_peer) { + /* auto accept any incoming connection attempt */ + ctl_peer_connect(p, "auto", ""); + + /* 60s timeout in case the connect fails. Yes, stupid wpas does + * not catch this and notify us.. and as it turns out, DHCP + * negotiation with some proprietary devices can take up to 30s + * so lets be safe. */ + schedule_timeout(&scan_timeout, + 60 * 1000ULL * 1000ULL, + scan_timeout_fn, + NULL); + } +} + +void ctl_fn_peer_connected(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_GREEN "CONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); + + if (!running_peer) { + running_peer = p; + cli_printf("now running on peer %s\n", running_peer->label); + stop_timeout(&scan_timeout); + + sink_connected = false; + sink_timeout_time = 1; + schedule_timeout(&sink_timeout, + sink_timeout_time * 1000ULL * 1000ULL, + sink_timeout_fn, + NULL); + } +} + +void ctl_fn_peer_disconnected(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (p == running_peer) { + cli_printf("no longer running on peer %s\n", + running_peer->label); + stop_timeout(&sink_timeout); + ctl_sink_close(sink); + running_peer = NULL; + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } + + if (cli_running()) + cli_printf("[" CLI_YELLOW "DISCONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_link_new(struct ctl_link *l) +{ + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Link: %s\n", + l->label); + + /* If we're not running but have a bound link, try to find it now and + * start running if the link is now available. */ + if (!running_link && bound_link) { + l = ctl_wifi_search_link(wifi, bound_link); + if (l) + run_on(l); + } +} + +void ctl_fn_link_free(struct ctl_link *l) +{ + if (l == running_link) { + cli_printf("no longer running on link %s\n", + running_link->label); + running_link = NULL; + stop_timeout(&scan_timeout); + } + + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Link: %s\n", + l->label); +} + +void cli_fn_help() +{ + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + printf("%s [OPTIONS...] ...\n\n" + "Control a dedicated local sink via MiracleCast.\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + "\n" + "Commands:\n" + , program_invocation_short_name); + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ +} + +static int ctl_interactive(char **argv, int argc) +{ + int r; + + r = cli_init(bus, cli_cmds); + if (r < 0) + return r; + + r = ctl_sink_new(&sink, cli_event); + if (r < 0) + goto error; + + r = ctl_wifi_fetch(wifi); + if (r < 0) + goto error; + + if (argc > 0) { + r = cli_do(cli_cmds, argv, argc); + if (r == -EAGAIN) + cli_error("unknown operation %s", argv[0]); + } + + r = cli_run(); + +error: + ctl_sink_free(sink); + cli_destroy(); + return r; +} + +static int ctl_main(int argc, char *argv[]) +{ + struct shl_dlist *i; + struct ctl_link *l; + int r, left; + + r = ctl_wifi_new(&wifi, bus); + if (r < 0) + return r; + + left = argc - optind; + r = ctl_interactive(argv + optind, left <= 0 ? 0 : left); + + /* stop all scans */ + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + if (l->have_p2p_scan) + ctl_link_set_p2p_scanning(l, false); + } + + ctl_wifi_free(wifi); + return r; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return cli_help(cli_cmds); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + cli_max_sev = atoi(optarg); + break; + case '?': + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = sd_bus_default_system(&bus); + if (r < 0) { + cli_error("cannot connect to system bus: %s", strerror(-r)); + return EXIT_FAILURE; + } + + r = ctl_main(argc, argv); + sd_bus_unref(bus); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +}