From b3e4b1558359b71352d847652eb5294c5687ffd7 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 18 Nov 2025 14:23:18 +0100 Subject: [PATCH] ucm: sort devices by priority Signed-off-by: Jaroslav Kysela --- src/ucm/parser.c | 123 ++++++++++++++++++++++++++++++++++++++++++ src/ucm/ucm_confdoc.h | 55 +++++++++++++++++++ src/ucm/ucm_local.h | 3 ++ 3 files changed, 181 insertions(+) diff --git a/src/ucm/parser.c b/src/ucm/parser.c index 5205dff7..0cb2f76e 100644 --- a/src/ucm/parser.c +++ b/src/ucm/parser.c @@ -2098,6 +2098,120 @@ static int verb_strip_single_device_index(struct use_case_verb *verb) return 0; } +/* + * Determine priority for a device. + * Priority order: + * 1. If 'Priority' value exists, use it as the sort key + * 2. If 'PlaybackPriority' value exists, use it as the sort key + * 3. If 'CapturePriority' value exists, use it as the sort key + * 4. Fallback: LONG_MIN (no priority) + */ +static long verb_device_get_priority(struct use_case_device *device) +{ + struct list_head *pos; + struct ucm_value *val; + const char *priority_str = NULL; + long priority = LONG_MIN; + int err; + + list_for_each(pos, &device->value_list) { + val = list_entry(pos, struct ucm_value, list); + if (strcmp(val->name, "Priority") == 0) { + priority_str = val->data; + break; + } + } + + if (!priority_str) { + list_for_each(pos, &device->value_list) { + val = list_entry(pos, struct ucm_value, list); + if (strcmp(val->name, "PlaybackPriority") == 0) { + priority_str = val->data; + break; + } + } + } + + if (!priority_str) { + list_for_each(pos, &device->value_list) { + val = list_entry(pos, struct ucm_value, list); + if (strcmp(val->name, "CapturePriority") == 0) { + priority_str = val->data; + break; + } + } + } + + if (priority_str) { + err = safe_strtol(priority_str, &priority); + if (err < 0) + priority = LONG_MIN; + } + + return priority; +} + +/* + * Sort devices based on priority values. + * Priority order: + * 1. If 'Priority' value exists, use it as the sort key + * 2. If 'PlaybackPriority' value exists, use it as the sort key + * 3. If 'CapturePriority' value exists, use it as the sort key + * 4. Fallback: use device->name (original) as the sort key + * Higher priority values are placed first in the list. + */ +static int verb_sort_devices(struct use_case_verb *verb) +{ + struct list_head sorted_list; + struct list_head *pos, *npos; + struct use_case_device *device, *insert_dev; + + INIT_LIST_HEAD(&sorted_list); + + /* First pass: determine and cache priority for all devices */ + list_for_each(pos, &verb->device_list) { + device = list_entry(pos, struct use_case_device, list); + device->sort_priority = verb_device_get_priority(device); + } + + /* Move devices from verb->device_list to sorted_list in sorted order */ + list_for_each_safe(pos, npos, &verb->device_list) { + device = list_entry(pos, struct use_case_device, list); + + /* Remove device from original list */ + list_del(&device->list); + + /* Find the insertion point in sorted_list */ + /* Devices are sorted in descending order of priority (higher priority first) */ + /* If priorities are equal or not defined, use device name as key */ + if (list_empty(&sorted_list)) { + list_add_tail(&device->list, &sorted_list); + } else { + struct list_head *pos2, *insert_pos = &sorted_list; + list_for_each(pos2, &sorted_list) { + insert_dev = list_entry(pos2, struct use_case_device, list); + + if (device->sort_priority > insert_dev->sort_priority) { + insert_pos = pos2; + break; + } else if (device->sort_priority == insert_dev->sort_priority) { + if (strcmp(device->name, insert_dev->name) < 0) { + insert_pos = pos2; + break; + } + } + } + + list_add_tail(&device->list, insert_pos); + } + } + + /* Move sorted list back to verb->device_list */ + list_splice_init(&sorted_list, &verb->device_list); + + return 0; +} + static int verb_device_management(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb) { struct list_head *pos; @@ -2128,6 +2242,14 @@ static int verb_device_management(snd_use_case_mgr_t *uc_mgr, struct use_case_ve uc_mgr_free_dev_name_list(&verb->rename_list); uc_mgr_free_dev_name_list(&verb->remove_list); + /* strip index from single device names */ + if (uc_mgr->conf_format >= 8) { + /* sort devices by priority */ + err = verb_sort_devices(verb); + if (err < 0) + return err; + } + /* normalize device names to remove spaces per use-case.h specification */ err = verb_normalize_device_names(uc_mgr, verb); if (err < 0) @@ -2138,6 +2260,7 @@ static int verb_device_management(snd_use_case_mgr_t *uc_mgr, struct use_case_ve err = verb_strip_single_device_index(verb); if (err < 0) return err; + } /* handle conflicting/supported lists */ diff --git a/src/ucm/ucm_confdoc.h b/src/ucm/ucm_confdoc.h index 34269d7b..48dc45a9 100644 --- a/src/ucm/ucm_confdoc.h +++ b/src/ucm/ucm_confdoc.h @@ -395,6 +395,61 @@ in the laptop instead the microphone in headphones. The preference of the devices is determined by the priority value (higher value = higher priority). +#### Device ordering (Syntax 8+) + +Starting with **Syntax 8**, devices are automatically sorted based on their priority values. +The sorting is performed at the end of device management processing, after device renaming +and index assignment. + +The priority key selection order is: +1. **Priority** - If this value exists, use it as the sorting key +2. **PlaybackPriority** - If Priority doesn't exist but PlaybackPriority exists, use it +3. **CapturePriority** - If neither Priority nor PlaybackPriority exist, use CapturePriority +4. **Fallback** - If no priority value is defined, use the device name for alphabetical sorting + +Devices are sorted in **descending order** of priority (higher priority values appear first +in the device list). When two devices have the same priority value, they are sorted +alphabetically by device name. + +Example - Device priority ordering: + +~~~{.html} +SectionDevice."Speaker" { + Comment "Internal speaker" + EnableSequence [ + cset "name='Speaker Switch' on" + ] + Value { + PlaybackPriority 100 + PlaybackPCM "hw:${CardId},0" + } +} + +SectionDevice."Headphones" { + Comment "Headphone jack" + EnableSequence [ + cset "name='Headphone Switch' on" + ] + Value { + PlaybackPriority 200 + PlaybackPCM "hw:${CardId},1" + } +} + +SectionDevice."HDMI" { + Comment "HDMI output" + EnableSequence [ + cset "name='HDMI Switch' on" + ] + Value { + PlaybackPriority 150 + PlaybackPCM "hw:${CardId},3" + } +} +~~~ + +In this example, the device list will be ordered as: Headphones (200), HDMI (150), Speaker (100). + See the SND_USE_CASE_MOD constants like #SND_USE_CASE_MOD_ECHO_REF for the full list of known modifiers. ### Boot (alsactl) diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h index bf7bc392..e4fddb40 100644 --- a/src/ucm/ucm_local.h +++ b/src/ucm/ucm_local.h @@ -179,6 +179,9 @@ struct use_case_device { /* value list */ struct list_head value_list; + + /* cached priority for sorting (LONG_MIN if not determined) */ + long sort_priority; }; /* -- 2.47.3