917 lines
26 KiB
C
917 lines
26 KiB
C
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "gtkhelper.h"
|
|
#include "iface-mixer.h"
|
|
#include "routing-drag-line.h"
|
|
#include "routing-lines.h"
|
|
#include "stringhelper.h"
|
|
#include "widget-boolean.h"
|
|
#include "window-mixer.h"
|
|
#include "window-routing.h"
|
|
|
|
static void get_routing_srcs(struct alsa_card *card) {
|
|
struct alsa_elem *elem = card->sample_capture_elem;
|
|
|
|
int count = alsa_get_item_count(elem);
|
|
card->routing_srcs = g_array_new(
|
|
FALSE, TRUE, sizeof(struct routing_src)
|
|
);
|
|
g_array_set_size(card->routing_srcs, count);
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
char *name = alsa_get_item_name(elem, i);
|
|
|
|
struct routing_src *r = &g_array_index(
|
|
card->routing_srcs, struct routing_src, i
|
|
);
|
|
r->card = card;
|
|
r->id = i;
|
|
|
|
if (strncmp(name, "Mix", 3) == 0)
|
|
r->port_category = PC_MIX;
|
|
else if (strncmp(name, "PCM", 3) == 0)
|
|
r->port_category = PC_PCM;
|
|
else
|
|
r->port_category = PC_HW;
|
|
|
|
r->name = name;
|
|
r->lr_num =
|
|
r->port_category == PC_MIX
|
|
? name[4] - 'A' + 1
|
|
: get_num_from_string(name);
|
|
|
|
r->port_num = card->routing_in_count[r->port_category]++;
|
|
}
|
|
|
|
assert(card->routing_in_count[PC_MIX] <= MAX_MIX_OUT);
|
|
}
|
|
|
|
static void get_routing_dsts(struct alsa_card *card) {
|
|
GArray *elems = card->elems;
|
|
|
|
int count = 0;
|
|
|
|
// count and label routing dsts
|
|
for (int i = 0; i < elems->len; i++) {
|
|
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
|
|
|
if (!elem->card)
|
|
continue;
|
|
|
|
if (!is_elem_routing_dst(elem))
|
|
continue;
|
|
|
|
int i = get_num_from_string(elem->name);
|
|
if (i < 0)
|
|
continue;
|
|
|
|
elem->lr_num = i;
|
|
count++;
|
|
}
|
|
|
|
// create an array of routing dsts pointing to those elements
|
|
card->routing_dsts = g_array_new(
|
|
FALSE, TRUE, sizeof(struct routing_dst)
|
|
);
|
|
g_array_set_size(card->routing_dsts, count);
|
|
|
|
// count through card->rounting_dsts
|
|
int j = 0;
|
|
|
|
for (int i = 0; i < elems->len; i++) {
|
|
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
|
|
|
|
if (!elem->lr_num)
|
|
continue;
|
|
|
|
struct routing_dst *r = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, j
|
|
);
|
|
r->idx = j;
|
|
j++;
|
|
r->elem = elem;
|
|
if (strncmp(elem->name, "Mixer Input", 11) == 0) {
|
|
r->port_category = PC_MIX;
|
|
} else if (strncmp(elem->name, "PCM", 3) == 0) {
|
|
r->port_category = PC_PCM;
|
|
} else if (strstr(elem->name, "Playback Enum")) {
|
|
r->port_category = PC_HW;
|
|
} else {
|
|
printf("unknown mixer routing elem %s\n", elem->name);
|
|
continue;
|
|
}
|
|
r->port_num = card->routing_out_count[r->port_category]++;
|
|
}
|
|
|
|
assert(j == count);
|
|
}
|
|
|
|
static void routing_grid_label(char *s, GtkGrid *g) {
|
|
GtkWidget *l = gtk_label_new(s);
|
|
gtk_grid_attach(g, l, 0, 0, 1, 1);
|
|
}
|
|
|
|
// clear all the routing destinations
|
|
static void routing_preset_clear(struct alsa_card *card) {
|
|
for (int i = 0; i < card->routing_dsts->len; i++) {
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, i
|
|
);
|
|
|
|
alsa_set_elem_value(r_dst->elem, 0);
|
|
}
|
|
}
|
|
|
|
static void routing_preset_link(
|
|
struct alsa_card *card,
|
|
int src_port_category,
|
|
int src_mod,
|
|
int dst_port_category
|
|
) {
|
|
|
|
// find the first src port with the selected port category
|
|
int start_src_idx;
|
|
for (start_src_idx = 1;
|
|
start_src_idx < card->routing_srcs->len;
|
|
start_src_idx++) {
|
|
struct routing_src *r_src = &g_array_index(
|
|
card->routing_srcs, struct routing_src, start_src_idx
|
|
);
|
|
|
|
if (r_src->port_category == src_port_category)
|
|
break;
|
|
}
|
|
|
|
// find the first dst port with the selected port category
|
|
int dst_idx;
|
|
for (dst_idx = 0;
|
|
dst_idx < card->routing_dsts->len;
|
|
dst_idx++) {
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, dst_idx
|
|
);
|
|
|
|
if (r_dst->port_category == dst_port_category)
|
|
break;
|
|
}
|
|
|
|
// start assigning
|
|
int src_idx = start_src_idx;
|
|
int src_count = 0;
|
|
while (src_idx < card->routing_srcs->len &&
|
|
dst_idx < card->routing_dsts->len) {
|
|
|
|
// stop if no more of the selected src port category
|
|
struct routing_src *r_src = &g_array_index(
|
|
card->routing_srcs, struct routing_src, src_idx
|
|
);
|
|
if (r_src->port_category != src_port_category)
|
|
break;
|
|
|
|
// stop if no more of the selected dst port category
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, dst_idx
|
|
);
|
|
if (r_dst->port_category != dst_port_category)
|
|
break;
|
|
|
|
// do the assignment
|
|
alsa_set_elem_value(r_dst->elem, r_src->id);
|
|
|
|
// get the next index
|
|
src_idx++;
|
|
src_count++;
|
|
dst_idx++;
|
|
|
|
if (src_count == src_mod) {
|
|
src_idx = start_src_idx;
|
|
src_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void routing_preset_direct(struct alsa_card *card) {
|
|
routing_preset_link(card, PC_HW, 0, PC_PCM);
|
|
routing_preset_link(card, PC_PCM, 0, PC_HW);
|
|
}
|
|
|
|
static void routing_preset_preamp(struct alsa_card *card) {
|
|
routing_preset_link(card, PC_HW, 0, PC_HW);
|
|
}
|
|
|
|
static void routing_preset_stereo_out(struct alsa_card *card) {
|
|
routing_preset_link(card, PC_PCM, 2, PC_HW);
|
|
}
|
|
|
|
static void routing_preset(
|
|
GSimpleAction *action,
|
|
GVariant *value,
|
|
struct alsa_card *card
|
|
) {
|
|
const char *s = g_variant_get_string(value, NULL);
|
|
|
|
if (strcmp(s, "clear") == 0) {
|
|
routing_preset_clear(card);
|
|
} else if (strcmp(s, "direct") == 0) {
|
|
routing_preset_direct(card);
|
|
} else if (strcmp(s, "preamp") == 0) {
|
|
routing_preset_preamp(card);
|
|
} else if (strcmp(s, "stereo_out") == 0) {
|
|
routing_preset_stereo_out(card);
|
|
}
|
|
}
|
|
|
|
static GtkWidget *make_preset_menu_button(struct alsa_card *card) {
|
|
GMenu *menu = g_menu_new();
|
|
|
|
g_menu_append(menu, "Clear", "routing.preset('clear')");
|
|
g_menu_append(menu, "Direct", "routing.preset('direct')");
|
|
g_menu_append(menu, "Preamp", "routing.preset('preamp')");
|
|
g_menu_append(menu, "Stereo Out", "routing.preset('stereo_out')");
|
|
|
|
GtkWidget *button = gtk_menu_button_new();
|
|
gtk_menu_button_set_label(GTK_MENU_BUTTON(button), "Presets");
|
|
gtk_menu_button_set_menu_model(
|
|
GTK_MENU_BUTTON(button),
|
|
G_MENU_MODEL(menu)
|
|
);
|
|
|
|
GSimpleActionGroup *action_group = g_simple_action_group_new();
|
|
GSimpleAction *action = g_simple_action_new_stateful(
|
|
"preset", G_VARIANT_TYPE_STRING, NULL
|
|
);
|
|
g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action));
|
|
g_signal_connect(
|
|
action, "activate", G_CALLBACK(routing_preset), card
|
|
);
|
|
gtk_widget_insert_action_group(
|
|
button, "routing", G_ACTION_GROUP(action_group)
|
|
);
|
|
|
|
return button;
|
|
}
|
|
|
|
static void create_routing_grid(struct alsa_card *card) {
|
|
GtkWidget *routing_grid = card->routing_grid = gtk_grid_new();
|
|
|
|
GtkWidget *preset_menu_button = make_preset_menu_button(card);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), preset_menu_button, 0, 0, 1, 1
|
|
);
|
|
|
|
card->routing_hw_in_grid = gtk_grid_new();
|
|
card->routing_pcm_in_grid = gtk_grid_new();
|
|
card->routing_pcm_out_grid = gtk_grid_new();
|
|
card->routing_hw_out_grid = gtk_grid_new();
|
|
card->routing_mixer_in_grid = gtk_grid_new();
|
|
card->routing_mixer_out_grid = gtk_grid_new();
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_hw_in_grid, 0, 1, 1, 1
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_pcm_in_grid, 0, 2, 1, 1
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_pcm_out_grid, 2, 1, 1, 1
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_hw_out_grid, 2, 2, 1, 1
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_mixer_in_grid, 1, 0, 1, 1
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(routing_grid), card->routing_mixer_out_grid, 1, 3, 1, 1
|
|
);
|
|
gtk_widget_set_margin(routing_grid, 10);
|
|
gtk_grid_set_spacing(GTK_GRID(routing_grid), 10);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_hw_in_grid), 2);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_pcm_in_grid), 2);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_pcm_out_grid), 2);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_hw_out_grid), 2);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_mixer_in_grid), 2);
|
|
gtk_grid_set_spacing(GTK_GRID(card->routing_mixer_out_grid), 10);
|
|
gtk_grid_set_row_spacing(GTK_GRID(card->routing_mixer_out_grid), 2);
|
|
gtk_grid_set_column_spacing(GTK_GRID(card->routing_mixer_out_grid), 10);
|
|
gtk_widget_set_vexpand(card->routing_hw_in_grid, TRUE);
|
|
gtk_widget_set_vexpand(card->routing_pcm_in_grid, TRUE);
|
|
gtk_widget_set_vexpand(card->routing_pcm_out_grid, TRUE);
|
|
gtk_widget_set_vexpand(card->routing_hw_out_grid, TRUE);
|
|
gtk_widget_set_hexpand(card->routing_mixer_in_grid, TRUE);
|
|
gtk_widget_set_hexpand(card->routing_mixer_out_grid, TRUE);
|
|
gtk_widget_set_align(
|
|
card->routing_hw_in_grid, GTK_ALIGN_END, GTK_ALIGN_CENTER
|
|
);
|
|
gtk_widget_set_align(
|
|
card->routing_pcm_in_grid, GTK_ALIGN_END, GTK_ALIGN_CENTER
|
|
);
|
|
gtk_widget_set_align(
|
|
card->routing_hw_out_grid, GTK_ALIGN_START, GTK_ALIGN_CENTER
|
|
);
|
|
gtk_widget_set_align(
|
|
card->routing_pcm_out_grid, GTK_ALIGN_START, GTK_ALIGN_CENTER
|
|
);
|
|
gtk_widget_set_align(
|
|
card->routing_mixer_in_grid, GTK_ALIGN_CENTER, GTK_ALIGN_END
|
|
);
|
|
gtk_widget_set_align(
|
|
card->routing_mixer_out_grid, GTK_ALIGN_CENTER, GTK_ALIGN_START
|
|
);
|
|
|
|
routing_grid_label("Hardware Inputs", GTK_GRID(card->routing_hw_in_grid));
|
|
routing_grid_label("Hardware Outputs", GTK_GRID(card->routing_hw_out_grid));
|
|
routing_grid_label("PCM Outputs", GTK_GRID(card->routing_pcm_in_grid));
|
|
routing_grid_label("PCM Inputs", GTK_GRID(card->routing_pcm_out_grid));
|
|
|
|
GtkWidget *src_label = gtk_label_new("↑\nSources →");
|
|
gtk_label_set_justify(GTK_LABEL(src_label), GTK_JUSTIFY_CENTER);
|
|
gtk_grid_attach(GTK_GRID(routing_grid), src_label, 0, 3, 1, 1);
|
|
|
|
GtkWidget *dst_label = gtk_label_new("← Destinations\n↓");
|
|
gtk_label_set_justify(GTK_LABEL(dst_label), GTK_JUSTIFY_CENTER);
|
|
gtk_grid_attach(GTK_GRID(routing_grid), dst_label, 2, 0, 1, 1);
|
|
}
|
|
|
|
static GtkWidget *make_socket_widget(void) {
|
|
return gtk_picture_new_for_resource(
|
|
"/vu/b4/alsa-scarlett-gui/icons/socket.svg"
|
|
);
|
|
}
|
|
|
|
// something was dropped on a routing source
|
|
static gboolean dropped_on_src(
|
|
GtkDropTarget *dest,
|
|
const GValue *value,
|
|
double x,
|
|
double y,
|
|
gpointer data
|
|
) {
|
|
struct routing_src *src = data;
|
|
int dst_id = g_value_get_int(value);
|
|
|
|
// don't accept src -> src drops
|
|
if (!(dst_id & 0x8000))
|
|
return FALSE;
|
|
|
|
// convert the int to a r_dst_idx
|
|
int r_dst_idx = dst_id & ~0x8000;
|
|
|
|
// check the index is in bounds
|
|
GArray *r_dsts = src->card->routing_dsts;
|
|
if (r_dst_idx < 0 || r_dst_idx >= r_dsts->len)
|
|
return FALSE;
|
|
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
r_dsts, struct routing_dst, r_dst_idx
|
|
);
|
|
alsa_set_elem_value(r_dst->elem, src->id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// something was dropped on a routing destination
|
|
static gboolean dropped_on_dst(
|
|
GtkDropTarget *dest,
|
|
const GValue *value,
|
|
double x,
|
|
double y,
|
|
gpointer data
|
|
) {
|
|
struct alsa_elem *elem = data;
|
|
int src_id = g_value_get_int(value);
|
|
|
|
// don't accept dst -> dst drops
|
|
if (src_id & 0x8000)
|
|
return FALSE;
|
|
|
|
alsa_set_elem_value(elem, src_id);
|
|
return TRUE;
|
|
}
|
|
|
|
static void src_routing_clicked(
|
|
GtkWidget *widget,
|
|
int n_press,
|
|
double x,
|
|
double y,
|
|
struct routing_src *r_src
|
|
) {
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
// go through all the routing destinations
|
|
for (int i = 0; i < card->routing_dsts->len; i++) {
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, i
|
|
);
|
|
|
|
int r_src_idx = alsa_get_elem_value(r_dst->elem);
|
|
|
|
if (r_src_idx == r_src->id)
|
|
alsa_set_elem_value(r_dst->elem, 0);
|
|
}
|
|
}
|
|
|
|
static void dst_routing_clicked(
|
|
GtkWidget *widget,
|
|
int n_press,
|
|
double x,
|
|
double y,
|
|
struct alsa_elem *elem
|
|
) {
|
|
alsa_set_elem_value(elem, 0);
|
|
}
|
|
|
|
static void src_drag_begin(
|
|
GtkDragSource *source,
|
|
GdkDrag *drag,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_src *r_src = user_data;
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
card->drag_type = DRAG_TYPE_SRC;
|
|
card->src_drag = r_src;
|
|
}
|
|
|
|
static void dst_drag_begin(
|
|
GtkDragSource *source,
|
|
GdkDrag *drag,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_dst *r_dst = user_data;
|
|
struct alsa_card *card = r_dst->elem->card;
|
|
|
|
card->drag_type = DRAG_TYPE_DST;
|
|
card->dst_drag = r_dst;
|
|
}
|
|
|
|
static void src_drag_end(
|
|
GtkDragSource *source,
|
|
GdkDrag *drag,
|
|
gboolean delete_data,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_src *r_src = user_data;
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
card->drag_type = DRAG_TYPE_NONE;
|
|
card->src_drag = NULL;
|
|
gtk_widget_queue_draw(card->drag_line);
|
|
gtk_widget_queue_draw(card->routing_lines);
|
|
}
|
|
|
|
static void dst_drag_end(
|
|
GtkDragSource *source,
|
|
GdkDrag *drag,
|
|
gboolean delete_data,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_dst *r_dst = user_data;
|
|
struct alsa_card *card = r_dst->elem->card;
|
|
|
|
card->drag_type = DRAG_TYPE_NONE;
|
|
card->dst_drag = NULL;
|
|
gtk_widget_queue_draw(card->drag_line);
|
|
gtk_widget_queue_draw(card->routing_lines);
|
|
}
|
|
|
|
static gboolean src_drop_accept(
|
|
GtkDropTarget *source,
|
|
GdkDrop *drop,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_src *r_src = user_data;
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
return card->drag_type == DRAG_TYPE_DST;
|
|
}
|
|
|
|
static gboolean dst_drop_accept(
|
|
GtkDropTarget *source,
|
|
GdkDrop *drop,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_dst *r_dst = user_data;
|
|
struct alsa_card *card = r_dst->elem->card;
|
|
|
|
return card->drag_type == DRAG_TYPE_SRC;
|
|
}
|
|
|
|
static GdkDragAction src_drop_enter(
|
|
GtkDropTarget *dest,
|
|
gdouble x,
|
|
gdouble y,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_src *r_src = user_data;
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
if (card->drag_type != DRAG_TYPE_DST)
|
|
return 0;
|
|
|
|
card->src_drag = r_src;
|
|
|
|
return GDK_ACTION_COPY;
|
|
}
|
|
|
|
static GdkDragAction dst_drop_enter(
|
|
GtkDropTarget *dest,
|
|
gdouble x,
|
|
gdouble y,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_dst *r_dst = user_data;
|
|
struct alsa_card *card = r_dst->elem->card;
|
|
|
|
if (card->drag_type != DRAG_TYPE_SRC)
|
|
return 0;
|
|
|
|
card->dst_drag = r_dst;
|
|
|
|
return GDK_ACTION_COPY;
|
|
}
|
|
|
|
static void src_drop_leave(
|
|
GtkDropTarget *dest,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_src *r_src = user_data;
|
|
struct alsa_card *card = r_src->card;
|
|
|
|
if (card->drag_type != DRAG_TYPE_DST)
|
|
return;
|
|
|
|
card->src_drag = NULL;
|
|
}
|
|
|
|
static void dst_drop_leave(
|
|
GtkDropTarget *dest,
|
|
gpointer user_data
|
|
) {
|
|
struct routing_dst *r_dst = user_data;
|
|
struct alsa_card *card = r_dst->elem->card;
|
|
|
|
if (card->drag_type != DRAG_TYPE_SRC)
|
|
return;
|
|
|
|
card->dst_drag = NULL;
|
|
}
|
|
|
|
static void setup_src_drag(struct routing_src *r_src) {
|
|
GtkWidget *box = r_src->widget;
|
|
|
|
// handle drags on the box
|
|
GtkDragSource *source = gtk_drag_source_new();
|
|
g_signal_connect(source, "drag-begin", G_CALLBACK(src_drag_begin), r_src);
|
|
g_signal_connect(source, "drag-end", G_CALLBACK(src_drag_end), r_src);
|
|
|
|
// set the box as a drag source
|
|
gtk_drag_source_set_actions(source, GDK_ACTION_COPY);
|
|
gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(source));
|
|
|
|
// set the content
|
|
GdkContentProvider *content = gdk_content_provider_new_typed(
|
|
G_TYPE_INT, r_src->id
|
|
);
|
|
gtk_drag_source_set_content(source, content);
|
|
g_object_unref(content);
|
|
|
|
// set a blank icon
|
|
GdkPaintable *paintable = gdk_paintable_new_empty(1, 1);
|
|
gtk_drag_source_set_icon(source, paintable, 0, 0);
|
|
g_object_unref(paintable);
|
|
|
|
// set the box as a drop target
|
|
GtkDropTarget *dest = gtk_drop_target_new(G_TYPE_INT, GDK_ACTION_COPY);
|
|
gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(dest));
|
|
g_signal_connect(dest, "drop", G_CALLBACK(dropped_on_src), r_src);
|
|
g_signal_connect(dest, "accept", G_CALLBACK(src_drop_accept), r_src);
|
|
g_signal_connect(dest, "enter", G_CALLBACK(src_drop_enter), r_src);
|
|
g_signal_connect(dest, "leave", G_CALLBACK(src_drop_leave), r_src);
|
|
}
|
|
|
|
static void setup_dst_drag(struct routing_dst *r_dst) {
|
|
struct alsa_elem *elem = r_dst->elem;
|
|
GtkWidget *box = elem->widget;
|
|
|
|
// handle drags on the box
|
|
GtkDragSource *source = gtk_drag_source_new();
|
|
g_signal_connect(source, "drag-begin", G_CALLBACK(dst_drag_begin), r_dst);
|
|
g_signal_connect(source, "drag-end", G_CALLBACK(dst_drag_end), r_dst);
|
|
|
|
// set the box as a drag source
|
|
gtk_drag_source_set_actions(source, GDK_ACTION_COPY);
|
|
gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(source));
|
|
|
|
// set the content
|
|
// 0x8000 flag indicates alsa_elem numid value
|
|
GdkContentProvider *content = gdk_content_provider_new_typed(
|
|
G_TYPE_INT, 0x8000 | r_dst->idx
|
|
);
|
|
gtk_drag_source_set_content(source, content);
|
|
g_object_unref(content);
|
|
|
|
// set a blank icon
|
|
GdkPaintable *paintable = gdk_paintable_new_empty(1, 1);
|
|
gtk_drag_source_set_icon(source, paintable, 0, 0);
|
|
g_object_unref(paintable);
|
|
|
|
// set the box as a drop target
|
|
GtkDropTarget *dest = gtk_drop_target_new(G_TYPE_INT, GDK_ACTION_COPY);
|
|
gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(dest));
|
|
g_signal_connect(dest, "drop", G_CALLBACK(dropped_on_dst), elem);
|
|
g_signal_connect(dest, "accept", G_CALLBACK(dst_drop_accept), r_dst);
|
|
g_signal_connect(dest, "enter", G_CALLBACK(dst_drop_enter), r_dst);
|
|
g_signal_connect(dest, "leave", G_CALLBACK(dst_drop_leave), r_dst);
|
|
}
|
|
|
|
static void make_src_routing_widget(
|
|
struct alsa_card *card,
|
|
struct routing_src *r_src,
|
|
char *name,
|
|
GtkOrientation orientation
|
|
) {
|
|
|
|
// create a box, a "socket", and a label
|
|
GtkWidget *box = r_src->widget = gtk_box_new(orientation, 5);
|
|
GtkWidget *socket = r_src->widget2 = make_socket_widget();
|
|
|
|
// create label for mixer inputs (length > 1) and mixer outputs if
|
|
// not talkback (talkback has a button outside the box instead of a
|
|
// label inside the box)
|
|
if (strlen(name) > 1 || !card->has_talkback) {
|
|
GtkWidget *label = gtk_label_new(name);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
gtk_widget_add_class(box, "route-label");
|
|
}
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL) {
|
|
gtk_box_append(GTK_BOX(box), socket);
|
|
gtk_widget_set_halign(box, GTK_ALIGN_END);
|
|
} else {
|
|
gtk_box_prepend(GTK_BOX(box), socket);
|
|
gtk_widget_set_margin_start(box, 5);
|
|
gtk_widget_set_margin_end(box, 5);
|
|
}
|
|
|
|
// handle clicks on the box
|
|
GtkGesture *gesture = gtk_gesture_click_new();
|
|
g_signal_connect(
|
|
gesture, "released", G_CALLBACK(src_routing_clicked), r_src
|
|
);
|
|
gtk_widget_add_controller(
|
|
GTK_WIDGET(box), GTK_EVENT_CONTROLLER(gesture)
|
|
);
|
|
|
|
// handle dragging to or from the box
|
|
setup_src_drag(r_src);
|
|
}
|
|
|
|
static GtkWidget *make_talkback_mix_widget(
|
|
struct alsa_card *card,
|
|
struct routing_src *r_src,
|
|
char *name
|
|
) {
|
|
char talkback_elem_name[80];
|
|
snprintf(talkback_elem_name, 80, "Talkback Mix %s Playback Switch", name);
|
|
struct alsa_elem *talkback_elem =
|
|
get_elem_by_name(card->elems, talkback_elem_name);
|
|
if (!talkback_elem)
|
|
return NULL;
|
|
return make_boolean_alsa_elem(talkback_elem, name, name);
|
|
}
|
|
|
|
static void make_dst_routing_widget(
|
|
struct routing_dst *r_dst,
|
|
char *name,
|
|
GtkOrientation orientation
|
|
) {
|
|
|
|
struct alsa_elem *elem = r_dst->elem;
|
|
|
|
// create a box, a "socket", and a label
|
|
GtkWidget *box = elem->widget = gtk_box_new(orientation, 5);
|
|
gtk_widget_add_class(box, "route-label");
|
|
GtkWidget *label = gtk_label_new(name);
|
|
gtk_box_append(GTK_BOX(box), label);
|
|
GtkWidget *socket = elem->widget2 = make_socket_widget();
|
|
if (orientation == GTK_ORIENTATION_VERTICAL) {
|
|
gtk_box_append(GTK_BOX(box), socket);
|
|
gtk_widget_set_margin_start(box, 5);
|
|
gtk_widget_set_margin_end(box, 5);
|
|
} else {
|
|
gtk_box_prepend(GTK_BOX(box), socket);
|
|
gtk_widget_set_halign(box, GTK_ALIGN_START);
|
|
}
|
|
|
|
// handle clicks on the box
|
|
GtkGesture *gesture = gtk_gesture_click_new();
|
|
g_signal_connect(
|
|
gesture, "released", G_CALLBACK(dst_routing_clicked), elem
|
|
);
|
|
gtk_widget_add_controller(
|
|
GTK_WIDGET(box), GTK_EVENT_CONTROLLER(gesture)
|
|
);
|
|
|
|
// handle dragging to or from the box
|
|
setup_dst_drag(r_dst);
|
|
}
|
|
|
|
static void routing_updated(struct alsa_elem *elem) {
|
|
struct alsa_card *card = elem->card;
|
|
|
|
update_mixer_labels(card);
|
|
gtk_widget_queue_draw(card->routing_lines);
|
|
}
|
|
|
|
static void make_routing_alsa_elem(struct routing_dst *r_dst) {
|
|
struct alsa_elem *elem = r_dst->elem;
|
|
struct alsa_card *card = elem->card;
|
|
|
|
// "Mixer Input X Capture Enum" controls (Mixer Inputs) go along
|
|
// the top, in card->routing_mixer_in_grid
|
|
if (r_dst->port_category == PC_MIX) {
|
|
|
|
char name[10];
|
|
|
|
snprintf(name, 10, "%d", elem->lr_num);
|
|
make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_VERTICAL);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_in_grid), elem->widget,
|
|
r_dst->port_num + 1, 0, 1, 1
|
|
);
|
|
|
|
// "PCM X Capture Enum" controls (PCM Inputs) go along the right,
|
|
// in card->routing_pcm_out_grid
|
|
} else if (r_dst->port_category == PC_PCM) {
|
|
char *name = strdup(elem->name);
|
|
char *name_end = strchr(name, ' ');
|
|
|
|
// in case the number is zero-padded
|
|
if (name_end)
|
|
snprintf(name_end, strlen(name_end) + 1, " %d", elem->lr_num);
|
|
|
|
make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_HORIZONTAL);
|
|
free(name);
|
|
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_pcm_out_grid), elem->widget,
|
|
0, r_dst->port_num + 1, 1, 1
|
|
);
|
|
|
|
// "* Output X Playback Enum" controls go along the right, in
|
|
// card->routing_hw_out_grid
|
|
} else if (r_dst->port_category == PC_HW) {
|
|
|
|
// Convert "Analogue 01 Output Playback Enum" to "Analogue 1"
|
|
char *name = strdup(elem->name);
|
|
char *name_end = strstr(name, " Output ");
|
|
|
|
// in case the number is zero-padded
|
|
if (name_end)
|
|
snprintf(name_end, strlen(name_end) + 1, " %d", elem->lr_num);
|
|
|
|
make_dst_routing_widget(r_dst, name, GTK_ORIENTATION_HORIZONTAL);
|
|
free(name);
|
|
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_hw_out_grid), elem->widget,
|
|
0, r_dst->port_num + 1, 1, 1
|
|
);
|
|
} else {
|
|
printf("invalid port category %d\n", r_dst->port_category);
|
|
}
|
|
|
|
elem->widget_callback = routing_updated;
|
|
}
|
|
|
|
static void add_routing_widgets(
|
|
struct alsa_card *card,
|
|
GtkWidget *routing_overlay
|
|
) {
|
|
GArray *r_dsts = card->routing_dsts;
|
|
|
|
// go through each routing destination and create its control
|
|
for (int i = 0; i < r_dsts->len; i++) {
|
|
struct routing_dst *r_dst = &g_array_index(r_dsts, struct routing_dst, i);
|
|
|
|
make_routing_alsa_elem(r_dst);
|
|
}
|
|
|
|
if (!card->routing_out_count[PC_MIX]) {
|
|
printf("no mixer inputs??\n");
|
|
return;
|
|
}
|
|
|
|
GtkWidget *l_mixer_in = gtk_label_new("Mixer\nInputs");
|
|
gtk_label_set_justify(GTK_LABEL(l_mixer_in), GTK_JUSTIFY_CENTER);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_in_grid), l_mixer_in,
|
|
0, 0, 1, 1
|
|
);
|
|
|
|
// start at 1 to skip the "Off" input
|
|
for (int i = 1; i < card->routing_srcs->len; i++) {
|
|
struct routing_src *r_src = &g_array_index(
|
|
card->routing_srcs, struct routing_src, i
|
|
);
|
|
|
|
if (r_src->port_category == PC_MIX) {
|
|
make_src_routing_widget(
|
|
card, r_src, r_src->name + 4, GTK_ORIENTATION_VERTICAL
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_out_grid), r_src->widget,
|
|
r_src->port_num + 1, 0, 1, 1
|
|
);
|
|
|
|
if (card->has_talkback) {
|
|
GtkWidget *w = make_talkback_mix_widget(card, r_src, r_src->name + 4);
|
|
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_out_grid), w,
|
|
r_src->port_num + 1, 1, 1, 1
|
|
);
|
|
}
|
|
} else if (r_src->port_category == PC_PCM) {
|
|
make_src_routing_widget(
|
|
card, r_src, r_src->name, GTK_ORIENTATION_HORIZONTAL
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_pcm_in_grid), r_src->widget,
|
|
0, r_src->port_num + 1, 1, 1
|
|
);
|
|
} else if (r_src->port_category == PC_HW) {
|
|
make_src_routing_widget(
|
|
card, r_src, r_src->name, GTK_ORIENTATION_HORIZONTAL
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_hw_in_grid), r_src->widget,
|
|
0, r_src->port_num + 1, 1, 1
|
|
);
|
|
} else {
|
|
printf("invalid port category %d\n", r_src->port_category);
|
|
}
|
|
}
|
|
|
|
GtkWidget *l_mixer_out = gtk_label_new(
|
|
card->has_talkback ? "Mixer Outputs" : "Mixer\nOutputs"
|
|
);
|
|
gtk_label_set_justify(GTK_LABEL(l_mixer_out), GTK_JUSTIFY_CENTER);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_out_grid), l_mixer_out,
|
|
0, 0, 1, 1
|
|
);
|
|
|
|
if (card->has_talkback) {
|
|
GtkWidget *l_talkback = gtk_label_new("Talkback");
|
|
gtk_widget_set_tooltip_text(
|
|
l_talkback,
|
|
"Mixer Outputs with Talkback enabled will have the level of "
|
|
"Mixer Input 25 internally raised and lowered when the "
|
|
"Talkback control is turned On and Off."
|
|
);
|
|
gtk_grid_attach(
|
|
GTK_GRID(card->routing_mixer_out_grid), l_talkback,
|
|
0, 1, 1, 1
|
|
);
|
|
}
|
|
|
|
card->routing_lines = gtk_drawing_area_new();
|
|
gtk_widget_set_can_target(card->routing_lines, FALSE);
|
|
gtk_drawing_area_set_draw_func(
|
|
GTK_DRAWING_AREA(card->routing_lines), draw_routing_lines, card, NULL
|
|
);
|
|
gtk_overlay_add_overlay(
|
|
GTK_OVERLAY(routing_overlay), card->routing_lines
|
|
);
|
|
|
|
update_mixer_labels(card);
|
|
}
|
|
|
|
GtkWidget *create_routing_controls(struct alsa_card *card) {
|
|
|
|
// check that we can find a routing control
|
|
card->sample_capture_elem =
|
|
get_elem_by_name(card->elems, "PCM 01 Capture Enum");
|
|
if (!card->sample_capture_elem) {
|
|
printf("couldn't find PCM 01 Capture Enum control; can't create GUI\n");
|
|
return NULL;
|
|
}
|
|
|
|
get_routing_srcs(card);
|
|
get_routing_dsts(card);
|
|
|
|
create_routing_grid(card);
|
|
|
|
GtkWidget *routing_overlay = gtk_overlay_new();
|
|
|
|
gtk_overlay_set_child(GTK_OVERLAY(routing_overlay), card->routing_grid);
|
|
|
|
add_routing_widgets(card, routing_overlay);
|
|
|
|
add_drop_controller_motion(card, routing_overlay);
|
|
|
|
return routing_overlay;
|
|
}
|