The kernel driver has some hints you can send to it that changes parser behaviour. This patch exposes that functionality to the user.
(This patch also includes minor fixes for documentation, GTK warnings and whitespace.)
Signed-off-by: David Henningsson david.henningsson@canonical.com --- hdajackretask/Makefile.am | 2 +- hdajackretask/README | 12 ++-- hdajackretask/apply-changes.c | 48 +++++++++---- hdajackretask/apply-changes.h | 4 +- hdajackretask/main-gtk.c | 144 +++++++++++++++++++++++++++++++++++--- hdajackretask/sysfs-pin-configs.c | 28 +++++++- hdajackretask/sysfs-pin-configs.h | 4 ++ 7 files changed, 209 insertions(+), 33 deletions(-)
diff --git a/hdajackretask/Makefile.am b/hdajackretask/Makefile.am index 3479b83..e313159 100644 --- a/hdajackretask/Makefile.am +++ b/hdajackretask/Makefile.am @@ -1,5 +1,5 @@ EXTRA_DIST = gitcompile README -AM_CFLAGS = @GTK3_CFLAGS@ +AM_CFLAGS = @GTK3_CFLAGS@ -Wno-deprecated-declarations bin_PROGRAMS = hdajackretask man_MANS = hdajackretask_SOURCES = main-gtk.c sysfs-pin-configs.c apply-changes.c diff --git a/hdajackretask/README b/hdajackretask/README index 68f62b1..209e332 100644 --- a/hdajackretask/README +++ b/hdajackretask/README @@ -1,12 +1,12 @@ -Documentation for hda-jack-retask -================================= +Documentation for hdajackretask +===============================
Most HDA Intel soundcards are to some degree retaskable, i e can be used for more than one thing. This tool is a GUI to make it easy to retask your jacks - e g, turn your Mic jack into an extra Headphone, or why not make them both line outs and connect them to your surround receiver?
Quickstart ==========
-Start the application "hda-jack-retask" from the command line. +Start the application "hdajackretask" from the command line. Select a codec in the top bar; some people have only one - if you have more than one, one is the "main" one, and the rest are probably HDMI codecs.
All jacks (and other inputs/outputs) are shown under "Pin configuration". To override one of your jacks, click the "Override" checkbox for that pin and select the desired function. @@ -44,9 +44,11 @@ Options Your BIOS is responsible for setting up what pins on the codec that are actually connected to something and which ones are not. Sometimes BIOS is buggy, and will not show all your jacks. If you have a jack your BIOS says you haven't, you can try enabling random pins and see if it works.
* Set Model=auto -Some codecs, especially older ones, are hard-coded to use a specific model, and thus will not care about your overrides. In many cases and with a reasonably new kernel, the auto parser now works well for these codecs as well. You can force the auto parser to be used by checking this box. In some cases, though, the explicit model is there for a reason, if so, you're stuck. +Some codecs, especially older ones and on kernels 3.8 and below, are hard-coded to use a specific model, and thus will not care about your overrides. In many cases and with a reasonably new kernel, the auto parser now works well for these codecs as well. You can force the auto parser to be used by checking this box. In some cases, though, the explicit model is there for a reason, if so, you're stuck.
* Advanced override This is for the experts only. It makes you select each configuration field individually, instead of just a few predefined values that make sense. Note that most combinations here are invalid in one way or the other, so you should probably not mess with this unless you have read and understood the "Configuration Default" section of the HD Audio specification. (Which, at the time of this writing, is available here: -http://www.intel.com/content/dam/doc/product-specification/high-definition-a... ) +http://www.intel.com/content/dam/www/public/us/en/documents/product-specific... )
+ * Parser hints +This enables you to send special "hints" to the driver that causes parsing to behave differently. Leave them at the "default" setting unless you have read the driver documentation. ( Which, at the time of this writing, is available here: https://www.kernel.org/doc/Documentation/sound/alsa/HD-Audio.txt - see the "Hint strings" section. ) diff --git a/hdajackretask/apply-changes.c b/hdajackretask/apply-changes.c index 840f649..aa291ce 100644 --- a/hdajackretask/apply-changes.c +++ b/hdajackretask/apply-changes.c @@ -15,6 +15,11 @@ static gchar* tempdir = NULL; static gchar* scriptfile = NULL; static gchar* errorfile = NULL;
+static GQuark quark() +{ + return g_quark_from_static_string("hda-jack-retask-error"); +} + static gboolean ensure_tempdir(GError** err) { if (!tempdir) { @@ -29,10 +34,10 @@ static gboolean ensure_tempdir(GError** err) }
static gboolean create_reconfig_script(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err) + const char* model, const char* hints, GError** err) { gchar* hwdir = g_strdup_printf("/sys/class/sound/hwC%dD%d", card, device); - gchar destbuf[120*40] = "#!/bin/sh\n"; + gchar destbuf[150*40] = "#!/bin/sh\n"; int bufleft = sizeof(destbuf) - strlen(destbuf); gboolean ok = FALSE; gchar* s = destbuf + strlen(destbuf); @@ -44,9 +49,16 @@ static gboolean create_reconfig_script(pin_configs_t* pins, int entries, int car int l = g_snprintf(s, bufleft, "echo "%s" | tee %s/modelname 2>>%s\n", model, hwdir, errorfile); bufleft-=l; - s+=l; + s+=l; } - + + if (hints) { + int l = g_snprintf(s, bufleft, "echo "%s" | tee %s/hints 2>>%s\n", + hints, hwdir, errorfile); + bufleft-=l; + s+=l; + } + while (entries) { int l = g_snprintf(s, bufleft, "echo "0x%02x 0x%08x" | tee %s/user_pin_configs 2>>%s\n", pins->nid, (unsigned int) actual_pin_config(pins), hwdir, errorfile); @@ -57,7 +69,7 @@ static gboolean create_reconfig_script(pin_configs_t* pins, int entries, int car }
if (bufleft < g_snprintf(s, bufleft, "echo 1 | tee %s/reconfig 2>>%s", hwdir, errorfile)) { - g_set_error(err, 0, 0, "Bug in %s:%d!", __FILE__, __LINE__); + g_set_error(err, quark(), 0, "Bug in %s:%d!", __FILE__, __LINE__); goto cleanup; }
@@ -85,7 +97,7 @@ gboolean run_sudo_script(const gchar* script_name, GError** err) g_chmod(script_name, 0755); g_spawn_command_line_sync(cmdline, NULL, NULL, &exit_status, NULL); if (errorfile && g_file_get_contents(errorfile, &errfilecontents, &errlen, NULL) && errlen) { - g_set_error(err, 0, 0, "%s", errfilecontents); + g_set_error(err, quark(), 0, "%s", errfilecontents); ok = FALSE; } else ok = TRUE; @@ -130,7 +142,7 @@ static gboolean kill_pulseaudio(gboolean* was_killed, int card, GError** err)
clientconf = get_pulseaudio_client_conf(); if (!(ok = !g_file_test(clientconf, G_FILE_TEST_EXISTS))) { - g_set_error(err, 0, 0, "Cannot block PulseAudio from respawning:\n" + g_set_error(err, quark(), 0, "Cannot block PulseAudio from respawning:\n" "Please either remove '%s' or kill PulseAudio manually.", clientconf); goto cleanup; } @@ -153,7 +165,7 @@ static gboolean restore_pulseaudio(gboolean was_killed, GError** err) { gchar* clientconf = get_pulseaudio_client_conf(); if (was_killed && g_unlink(clientconf) != 0) { - g_set_error(err, 0, 0, "%s", g_strerror(errno)); + g_set_error(err, quark(), 0, "%s", g_strerror(errno)); g_free(clientconf); return FALSE; } @@ -162,7 +174,7 @@ static gboolean restore_pulseaudio(gboolean was_killed, GError** err) }
gboolean apply_changes_reconfig(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err) + const char* model, const char* hints, GError** err) { gboolean result = FALSE; // gchar* script_name = NULL; @@ -172,7 +184,7 @@ gboolean apply_changes_reconfig(pin_configs_t* pins, int entries, int card, int if (!kill_pulseaudio(&pa_killed, card, err)) goto cleanup; /* Create script */ - if (!create_reconfig_script(pins, entries, card, device, model, err)) + if (!create_reconfig_script(pins, entries, card, device, model, hints, err)) goto cleanup; /* Run script as root */ if (!run_sudo_script(scriptfile, err)) @@ -187,10 +199,10 @@ cleanup: }
static gboolean create_firmware_file(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err) + const char* model, const char* hints, GError** err) { gboolean ok; - gchar destbuf[40*40] = ""; + gchar destbuf[40*40+40*24] = ""; gchar* s = destbuf; gchar* filename = g_strdup_printf("%s/hda-jack-retask.fw", tempdir); unsigned int address, codec_vendorid, codec_ssid; @@ -216,6 +228,12 @@ static gboolean create_firmware_file(pin_configs_t* pins, int entries, int card, s+=l; }
+ if (hints) { + int l = g_snprintf(s, bufleft, "\n[hints]\n%s\n", hints); + bufleft-=l; + s+=l; + } + ok = g_file_set_contents(filename, destbuf, -1, err); g_free(filename); return ok; @@ -238,14 +256,14 @@ static const gchar* install_script = "mv %s/hda-jack-retask.conf /etc/modprobe.d/hda-jack-retask.conf 2>>%s\n";
gboolean apply_changes_boot(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err) + const char* model, const char* hints, GError** err) { gchar *s;
if (!ensure_tempdir(err)) return FALSE;
- if (!create_firmware_file(pins, entries, card, device, model, err)) + if (!create_firmware_file(pins, entries, card, device, model, hints, err)) return FALSE;
/* Create hda-jack-retask.conf */ @@ -277,7 +295,7 @@ gboolean reset_changes_boot(GError** err) if ((g_file_test("/etc/modprobe.d/hda-jack-retask.conf", G_FILE_TEST_EXISTS) == 0) && (g_file_test("/lib/firmware/hda-jack-retask.fw", G_FILE_TEST_EXISTS) == 0)) { - g_set_error(err, 0, 0, "No boot override is currently installed, nothing to remove."); + g_set_error(err, quark(), 0, "No boot override is currently installed, nothing to remove."); return FALSE; }
diff --git a/hdajackretask/apply-changes.h b/hdajackretask/apply-changes.h index 2507a6a..e08d66d 100644 --- a/hdajackretask/apply-changes.h +++ b/hdajackretask/apply-changes.h @@ -5,10 +5,10 @@ #include <glib.h>
gboolean apply_changes_reconfig(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err); + const char* model, const char* hints, GError** err);
gboolean apply_changes_boot(pin_configs_t* pins, int entries, int card, int device, - const char* model, GError** err); + const char* model, const char* hints, GError** err); gboolean reset_changes_boot();
#endif diff --git a/hdajackretask/main-gtk.c b/hdajackretask/main-gtk.c index 9101af4..f5ff6e4 100644 --- a/hdajackretask/main-gtk.c +++ b/hdajackretask/main-gtk.c @@ -20,6 +20,13 @@ typedef struct pin_ui_data_t { ui_data_t* owner; } pin_ui_data_t;
+typedef struct hints_ui_data_t { + gboolean visible; + GtkWidget *frame; + GtkListStore *store; + gchar *values; +} hints_ui_data_t; + struct ui_data_t { GList* pin_ui_data; GtkWidget *main_window; @@ -35,6 +42,8 @@ struct ui_data_t { gboolean trust_codec; gboolean trust_defcfg; gboolean model_auto; + + hints_ui_data_t hints; };
static void update_user_pin_config(ui_data_t* ui, pin_configs_t* cfg); @@ -229,14 +238,44 @@ static void update_all_user_pin_config(ui_data_t* ui) update_user_pin_config(ui, &ui->sysfs_pins[i]); }
+static gboolean update_one_hint(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer userdata) +{ + gchar *name, *value; + ui_data_t *ui = userdata; + gtk_tree_model_get(GTK_TREE_MODEL(ui->hints.store), iter, 0, &name, 1, &value, -1); + if (g_strcmp0(value, "default")) { + gchar *s = g_strconcat(name, "=", value, "\n", ui->hints.values, NULL); + g_free(ui->hints.values); + ui->hints.values = s; + } + g_free(name); + g_free(value); + return FALSE; +} + +static void update_hints(ui_data_t* ui) +{ + g_free(ui->hints.values); + ui->hints.values = NULL; + if (ui->hints.visible) + gtk_tree_model_foreach(GTK_TREE_MODEL(ui->hints.store), update_one_hint, ui); +} + +static GQuark quark() +{ + return g_quark_from_static_string("hda-jack-retask-error"); +} + static gboolean validate_user_pin_config(ui_data_t* ui, GError** err) { int i;
if (!ui->current_codec) { - g_set_error(err, 0, 0, "You must first select a codec!"); + g_set_error(err, quark(), 0, "You must first select a codec!"); return FALSE; } + update_hints(ui); update_all_user_pin_config(ui); if (ui->free_overrides) return TRUE; @@ -249,21 +288,44 @@ static gboolean validate_user_pin_config(ui_data_t* ui, GError** err) if ((v & 0xf0) != 0x10) continue; if (((v & 0xf) != 0) && !find_pin_channel_match(ui->sysfs_pins, ui->sysfs_pincount, v & 0xf0)) { - g_set_error(err, 0, 0, "This surround setup also requires a "front" channel override."); + g_set_error(err, quark(), 0, "This surround setup also requires a "front" channel override."); return FALSE; } if (((v & 0xf) >= 3) && !find_pin_channel_match(ui->sysfs_pins, ui->sysfs_pincount, 2 + (v & 0xf0))) { - g_set_error(err, 0, 0, "This surround setup also requires a "back" channel override."); + g_set_error(err, quark(), 0, "This surround setup also requires a "back" channel override."); return FALSE; } if ((v & 0xf) >= 3 && !find_pin_channel_match(ui->sysfs_pins, ui->sysfs_pincount, 1 + (v & 0xf0))) { - g_set_error(err, 0, 0, "This surround setup also requires a "Center/LFE" channel override."); + g_set_error(err, quark(), 0, "This surround setup also requires a "Center/LFE" channel override."); return FALSE; } } return TRUE; }
+static gboolean update_tree_one_hint(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer userdata) +{ + gchar *name; + ui_data_t *ui = userdata; + gtk_tree_model_get(GTK_TREE_MODEL(ui->hints.store), iter, 0, &name, -1); + gchar *s = strstr(ui->hints.values, name); + if (!s) { + g_free(name); + gtk_list_store_set(ui->hints.store, iter, 1, "default", -1); + return FALSE; + } + s += strlen(name); + while (*s == ' ' || *s == '=') s++; + gchar *s2 = s; + while (*s != '\n' && *s != '\0') s++; + s2 = g_strndup(s2, s - s2); + gtk_list_store_set(ui->hints.store, iter, 1, s2, -1); + g_free(s2); + g_free(name); + return FALSE; +} + static void show_action_result(ui_data_t* ui, GError* err, const gchar* ok_msg) { GtkWidget* dialog; @@ -285,7 +347,7 @@ static void apply_now_clicked(GtkButton* button, gpointer user_data) if (ok) apply_changes_reconfig(ui->sysfs_pins, ui->sysfs_pincount, ui->current_codec->card, ui->current_codec->device, - ui->model_auto ? "auto" : NULL, &err); + ui->model_auto ? "auto" : NULL, ui->hints.values, &err); show_action_result(ui, err, "Ok, now go ahead and test to see if it actually worked!\n" "(Remember, this stuff is still experimental.)"); @@ -299,8 +361,8 @@ static void apply_boot_clicked(GtkButton* button, gpointer user_data) if (ok) apply_changes_boot(ui->sysfs_pins, ui->sysfs_pincount, ui->current_codec->card, ui->current_codec->device, - ui->model_auto ? "auto" : NULL, &err); - show_action_result(ui, err, + ui->model_auto ? "auto" : NULL, ui->hints.values, &err); + show_action_result(ui, err, "Ok, now reboot to test to see if it actually worked!\n" "(Remember, this stuff is still experimental.)"); } @@ -359,15 +421,24 @@ static void update_codec_ui(ui_data_t* ui, bool codec_change) if (codec_index < 0) return; ui->current_codec = &ui->sysfs_codec_names[codec_index]; - if (codec_change) + if (codec_change) { ui->sysfs_pincount = get_pin_configs_list(ui->sysfs_pins, 32, ui->current_codec->card, ui->current_codec->device); + ui->hints.values = get_hint_overrides(ui->current_codec->card, ui->current_codec->device); + gtk_tree_model_foreach(GTK_TREE_MODEL(ui->hints.store), update_tree_one_hint, ui); + } for (i = 0; i < ui->sysfs_pincount; i++) { GtkWidget *w = create_pin_ui(ui, &ui->sysfs_pins[i]); if (w) gtk_container_add(GTK_CONTAINER(ui->content_inner_box), w); } - + gtk_widget_show_all(GTK_WIDGET(ui->content_inner_box)); + + if (ui->hints.visible) + gtk_widget_show_all(ui->hints.frame); + else + gtk_widget_hide(ui->hints.frame); + resize_main_window(ui); }
@@ -394,6 +465,32 @@ static void free_override_toggled(GtkWidget* sender, ui_data_t* ui_data) update_codec_ui(ui_data, false); }
+static void hints_toggled(GtkWidget* sender, ui_data_t* ui_data) +{ + ui_data->hints.visible = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sender)); + update_codec_ui(ui_data, false); +} + +static void hints_row_activated(GtkTreeView *tree_view, GtkTreePath *path, + GtkTreeViewColumn *column, ui_data_t* ui_data) +{ + GtkTreeIter iter; + gchar *value; + const gchar *newvalue = "default"; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(ui_data->hints.store), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(ui_data->hints.store), &iter, 1, &value, -1); + + if (!g_strcmp0(value, "default")) + newvalue = "yes"; + else if (!g_strcmp0(value, "yes")) + newvalue = "no"; + gtk_list_store_set(ui_data->hints.store, &iter, 1, newvalue, -1); + + g_free(value); +} + + static const char* readme_text = #include "README.generated.h" ; @@ -480,6 +577,31 @@ static ui_data_t* create_ui() gtk_box_set_child_packing(GTK_BOX(toplevel_2ndbox), frame, TRUE, TRUE, 2, GTK_PACK_START); }
+ /* Create hints */ + { + GtkWidget* frame = gtk_frame_new("Hints"); + ui->hints.frame = frame; + + GtkListStore *store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + ui->hints.store = store; + const gchar** names = get_standard_hint_names(); + for (; *names; names++) { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, *names, 1, "default", -1); + } + + GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), gtk_tree_view_column_new_with_attributes + ("Name", gtk_cell_renderer_text_new(), "text", 0, NULL)); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), gtk_tree_view_column_new_with_attributes + ("Value", gtk_cell_renderer_text_new(), "text", 1, NULL)); + g_signal_connect(tree, "row-activated", G_CALLBACK(hints_row_activated), ui); + + gtk_container_add(GTK_CONTAINER(frame), tree); + gtk_container_add(toplevel_2ndbox, frame); + } + /* Create settings */ { GtkWidget* frame = gtk_frame_new("Options"); @@ -498,6 +620,10 @@ static ui_data_t* create_ui() g_signal_connect(check, "toggled", G_CALLBACK(free_override_toggled), ui); gtk_container_add(box, check);
+ check = gtk_check_button_new_with_label("Parser hints"); + g_signal_connect(check, "toggled", G_CALLBACK(hints_toggled), ui); + gtk_container_add(box, check); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(box)); gtk_container_add(rightside_box, frame); } diff --git a/hdajackretask/sysfs-pin-configs.c b/hdajackretask/sysfs-pin-configs.c index 5bae0f9..592ffe2 100644 --- a/hdajackretask/sysfs-pin-configs.c +++ b/hdajackretask/sysfs-pin-configs.c @@ -7,6 +7,21 @@ #include "sysfs-pin-configs.h" #include "apply-changes.h"
+const gchar *hint_names[25] = { +"jack_detect", "inv_jack_detect", "trigger_sense", "inv_eapd", +"pcm_format_first", "sticky_stream", "spdif_status_reset", +"pin_amp_workaround", "single_adc_amp", "auto_mute", "auto_mic", +"line_in_auto_switch", "auto_mute_via_amp", "need_dac_fix", "primary_hp", +"multi_io", "multi_cap_vol", "inv_dmic_split", "indep_hp", +"add_stereo_mix_input", "add_jack_modes", "power_down_unused", "add_hp_mic", +"hp_mic_detect", NULL }; + +const gchar** get_standard_hint_names() +{ + return hint_names; +} + + int get_codec_name_list(codec_name_t* names, int entries) { GDir* sysdir = g_dir_open("/sys/class/sound", 0, NULL); @@ -117,6 +132,16 @@ static void get_pin_caps(pin_configs_t* pins, int entries, int card, int device) g_free(contents); }
+gchar *get_hint_overrides(int card, int device) +{ + gchar* filename = g_strdup_printf("/sys/class/sound/hwC%dD%d/hints", card, device); + gchar* contents = NULL; + int ok = g_file_get_contents(filename, &contents, NULL, NULL); + g_free(filename); + if (!ok) + return NULL; + return contents; +}
static void read_pin_overrides(pin_configs_t* pins, int entries, int card, int device, gboolean is_user) { @@ -126,7 +151,7 @@ static void read_pin_overrides(pin_configs_t* pins, int entries, int card, int d int count = 0; int ok = g_file_get_contents(filename, &contents, NULL, NULL); g_free(filename); - if (!ok) + if (!ok) return; line_iterator = lines = g_strsplit(contents, "\n", entries); while (count < entries && *line_iterator) { @@ -151,6 +176,7 @@ static void read_pin_overrides(pin_configs_t* pins, int entries, int card, int d g_strfreev(line); } g_strfreev(lines); + g_free(contents); }
int get_pin_configs_list(pin_configs_t* pins, int entries, int card, int device) diff --git a/hdajackretask/sysfs-pin-configs.h b/hdajackretask/sysfs-pin-configs.h index 9a0a902..d198f8b 100644 --- a/hdajackretask/sysfs-pin-configs.h +++ b/hdajackretask/sysfs-pin-configs.h @@ -48,6 +48,10 @@ gchar* get_config_description(unsigned long config);
gchar* get_caps_description(unsigned long pin_caps);
+const gchar** get_standard_hint_names(); +gchar *get_hint_overrides(int card, int device); + + /* 0 = Jack, 1 = N/A, 2 = Internal, 3 = Both (?!) */ int get_port_conn(unsigned long config);