diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index b05bd7d06..c66e4e2c0 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -276,6 +276,168 @@ static void unescape(const char *src, char *dst) *d = 0; } +static enum wireless_status read_wireless_status(const char *sysfs_path) +{ + char buf[16]; + ssize_t sz; + int fd; + + fd = open(sysfs_path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return WIRELESS_STATUS_UNKNOWN; + + sz = read(fd, buf, sizeof(buf) - 1); + close(fd); + + if (sz <= 0) + return WIRELESS_STATUS_UNKNOWN; + + buf[sz] = '\0'; + if (buf[sz - 1] == '\n') + buf[sz - 1] = '\0'; + + if (spa_streq(buf, "connected")) + return WIRELESS_STATUS_CONNECTED; + if (spa_streq(buf, "disconnected")) + return WIRELESS_STATUS_DISCONNECTED; + + return WIRELESS_STATUS_UNKNOWN; +} + +static bool is_usb_interface_dir(const char *name) +{ + const char *colon = strchr(name, ':'); + if (!colon || !strchr(name, '-')) + return false; + + for (const char *p = colon + 1; *p; p++) + if (*p != '.' && !(*p >= '0' && *p <= '9')) + return false; + + return true; +} + +static char *check_wireless_status_at_path(struct impl *this, const char *path) +{ + char wireless_path[PATH_MAX]; + char *result; + int res; + + res = spa_scnprintf(wireless_path, sizeof(wireless_path), + "%s/wireless_status", path); + if (res < 0 || (size_t)res >= sizeof(wireless_path)) + return NULL; + + if (access(wireless_path, R_OK) < 0) + return NULL; + + result = strdup(wireless_path); + if (!result) + spa_log_error(this->log, "failed to allocate wireless_status path"); + return result; +} + +static char *search_siblings_for_wireless_status(struct impl *this, const char *parent_path) +{ + struct dirent *entry; + char *result = NULL; + int count = 0; + + spa_autoptr(DIR) parent_dir = opendir(parent_path); + if (!parent_dir) + return NULL; + + while ((entry = readdir(parent_dir)) != NULL) { + char sibling_path[PATH_MAX]; + char *path; + int res; + + if (entry->d_name[0] == '.') + continue; + + if (!is_usb_interface_dir(entry->d_name)) + continue; + + if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_DIR) + continue; + + res = spa_scnprintf(sibling_path, sizeof(sibling_path), + "%s/%s", parent_path, entry->d_name); + if (res < 0 || (size_t)res >= sizeof(sibling_path)) + continue; + + path = check_wireless_status_at_path(this, sibling_path); + if (path) { + count++; + if (!result) + result = path; + else + free(path); + } + } + + if (count > 1) + spa_log_info(this->log, "found %d wireless_status files, using first one", count); + + return result; +} + +static char *find_wireless_status_path(struct impl *this, const char *card_syspath) +{ + char usb_device_path[PATH_MAX]; + char *last_slash, *result; + int i, res; + + res = spa_scnprintf(usb_device_path, sizeof(usb_device_path), "%s", card_syspath); + if (res < 0 || (size_t)res >= sizeof(usb_device_path)) + return NULL; + + result = check_wireless_status_at_path(this, usb_device_path); + if (result) + return result; + + for (i = 0; i < MAX_PARENT_TRAVERSAL_DEPTH; i++) { + last_slash = strrchr(usb_device_path, '/'); + if (!last_slash || last_slash == usb_device_path) + break; + + *last_slash = '\0'; + + result = search_siblings_for_wireless_status(this, usb_device_path); + if (result) + return result; + } + + return NULL; +} + +static void update_wireless_status(struct impl *this, struct card *card) +{ + enum wireless_status status; + bool disconnected; + + if (!card->wireless_status_path) + return; + + status = read_wireless_status(card->wireless_status_path); + + if (status == WIRELESS_STATUS_UNKNOWN) { + spa_log_info(this->log, "card %u wireless_status unreadable, removing monitor", + card->card_nr); + free(card->wireless_status_path); + card->wireless_status_path = NULL; + card->wireless_disconnected = false; + return; + } + + disconnected = (status == WIRELESS_STATUS_DISCONNECTED); + if (disconnected != card->wireless_disconnected) { + spa_log_info(this->log, "card %u wireless device %s", + card->card_nr, disconnected ? "disconnected" : "connected"); + card->wireless_disconnected = disconnected; + } +} + static int check_device_pcm_class(const char *devname) { char path[PATH_MAX]; @@ -906,6 +1068,23 @@ static int start_inotify(struct impl *this) return 0; } +static struct card *find_card_by_wireless_status_path(struct impl *this, const char *syspath) +{ + char wireless_path[PATH_MAX]; + int res; + + res = spa_scnprintf(wireless_path, sizeof(wireless_path), "%s/wireless_status", syspath); + if (res < 0 || (size_t)res >= sizeof(wireless_path)) + return NULL; + + for (unsigned int i = 0; i < this->n_cards; i++) { + if (this->cards[i].wireless_status_path && + spa_streq(this->cards[i].wireless_status_path, wireless_path)) + return &this->cards[i]; + } + return NULL; +} + static void impl_on_fd_events(struct spa_source *source) { struct impl *this = source->data;