ucm: sort devices by priority

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Jaroslav Kysela 2025-11-18 14:23:18 +01:00
parent a525015e3b
commit b3e4b15583
3 changed files with 181 additions and 0 deletions

View file

@ -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 */

View file

@ -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)

View file

@ -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;
};
/*