You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

354 lines
8.7 KiB

/*
Copyright Jeroen Vreeken (jeroen@vreeken.net), 2020
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <dml/dml_host.h>
#include <dml_config.h>
#include <gst/gst.h>
#include <gtk/gtk.h>
#include <string.h>
#define debug(...) printf(__VA_ARGS__)
static GtkWidget *window;
static GtkWidget *streamlist;
GtkWidget *combo_audio_sink;
struct dml_stream_priv {
GtkWidget *label;
};
struct device_list {
GstDevice *device;
char *name;
struct device_list *next;
};
struct device_list *devices_audio_sink = NULL;
gint streamlist_sort(GtkListBoxRow *row1, GtkListBoxRow *row2, gpointer user_data)
{
/* Do a sort based on domain, so alphabetical, but working backwards per dot. */
struct dml_stream *ds1 = g_object_get_data(G_OBJECT(row1), "dml_stream");
struct dml_stream *ds2 = g_object_get_data(G_OBJECT(row2), "dml_stream");
char *name1 = dml_stream_name_get(ds1);
char *name2 = dml_stream_name_get(ds2);
size_t p1 = strlen(name1) - 2;
size_t p2 = strlen(name2) - 2;
while (p1 >= 0 && p2 >= 0) {
if (name1[p1] == '.')
p1--;
if (name2[p2] == '.')
p2--;
for (; p1; p1--) {
if (name1[p1] == '.') {
break;
}
}
for (; p2; p2--) {
if (name2[p2] == '.') {
break;
}
}
char *suf1 = name1 + p1;
char *suf2 = name2 + p2;
int r = strcmp(suf1, suf2);
if (r)
return r;
if (p1 == 0 || p2 == 0) {
return p1 - p2;
}
}
return 0;
}
static void stream_added_cb(struct dml_host *host, struct dml_stream *ds, void *arg)
{
struct dml_stream_priv *priv = calloc(1, sizeof(*priv));
char *mime, *name, *alias, *description;
dml_stream_priv_set(ds, priv);
mime = dml_stream_mime_get(ds);
name = dml_stream_name_get(ds);
alias = dml_stream_alias_get(ds);
description = dml_stream_description_get(ds);
char *lstr;
asprintf(&lstr, "%s [%s]: %s (%s)", name, alias, description, mime);
priv->label = gtk_label_new(lstr);
gtk_label_set_xalign(GTK_LABEL(priv->label), 0.0);
GtkWidget *row = gtk_list_box_row_new();
g_object_set_data (G_OBJECT(row), "dml_stream", ds);
gtk_container_add(GTK_CONTAINER(row), priv->label);
gtk_container_add(GTK_CONTAINER(streamlist), row);
gtk_widget_show_all(window);
debug("%s\n", lstr);
free(lstr);
}
void stream_removed_cb(struct dml_host *host, struct dml_stream *ds, void *arg)
{
struct dml_stream_priv *priv = dml_stream_priv_get(ds);
gtk_widget_destroy(priv->label);
free(priv);
}
void receive_clicked(GtkButton *button, gpointer data)
{
GtkListBoxRow *selrow = gtk_list_box_get_selected_row(GTK_LIST_BOX(streamlist));
if (!selrow) {
debug("nothing selected\n");
return;
}
struct dml_stream *ds = g_object_get_data(G_OBJECT(selrow), "dml_stream");
char *name = dml_stream_name_get(ds);
debug("Selected: %s\n", name);
}
void connect_clicked(GtkButton *button, gpointer data)
{
GtkListBoxRow *selrow = gtk_list_box_get_selected_row(GTK_LIST_BOX(streamlist));
if (!selrow) {
debug("nothing selected\n");
return;
}
struct dml_stream *ds = g_object_get_data(G_OBJECT(selrow), "dml_stream");
char *name = dml_stream_name_get(ds);
debug("Selected: %s\n", name);
}
static void device_changed(void)
{
gchar *active_name = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT(combo_audio_sink));
if (active_name) {
dml_config_set("device_audio_sink", active_name);
}
g_free(active_name);
}
static void device_update_list(void)
{
struct device_list *entry;
int active = -1;
char *config_name = dml_config_value("device_audio_sink", NULL, NULL);
gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(combo_audio_sink));
int i;
for (i = 0, entry = devices_audio_sink; entry; entry = entry->next, i++) {
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_audio_sink), NULL, entry->name);
if (config_name && !strcmp(config_name, entry->name)) {
active = i;
}
}
if (active >= 0) {
gtk_combo_box_set_active(GTK_COMBO_BOX(combo_audio_sink), active);
}
gtk_widget_show_all(combo_audio_sink);
}
int create_window(int *argc, char ***argv)
{
/*
+------------------------------------+
| hbox |
| +------------+ +-----------------+ |
| | controlbox | | streamscroll | |
| | | | +-------------+ | |
| | | | | streamlist | | |
| | | | +-------------+ | |
| +------------+ +-----------------+ |
+------------------------------------+
*/
gtk_init(argc, argv);
/* Create the main, top level window */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "gdml");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 500);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget *controlbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
streamlist = gtk_list_box_new();
gtk_list_box_set_sort_func (GTK_LIST_BOX(streamlist), streamlist_sort, NULL, NULL);
GtkWidget *streamscroll = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(streamscroll), streamlist);
gtk_box_pack_end (GTK_BOX(hbox), streamscroll, true, true, 0);
GtkWidget *button_receive = gtk_button_new_with_label("Receive");
g_signal_connect (button_receive, "clicked", G_CALLBACK(connect_clicked), NULL);
gtk_box_pack_start (GTK_BOX(controlbox), button_receive, false, false, 0);
GtkWidget *button_connect = gtk_button_new_with_label("Connect");
g_signal_connect (button_connect, "clicked", G_CALLBACK(connect_clicked), NULL);
gtk_box_pack_start (GTK_BOX(controlbox), button_connect, false, false, 0);
GtkWidget *label = gtk_label_new("Audio sink");
gtk_box_pack_start (GTK_BOX(controlbox), label, false, false, 0);
combo_audio_sink = gtk_combo_box_text_new();
g_signal_connect(combo_audio_sink, "changed", G_CALLBACK(device_changed), NULL);
gtk_box_pack_start (GTK_BOX(controlbox), combo_audio_sink, false, false, 0);
gtk_box_pack_start (GTK_BOX(hbox), controlbox, false, false, 0);
gtk_container_add(GTK_CONTAINER(window), hbox);
gtk_widget_show_all(window);
debug("window created\n");
return 0;
}
static gboolean gst_monitor_cb(GstBus *bus, GstMessage *message, gpointer user_data)
{
GstDevice *device;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_DEVICE_ADDED:
gst_message_parse_device_added(message, &device);
gchar *class = gst_device_get_device_class(device);
debug("Device added: %p, class: '%s'\n", device, class);
if (!strcmp(class, "Audio/Sink")) {
debug("Add audio sink\n");
struct device_list *entry = calloc(1, sizeof(struct device_list));
struct device_list **end;
if (!entry)
break;
entry->name = gst_device_get_display_name(device);
entry->device = device;
for (end = &devices_audio_sink; *end; end = &(*end)->next);
*end = entry;
device_update_list();
}
g_free(class);
break;
case GST_MESSAGE_DEVICE_REMOVED: {
gst_message_parse_device_removed (message, &device);
debug("Device removed: %p\n", device);
struct device_list **ep;
for (ep = &devices_audio_sink; *ep; ep = &(*ep)->next) {
if ((*ep)->device == device) {
struct device_list *entry = *ep;
g_free(entry->name);
*ep = entry->next;
free(entry);
device_update_list();
break;
}
}
break;
}
default:
break;
}
return G_SOURCE_CONTINUE;
}
int create_media(int *argc, char ***argv)
{
gst_init(argc, argv);
GstDeviceMonitor *gst_mon = gst_device_monitor_new();
GstBus *gst_mon_bus = gst_device_monitor_get_bus(gst_mon);
gst_bus_add_watch(gst_mon_bus, gst_monitor_cb, NULL);
gst_object_unref(gst_mon_bus);
if (!gst_device_monitor_start(gst_mon)) {
debug("Could not start device monitor\n");
}
return 0;
}
int main (int argc, char **argv)
{
create_window(&argc, &argv);
create_media(&argc, &argv);
struct dml_host *host;
host = dml_host_create(NULL);
if (!host) {
debug("Could not create host\n");
return -1;
}
dml_host_stream_added_cb_set(host, stream_added_cb, NULL);
dml_host_stream_removed_cb_set(host, stream_removed_cb, NULL);
debug("Start main loop\n");
gtk_main();
debug("Saving config\n");
dml_config_save(NULL);
return 0;
}