1c72fcc34Sopenharmony_ci/* 2c72fcc34Sopenharmony_ci * mixer_widget.c - mixer widget and keys handling 3c72fcc34Sopenharmony_ci * Copyright (c) 1998,1999 Tim Janik 4c72fcc34Sopenharmony_ci * Jaroslav Kysela <perex@perex.cz> 5c72fcc34Sopenharmony_ci * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> 6c72fcc34Sopenharmony_ci * 7c72fcc34Sopenharmony_ci * This program is free software: you can redistribute it and/or modify 8c72fcc34Sopenharmony_ci * it under the terms of the GNU General Public License as published by 9c72fcc34Sopenharmony_ci * the Free Software Foundation, either version 2 of the License, or 10c72fcc34Sopenharmony_ci * (at your option) any later version. 11c72fcc34Sopenharmony_ci * 12c72fcc34Sopenharmony_ci * This program is distributed in the hope that it will be useful, 13c72fcc34Sopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 14c72fcc34Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15c72fcc34Sopenharmony_ci * GNU General Public License for more details. 16c72fcc34Sopenharmony_ci * 17c72fcc34Sopenharmony_ci * You should have received a copy of the GNU General Public License 18c72fcc34Sopenharmony_ci * along with this program. If not, see <http://www.gnu.org/licenses/>. 19c72fcc34Sopenharmony_ci */ 20c72fcc34Sopenharmony_ci 21c72fcc34Sopenharmony_ci#include "aconfig.h" 22c72fcc34Sopenharmony_ci#include <stdlib.h> 23c72fcc34Sopenharmony_ci#include <string.h> 24c72fcc34Sopenharmony_ci#include <errno.h> 25c72fcc34Sopenharmony_ci#include <alsa/asoundlib.h> 26c72fcc34Sopenharmony_ci#include "gettext_curses.h" 27c72fcc34Sopenharmony_ci#include "version.h" 28c72fcc34Sopenharmony_ci#include "utils.h" 29c72fcc34Sopenharmony_ci#include "die.h" 30c72fcc34Sopenharmony_ci#include "mem.h" 31c72fcc34Sopenharmony_ci#include "colors.h" 32c72fcc34Sopenharmony_ci#include "widget.h" 33c72fcc34Sopenharmony_ci#include "textbox.h" 34c72fcc34Sopenharmony_ci#include "proc_files.h" 35c72fcc34Sopenharmony_ci#include "card_select.h" 36c72fcc34Sopenharmony_ci#include "volume_mapping.h" 37c72fcc34Sopenharmony_ci#include "mixer_clickable.h" 38c72fcc34Sopenharmony_ci#include "mixer_controls.h" 39c72fcc34Sopenharmony_ci#include "mixer_display.h" 40c72fcc34Sopenharmony_ci#include "mixer_widget.h" 41c72fcc34Sopenharmony_ci#include "bindings.h" 42c72fcc34Sopenharmony_ci 43c72fcc34Sopenharmony_cisnd_mixer_t *mixer; 44c72fcc34Sopenharmony_cichar *mixer_device_name; 45c72fcc34Sopenharmony_cibool unplugged; 46c72fcc34Sopenharmony_ci 47c72fcc34Sopenharmony_cistruct widget mixer_widget; 48c72fcc34Sopenharmony_ci 49c72fcc34Sopenharmony_cienum view_mode view_mode; 50c72fcc34Sopenharmony_ci 51c72fcc34Sopenharmony_ciint focus_control_index; 52c72fcc34Sopenharmony_cisnd_mixer_selem_id_t *current_selem_id; 53c72fcc34Sopenharmony_ciunsigned int current_control_flags; 54c72fcc34Sopenharmony_ci 55c72fcc34Sopenharmony_cibool control_values_changed; 56c72fcc34Sopenharmony_cibool controls_changed; 57c72fcc34Sopenharmony_ci 58c72fcc34Sopenharmony_ciunsigned int mouse_wheel_step = 1; 59c72fcc34Sopenharmony_cibool mouse_wheel_focuses_control = 1; 60c72fcc34Sopenharmony_ci 61c72fcc34Sopenharmony_cistatic int elem_callback(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED, unsigned int mask) 62c72fcc34Sopenharmony_ci{ 63c72fcc34Sopenharmony_ci if (mask == SND_CTL_EVENT_MASK_REMOVE) { 64c72fcc34Sopenharmony_ci controls_changed = TRUE; 65c72fcc34Sopenharmony_ci } else { 66c72fcc34Sopenharmony_ci if (mask & SND_CTL_EVENT_MASK_VALUE) 67c72fcc34Sopenharmony_ci control_values_changed = TRUE; 68c72fcc34Sopenharmony_ci 69c72fcc34Sopenharmony_ci if (mask & SND_CTL_EVENT_MASK_INFO) 70c72fcc34Sopenharmony_ci controls_changed = TRUE; 71c72fcc34Sopenharmony_ci } 72c72fcc34Sopenharmony_ci 73c72fcc34Sopenharmony_ci return 0; 74c72fcc34Sopenharmony_ci} 75c72fcc34Sopenharmony_ci 76c72fcc34Sopenharmony_cistatic int mixer_callback(snd_mixer_t *mixer ATTRIBUTE_UNUSED, unsigned int mask, snd_mixer_elem_t *elem) 77c72fcc34Sopenharmony_ci{ 78c72fcc34Sopenharmony_ci if (mask & SND_CTL_EVENT_MASK_ADD) { 79c72fcc34Sopenharmony_ci snd_mixer_elem_set_callback(elem, elem_callback); 80c72fcc34Sopenharmony_ci controls_changed = TRUE; 81c72fcc34Sopenharmony_ci } 82c72fcc34Sopenharmony_ci return 0; 83c72fcc34Sopenharmony_ci} 84c72fcc34Sopenharmony_ci 85c72fcc34Sopenharmony_civoid create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt) 86c72fcc34Sopenharmony_ci{ 87c72fcc34Sopenharmony_ci int err; 88c72fcc34Sopenharmony_ci 89c72fcc34Sopenharmony_ci err = snd_mixer_open(&mixer, 0); 90c72fcc34Sopenharmony_ci if (err < 0) 91c72fcc34Sopenharmony_ci fatal_alsa_error(_("cannot open mixer"), err); 92c72fcc34Sopenharmony_ci 93c72fcc34Sopenharmony_ci mixer_device_name = cstrdup(selem_regopt->device); 94c72fcc34Sopenharmony_ci err = snd_mixer_selem_register(mixer, selem_regopt, NULL); 95c72fcc34Sopenharmony_ci if (err < 0) 96c72fcc34Sopenharmony_ci fatal_alsa_error(_("cannot open mixer"), err); 97c72fcc34Sopenharmony_ci 98c72fcc34Sopenharmony_ci snd_mixer_set_callback(mixer, mixer_callback); 99c72fcc34Sopenharmony_ci 100c72fcc34Sopenharmony_ci err = snd_mixer_load(mixer); 101c72fcc34Sopenharmony_ci if (err < 0) 102c72fcc34Sopenharmony_ci fatal_alsa_error(_("cannot load mixer controls"), err); 103c72fcc34Sopenharmony_ci 104c72fcc34Sopenharmony_ci err = snd_mixer_selem_id_malloc(¤t_selem_id); 105c72fcc34Sopenharmony_ci if (err < 0) 106c72fcc34Sopenharmony_ci fatal_error("out of memory"); 107c72fcc34Sopenharmony_ci} 108c72fcc34Sopenharmony_ci 109c72fcc34Sopenharmony_cistatic void set_view_mode(enum view_mode m) 110c72fcc34Sopenharmony_ci{ 111c72fcc34Sopenharmony_ci view_mode = m; 112c72fcc34Sopenharmony_ci create_controls(); 113c72fcc34Sopenharmony_ci} 114c72fcc34Sopenharmony_ci 115c72fcc34Sopenharmony_cistatic void close_hctl(void) 116c72fcc34Sopenharmony_ci{ 117c72fcc34Sopenharmony_ci free_controls(); 118c72fcc34Sopenharmony_ci if (mixer_device_name) { 119c72fcc34Sopenharmony_ci snd_mixer_detach(mixer, mixer_device_name); 120c72fcc34Sopenharmony_ci free(mixer_device_name); 121c72fcc34Sopenharmony_ci mixer_device_name = NULL; 122c72fcc34Sopenharmony_ci } 123c72fcc34Sopenharmony_ci} 124c72fcc34Sopenharmony_ci 125c72fcc34Sopenharmony_cistatic void check_unplugged(void) 126c72fcc34Sopenharmony_ci{ 127c72fcc34Sopenharmony_ci snd_hctl_t *hctl; 128c72fcc34Sopenharmony_ci snd_ctl_t *ctl; 129c72fcc34Sopenharmony_ci unsigned int state; 130c72fcc34Sopenharmony_ci int err; 131c72fcc34Sopenharmony_ci 132c72fcc34Sopenharmony_ci unplugged = FALSE; 133c72fcc34Sopenharmony_ci if (mixer_device_name) { 134c72fcc34Sopenharmony_ci err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl); 135c72fcc34Sopenharmony_ci if (err >= 0) { 136c72fcc34Sopenharmony_ci ctl = snd_hctl_ctl(hctl); 137c72fcc34Sopenharmony_ci /* just any random function that does an ioctl() */ 138c72fcc34Sopenharmony_ci err = snd_ctl_get_power_state(ctl, &state); 139c72fcc34Sopenharmony_ci if (err == -ENODEV) 140c72fcc34Sopenharmony_ci unplugged = TRUE; 141c72fcc34Sopenharmony_ci } 142c72fcc34Sopenharmony_ci } 143c72fcc34Sopenharmony_ci} 144c72fcc34Sopenharmony_ci 145c72fcc34Sopenharmony_civoid close_mixer_device(void) 146c72fcc34Sopenharmony_ci{ 147c72fcc34Sopenharmony_ci check_unplugged(); 148c72fcc34Sopenharmony_ci close_hctl(); 149c72fcc34Sopenharmony_ci 150c72fcc34Sopenharmony_ci display_card_info(); 151c72fcc34Sopenharmony_ci set_view_mode(view_mode); 152c72fcc34Sopenharmony_ci} 153c72fcc34Sopenharmony_ci 154c72fcc34Sopenharmony_cibool select_card_by_name(const char *device_name) 155c72fcc34Sopenharmony_ci{ 156c72fcc34Sopenharmony_ci int err; 157c72fcc34Sopenharmony_ci bool opened; 158c72fcc34Sopenharmony_ci char *msg; 159c72fcc34Sopenharmony_ci 160c72fcc34Sopenharmony_ci close_hctl(); 161c72fcc34Sopenharmony_ci unplugged = FALSE; 162c72fcc34Sopenharmony_ci 163c72fcc34Sopenharmony_ci opened = FALSE; 164c72fcc34Sopenharmony_ci if (device_name) { 165c72fcc34Sopenharmony_ci err = snd_mixer_attach(mixer, device_name); 166c72fcc34Sopenharmony_ci if (err >= 0) 167c72fcc34Sopenharmony_ci opened = TRUE; 168c72fcc34Sopenharmony_ci else { 169c72fcc34Sopenharmony_ci msg = casprintf(_("Cannot open mixer device '%s'."), device_name); 170c72fcc34Sopenharmony_ci show_alsa_error(msg, err); 171c72fcc34Sopenharmony_ci free(msg); 172c72fcc34Sopenharmony_ci } 173c72fcc34Sopenharmony_ci } 174c72fcc34Sopenharmony_ci if (opened) { 175c72fcc34Sopenharmony_ci mixer_device_name = cstrdup(device_name); 176c72fcc34Sopenharmony_ci 177c72fcc34Sopenharmony_ci err = snd_mixer_load(mixer); 178c72fcc34Sopenharmony_ci if (err < 0) 179c72fcc34Sopenharmony_ci fatal_alsa_error(_("cannot load mixer controls"), err); 180c72fcc34Sopenharmony_ci } 181c72fcc34Sopenharmony_ci 182c72fcc34Sopenharmony_ci display_card_info(); 183c72fcc34Sopenharmony_ci set_view_mode(view_mode); 184c72fcc34Sopenharmony_ci return opened; 185c72fcc34Sopenharmony_ci} 186c72fcc34Sopenharmony_ci 187c72fcc34Sopenharmony_cistatic void show_help(void) 188c72fcc34Sopenharmony_ci{ 189c72fcc34Sopenharmony_ci const char *help[] = { 190c72fcc34Sopenharmony_ci _("Esc Exit"), 191c72fcc34Sopenharmony_ci _("F1 ? H Help"), 192c72fcc34Sopenharmony_ci _("F2 / System information"), 193c72fcc34Sopenharmony_ci _("F3 Show playback controls"), 194c72fcc34Sopenharmony_ci _("F4 Show capture controls"), 195c72fcc34Sopenharmony_ci _("F5 Show all controls"), 196c72fcc34Sopenharmony_ci _("Tab Toggle view mode (F3/F4/F5)"), 197c72fcc34Sopenharmony_ci _("F6 S Select sound card"), 198c72fcc34Sopenharmony_ci _("L Redraw screen"), 199c72fcc34Sopenharmony_ci "", 200c72fcc34Sopenharmony_ci _("Left Move to the previous control"), 201c72fcc34Sopenharmony_ci _("Right Move to the next control"), 202c72fcc34Sopenharmony_ci "", 203c72fcc34Sopenharmony_ci _("Up/Down Change volume"), 204c72fcc34Sopenharmony_ci _("+ - Change volume"), 205c72fcc34Sopenharmony_ci _("Page Up/Dn Change volume in big steps"), 206c72fcc34Sopenharmony_ci _("End Set volume to 0%"), 207c72fcc34Sopenharmony_ci _("0-9 Set volume to 0%-90%"), 208c72fcc34Sopenharmony_ci _("Q W E Increase left/both/right volumes"), 209c72fcc34Sopenharmony_ci /* TRANSLATORS: or Y instead of Z */ 210c72fcc34Sopenharmony_ci _("Z X C Decrease left/both/right volumes"), 211c72fcc34Sopenharmony_ci _("B Balance left and right volumes"), 212c72fcc34Sopenharmony_ci "", 213c72fcc34Sopenharmony_ci _("M Toggle mute"), 214c72fcc34Sopenharmony_ci /* TRANSLATORS: or , . */ 215c72fcc34Sopenharmony_ci _("< > Toggle left/right mute"), 216c72fcc34Sopenharmony_ci "", 217c72fcc34Sopenharmony_ci _("Space Toggle capture"), 218c72fcc34Sopenharmony_ci /* TRANSLATORS: or Insert Delete */ 219c72fcc34Sopenharmony_ci _("; ' Toggle left/right capture"), 220c72fcc34Sopenharmony_ci "", 221c72fcc34Sopenharmony_ci _("Authors:"), 222c72fcc34Sopenharmony_ci _(" Tim Janik"), 223c72fcc34Sopenharmony_ci _(" Jaroslav Kysela <perex@perex.cz>"), 224c72fcc34Sopenharmony_ci _(" Clemens Ladisch <clemens@ladisch.de>"), 225c72fcc34Sopenharmony_ci }; 226c72fcc34Sopenharmony_ci show_text(help, ARRAY_SIZE(help), _("Help")); 227c72fcc34Sopenharmony_ci} 228c72fcc34Sopenharmony_ci 229c72fcc34Sopenharmony_civoid refocus_control(void) 230c72fcc34Sopenharmony_ci{ 231c72fcc34Sopenharmony_ci if (focus_control_index >= 0 && 232c72fcc34Sopenharmony_ci focus_control_index < (int)controls_count) { 233c72fcc34Sopenharmony_ci snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id); 234c72fcc34Sopenharmony_ci current_control_flags = controls[focus_control_index].flags; 235c72fcc34Sopenharmony_ci } 236c72fcc34Sopenharmony_ci 237c72fcc34Sopenharmony_ci display_controls(); 238c72fcc34Sopenharmony_ci} 239c72fcc34Sopenharmony_ci 240c72fcc34Sopenharmony_cistatic struct control *get_focus_control(unsigned int type) 241c72fcc34Sopenharmony_ci{ 242c72fcc34Sopenharmony_ci if (focus_control_index >= 0 && 243c72fcc34Sopenharmony_ci focus_control_index < (int)controls_count && 244c72fcc34Sopenharmony_ci (controls[focus_control_index].flags & IS_ACTIVE) && 245c72fcc34Sopenharmony_ci (controls[focus_control_index].flags & type)) 246c72fcc34Sopenharmony_ci return &controls[focus_control_index]; 247c72fcc34Sopenharmony_ci else 248c72fcc34Sopenharmony_ci return NULL; 249c72fcc34Sopenharmony_ci} 250c72fcc34Sopenharmony_ci 251c72fcc34Sopenharmony_cistatic void change_enum_to_percent(struct control *control, int value) 252c72fcc34Sopenharmony_ci{ 253c72fcc34Sopenharmony_ci unsigned int i; 254c72fcc34Sopenharmony_ci unsigned int index; 255c72fcc34Sopenharmony_ci unsigned int new_index; 256c72fcc34Sopenharmony_ci int items; 257c72fcc34Sopenharmony_ci int err; 258c72fcc34Sopenharmony_ci 259c72fcc34Sopenharmony_ci i = ffs(control->enum_channel_bits) - 1; 260c72fcc34Sopenharmony_ci err = snd_mixer_selem_get_enum_item(control->elem, i, &index); 261c72fcc34Sopenharmony_ci if (err < 0) 262c72fcc34Sopenharmony_ci return; 263c72fcc34Sopenharmony_ci new_index = index; 264c72fcc34Sopenharmony_ci if (value == 0) { 265c72fcc34Sopenharmony_ci new_index = 0; 266c72fcc34Sopenharmony_ci } else if (value == 100) { 267c72fcc34Sopenharmony_ci items = snd_mixer_selem_get_enum_items(control->elem); 268c72fcc34Sopenharmony_ci if (items < 1) 269c72fcc34Sopenharmony_ci return; 270c72fcc34Sopenharmony_ci new_index = items - 1; 271c72fcc34Sopenharmony_ci } 272c72fcc34Sopenharmony_ci if (new_index == index) 273c72fcc34Sopenharmony_ci return; 274c72fcc34Sopenharmony_ci for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 275c72fcc34Sopenharmony_ci if (control->enum_channel_bits & (1 << i)) 276c72fcc34Sopenharmony_ci snd_mixer_selem_set_enum_item(control->elem, i, new_index); 277c72fcc34Sopenharmony_ci} 278c72fcc34Sopenharmony_ci 279c72fcc34Sopenharmony_cistatic void change_enum_relative(struct control *control, int delta) 280c72fcc34Sopenharmony_ci{ 281c72fcc34Sopenharmony_ci int items; 282c72fcc34Sopenharmony_ci unsigned int i; 283c72fcc34Sopenharmony_ci unsigned int index; 284c72fcc34Sopenharmony_ci int new_index; 285c72fcc34Sopenharmony_ci int err; 286c72fcc34Sopenharmony_ci 287c72fcc34Sopenharmony_ci items = snd_mixer_selem_get_enum_items(control->elem); 288c72fcc34Sopenharmony_ci if (items < 1) 289c72fcc34Sopenharmony_ci return; 290c72fcc34Sopenharmony_ci err = snd_mixer_selem_get_enum_item(control->elem, 0, &index); 291c72fcc34Sopenharmony_ci if (err < 0) 292c72fcc34Sopenharmony_ci return; 293c72fcc34Sopenharmony_ci new_index = (int)index + delta; 294c72fcc34Sopenharmony_ci if (new_index < 0) 295c72fcc34Sopenharmony_ci new_index = 0; 296c72fcc34Sopenharmony_ci else if (new_index >= items) 297c72fcc34Sopenharmony_ci new_index = items - 1; 298c72fcc34Sopenharmony_ci if (new_index == (int)index) 299c72fcc34Sopenharmony_ci return; 300c72fcc34Sopenharmony_ci for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i) 301c72fcc34Sopenharmony_ci if (control->enum_channel_bits & (1 << i)) 302c72fcc34Sopenharmony_ci snd_mixer_selem_set_enum_item(control->elem, i, new_index); 303c72fcc34Sopenharmony_ci} 304c72fcc34Sopenharmony_ci 305c72fcc34Sopenharmony_cistatic void change_volume_to_percent(struct control *control, int value, unsigned int channels) 306c72fcc34Sopenharmony_ci{ 307c72fcc34Sopenharmony_ci int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 308c72fcc34Sopenharmony_ci 309c72fcc34Sopenharmony_ci if (!(control->flags & HAS_VOLUME_1)) 310c72fcc34Sopenharmony_ci channels = LEFT; 311c72fcc34Sopenharmony_ci if (control->flags & TYPE_PVOLUME) 312c72fcc34Sopenharmony_ci set_func = set_normalized_playback_volume; 313c72fcc34Sopenharmony_ci else 314c72fcc34Sopenharmony_ci set_func = set_normalized_capture_volume; 315c72fcc34Sopenharmony_ci if (channels & LEFT) 316c72fcc34Sopenharmony_ci set_func(control->elem, control->volume_channels[0], value / 100.0, 0); 317c72fcc34Sopenharmony_ci if (channels & RIGHT) 318c72fcc34Sopenharmony_ci set_func(control->elem, control->volume_channels[1], value / 100.0, 0); 319c72fcc34Sopenharmony_ci} 320c72fcc34Sopenharmony_ci 321c72fcc34Sopenharmony_cistatic double clamp_volume(double v) 322c72fcc34Sopenharmony_ci{ 323c72fcc34Sopenharmony_ci if (v < 0) 324c72fcc34Sopenharmony_ci return 0; 325c72fcc34Sopenharmony_ci if (v > 1) 326c72fcc34Sopenharmony_ci return 1; 327c72fcc34Sopenharmony_ci return v; 328c72fcc34Sopenharmony_ci} 329c72fcc34Sopenharmony_ci 330c72fcc34Sopenharmony_cistatic void change_volume_relative(struct control *control, int delta, unsigned int channels) 331c72fcc34Sopenharmony_ci{ 332c72fcc34Sopenharmony_ci double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t); 333c72fcc34Sopenharmony_ci int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int); 334c72fcc34Sopenharmony_ci double left = 0, right = 0; 335c72fcc34Sopenharmony_ci int dir; 336c72fcc34Sopenharmony_ci 337c72fcc34Sopenharmony_ci if (!(control->flags & HAS_VOLUME_1)) 338c72fcc34Sopenharmony_ci channels = LEFT; 339c72fcc34Sopenharmony_ci if (control->flags & TYPE_PVOLUME) { 340c72fcc34Sopenharmony_ci get_func = get_normalized_playback_volume; 341c72fcc34Sopenharmony_ci set_func = set_normalized_playback_volume; 342c72fcc34Sopenharmony_ci } else { 343c72fcc34Sopenharmony_ci get_func = get_normalized_capture_volume; 344c72fcc34Sopenharmony_ci set_func = set_normalized_capture_volume; 345c72fcc34Sopenharmony_ci } 346c72fcc34Sopenharmony_ci if (channels & LEFT) 347c72fcc34Sopenharmony_ci left = get_func(control->elem, control->volume_channels[0]); 348c72fcc34Sopenharmony_ci if (channels & RIGHT) 349c72fcc34Sopenharmony_ci right = get_func(control->elem, control->volume_channels[1]); 350c72fcc34Sopenharmony_ci dir = delta > 0 ? 1 : -1; 351c72fcc34Sopenharmony_ci if (channels & LEFT) { 352c72fcc34Sopenharmony_ci left = clamp_volume(left + delta / 100.0); 353c72fcc34Sopenharmony_ci set_func(control->elem, control->volume_channels[0], left, dir); 354c72fcc34Sopenharmony_ci } 355c72fcc34Sopenharmony_ci if (channels & RIGHT) { 356c72fcc34Sopenharmony_ci right = clamp_volume(right + delta / 100.0); 357c72fcc34Sopenharmony_ci set_func(control->elem, control->volume_channels[1], right, dir); 358c72fcc34Sopenharmony_ci } 359c72fcc34Sopenharmony_ci} 360c72fcc34Sopenharmony_ci 361c72fcc34Sopenharmony_cistatic void change_control_to_percent(int value, unsigned int channels) 362c72fcc34Sopenharmony_ci{ 363c72fcc34Sopenharmony_ci struct control *control; 364c72fcc34Sopenharmony_ci 365c72fcc34Sopenharmony_ci control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 366c72fcc34Sopenharmony_ci if (!control) 367c72fcc34Sopenharmony_ci return; 368c72fcc34Sopenharmony_ci if (control->flags & TYPE_ENUM) 369c72fcc34Sopenharmony_ci change_enum_to_percent(control, value); 370c72fcc34Sopenharmony_ci else 371c72fcc34Sopenharmony_ci change_volume_to_percent(control, value, channels); 372c72fcc34Sopenharmony_ci display_controls(); 373c72fcc34Sopenharmony_ci} 374c72fcc34Sopenharmony_ci 375c72fcc34Sopenharmony_cistatic void change_control_relative(int delta, unsigned int channels) 376c72fcc34Sopenharmony_ci{ 377c72fcc34Sopenharmony_ci struct control *control; 378c72fcc34Sopenharmony_ci 379c72fcc34Sopenharmony_ci control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM); 380c72fcc34Sopenharmony_ci if (!control) 381c72fcc34Sopenharmony_ci return; 382c72fcc34Sopenharmony_ci if (control->flags & TYPE_ENUM) 383c72fcc34Sopenharmony_ci change_enum_relative(control, delta); 384c72fcc34Sopenharmony_ci else 385c72fcc34Sopenharmony_ci change_volume_relative(control, delta, channels); 386c72fcc34Sopenharmony_ci display_controls(); 387c72fcc34Sopenharmony_ci} 388c72fcc34Sopenharmony_ci 389c72fcc34Sopenharmony_cistatic void toggle_switches(unsigned int type, unsigned int channels) 390c72fcc34Sopenharmony_ci{ 391c72fcc34Sopenharmony_ci struct control *control; 392c72fcc34Sopenharmony_ci unsigned int switch_1_mask; 393c72fcc34Sopenharmony_ci int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *); 394c72fcc34Sopenharmony_ci int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int); 395c72fcc34Sopenharmony_ci snd_mixer_selem_channel_id_t channel_ids[2]; 396c72fcc34Sopenharmony_ci int left, right; 397c72fcc34Sopenharmony_ci int err; 398c72fcc34Sopenharmony_ci 399c72fcc34Sopenharmony_ci control = get_focus_control(type); 400c72fcc34Sopenharmony_ci if (!control) 401c72fcc34Sopenharmony_ci return; 402c72fcc34Sopenharmony_ci if (type == TYPE_PSWITCH) { 403c72fcc34Sopenharmony_ci switch_1_mask = HAS_PSWITCH_1; 404c72fcc34Sopenharmony_ci get_func = snd_mixer_selem_get_playback_switch; 405c72fcc34Sopenharmony_ci set_func = snd_mixer_selem_set_playback_switch; 406c72fcc34Sopenharmony_ci channel_ids[0] = control->pswitch_channels[0]; 407c72fcc34Sopenharmony_ci channel_ids[1] = control->pswitch_channels[1]; 408c72fcc34Sopenharmony_ci } else { 409c72fcc34Sopenharmony_ci switch_1_mask = HAS_CSWITCH_1; 410c72fcc34Sopenharmony_ci get_func = snd_mixer_selem_get_capture_switch; 411c72fcc34Sopenharmony_ci set_func = snd_mixer_selem_set_capture_switch; 412c72fcc34Sopenharmony_ci channel_ids[0] = control->cswitch_channels[0]; 413c72fcc34Sopenharmony_ci channel_ids[1] = control->cswitch_channels[1]; 414c72fcc34Sopenharmony_ci } 415c72fcc34Sopenharmony_ci if (!(control->flags & switch_1_mask)) 416c72fcc34Sopenharmony_ci channels = LEFT; 417c72fcc34Sopenharmony_ci if (channels & LEFT) { 418c72fcc34Sopenharmony_ci err = get_func(control->elem, channel_ids[0], &left); 419c72fcc34Sopenharmony_ci if (err < 0) 420c72fcc34Sopenharmony_ci return; 421c72fcc34Sopenharmony_ci } 422c72fcc34Sopenharmony_ci if (channels & RIGHT) { 423c72fcc34Sopenharmony_ci err = get_func(control->elem, channel_ids[1], &right); 424c72fcc34Sopenharmony_ci if (err < 0) 425c72fcc34Sopenharmony_ci return; 426c72fcc34Sopenharmony_ci } 427c72fcc34Sopenharmony_ci if (channels & LEFT) 428c72fcc34Sopenharmony_ci set_func(control->elem, channel_ids[0], !left); 429c72fcc34Sopenharmony_ci if (channels & RIGHT) 430c72fcc34Sopenharmony_ci set_func(control->elem, channel_ids[1], !right); 431c72fcc34Sopenharmony_ci display_controls(); 432c72fcc34Sopenharmony_ci} 433c72fcc34Sopenharmony_ci 434c72fcc34Sopenharmony_cistatic void toggle_mute(unsigned int channels) 435c72fcc34Sopenharmony_ci{ 436c72fcc34Sopenharmony_ci toggle_switches(TYPE_PSWITCH, channels); 437c72fcc34Sopenharmony_ci} 438c72fcc34Sopenharmony_ci 439c72fcc34Sopenharmony_cistatic void toggle_capture(unsigned int channels) 440c72fcc34Sopenharmony_ci{ 441c72fcc34Sopenharmony_ci toggle_switches(TYPE_CSWITCH, channels); 442c72fcc34Sopenharmony_ci} 443c72fcc34Sopenharmony_ci 444c72fcc34Sopenharmony_cistatic void balance_volumes(void) 445c72fcc34Sopenharmony_ci{ 446c72fcc34Sopenharmony_ci struct control *control; 447c72fcc34Sopenharmony_ci double left, right; 448c72fcc34Sopenharmony_ci 449c72fcc34Sopenharmony_ci control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME); 450c72fcc34Sopenharmony_ci if (!control || !(control->flags & HAS_VOLUME_1)) 451c72fcc34Sopenharmony_ci return; 452c72fcc34Sopenharmony_ci if (control->flags & TYPE_PVOLUME) { 453c72fcc34Sopenharmony_ci left = get_normalized_playback_volume(control->elem, control->volume_channels[0]); 454c72fcc34Sopenharmony_ci right = get_normalized_playback_volume(control->elem, control->volume_channels[1]); 455c72fcc34Sopenharmony_ci } else { 456c72fcc34Sopenharmony_ci left = get_normalized_capture_volume(control->elem, control->volume_channels[0]); 457c72fcc34Sopenharmony_ci right = get_normalized_capture_volume(control->elem, control->volume_channels[1]); 458c72fcc34Sopenharmony_ci } 459c72fcc34Sopenharmony_ci left = (left + right) / 2; 460c72fcc34Sopenharmony_ci if (control->flags & TYPE_PVOLUME) { 461c72fcc34Sopenharmony_ci set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0); 462c72fcc34Sopenharmony_ci set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0); 463c72fcc34Sopenharmony_ci } else { 464c72fcc34Sopenharmony_ci set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0); 465c72fcc34Sopenharmony_ci set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0); 466c72fcc34Sopenharmony_ci } 467c72fcc34Sopenharmony_ci display_controls(); 468c72fcc34Sopenharmony_ci} 469c72fcc34Sopenharmony_ci 470c72fcc34Sopenharmony_cistatic int on_mouse_key() { 471c72fcc34Sopenharmony_ci MEVENT m; 472c72fcc34Sopenharmony_ci command_enum cmd = 0; 473c72fcc34Sopenharmony_ci unsigned int channels = LEFT | RIGHT; 474c72fcc34Sopenharmony_ci unsigned int index; 475c72fcc34Sopenharmony_ci struct control *control; 476c72fcc34Sopenharmony_ci struct clickable_rect *rect; 477c72fcc34Sopenharmony_ci 478c72fcc34Sopenharmony_ci if (getmouse(&m) == ERR) 479c72fcc34Sopenharmony_ci return 0; 480c72fcc34Sopenharmony_ci 481c72fcc34Sopenharmony_ci if (m.bstate & ( 482c72fcc34Sopenharmony_ci BUTTON1_PRESSED|BUTTON1_RELEASED| 483c72fcc34Sopenharmony_ci BUTTON2_PRESSED|BUTTON2_RELEASED| 484c72fcc34Sopenharmony_ci BUTTON3_PRESSED|BUTTON3_RELEASED)) 485c72fcc34Sopenharmony_ci return 0; 486c72fcc34Sopenharmony_ci 487c72fcc34Sopenharmony_ci rect = clickable_find(m.y, m.x); 488c72fcc34Sopenharmony_ci if (rect) 489c72fcc34Sopenharmony_ci cmd = rect->command; 490c72fcc34Sopenharmony_ci 491c72fcc34Sopenharmony_ci#if NCURSES_MOUSE_VERSION > 1 492c72fcc34Sopenharmony_ci if (m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED|BUTTON5_CLICKED|BUTTON5_PRESSED)) { 493c72fcc34Sopenharmony_ci switch (cmd) { 494c72fcc34Sopenharmony_ci case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM: 495c72fcc34Sopenharmony_ci focus_control_index = rect->arg1; 496c72fcc34Sopenharmony_ci return CMD_WITH_ARG(( 497c72fcc34Sopenharmony_ci m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED) 498c72fcc34Sopenharmony_ci ? CMD_MIXER_CONTROL_UP 499c72fcc34Sopenharmony_ci : CMD_MIXER_CONTROL_DOWN 500c72fcc34Sopenharmony_ci ), 1); 501c72fcc34Sopenharmony_ci 502c72fcc34Sopenharmony_ci case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR: 503c72fcc34Sopenharmony_ci if (mouse_wheel_focuses_control) 504c72fcc34Sopenharmony_ci focus_control_index = rect->arg1; 505c72fcc34Sopenharmony_ci /* fall through */ 506c72fcc34Sopenharmony_ci 507c72fcc34Sopenharmony_ci default: 508c72fcc34Sopenharmony_ci return CMD_WITH_ARG(( 509c72fcc34Sopenharmony_ci m.bstate & (BUTTON4_CLICKED|BUTTON4_PRESSED) 510c72fcc34Sopenharmony_ci ? CMD_MIXER_CONTROL_UP 511c72fcc34Sopenharmony_ci : CMD_MIXER_CONTROL_DOWN 512c72fcc34Sopenharmony_ci ), mouse_wheel_step); 513c72fcc34Sopenharmony_ci } 514c72fcc34Sopenharmony_ci } 515c72fcc34Sopenharmony_ci#endif 516c72fcc34Sopenharmony_ci 517c72fcc34Sopenharmony_ci /* If the rectangle has got an argument (value != -1) it is used for 518c72fcc34Sopenharmony_ci * setting `focus_control_index` */ 519c72fcc34Sopenharmony_ci if (rect && rect->arg1 >= 0) 520c72fcc34Sopenharmony_ci focus_control_index = rect->arg1; 521c72fcc34Sopenharmony_ci 522c72fcc34Sopenharmony_ci switch (cmd) { 523c72fcc34Sopenharmony_ci case CMD_MIXER_MOUSE_CLICK_VOLUME_BAR: 524c72fcc34Sopenharmony_ci if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED)) 525c72fcc34Sopenharmony_ci channels = m.x - rect->x1 + 1; 526c72fcc34Sopenharmony_ci return CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channels - 1, 527c72fcc34Sopenharmony_ci (100 * (rect->y2 - m.y) / (rect->y2 - rect->y1)) // volume 528c72fcc34Sopenharmony_ci ); 529c72fcc34Sopenharmony_ci 530c72fcc34Sopenharmony_ci case CMD_MIXER_MOUSE_CLICK_MUTE: 531c72fcc34Sopenharmony_ci if (m.bstate & (BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED)) 532c72fcc34Sopenharmony_ci channels = m.x - rect->x1 + 1; 533c72fcc34Sopenharmony_ci return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channels); 534c72fcc34Sopenharmony_ci 535c72fcc34Sopenharmony_ci case CMD_MIXER_MOUSE_CLICK_CONTROL_ENUM: 536c72fcc34Sopenharmony_ci control = get_focus_control(TYPE_ENUM); 537c72fcc34Sopenharmony_ci if (control && 538c72fcc34Sopenharmony_ci (snd_mixer_selem_get_enum_item(control->elem, 0, &index) >= 0)) { 539c72fcc34Sopenharmony_ci return (index == 0 540c72fcc34Sopenharmony_ci ? CMD_WITH_ARG(CMD_MIXER_CONTROL_UP, 100) 541c72fcc34Sopenharmony_ci : CMD_WITH_ARG(CMD_MIXER_CONTROL_DOWN, 1)); 542c72fcc34Sopenharmony_ci } 543c72fcc34Sopenharmony_ci break; 544c72fcc34Sopenharmony_ci 545c72fcc34Sopenharmony_ci default: 546c72fcc34Sopenharmony_ci return cmd; // non-mouse command 547c72fcc34Sopenharmony_ci } 548c72fcc34Sopenharmony_ci 549c72fcc34Sopenharmony_ci return 0; // failed mouse command 550c72fcc34Sopenharmony_ci} 551c72fcc34Sopenharmony_ci 552c72fcc34Sopenharmony_cistatic void on_handle_key(int key) 553c72fcc34Sopenharmony_ci{ 554c72fcc34Sopenharmony_ci int arg; 555c72fcc34Sopenharmony_ci command_enum cmd; 556c72fcc34Sopenharmony_ci 557c72fcc34Sopenharmony_ci if (key == KEY_MOUSE) 558c72fcc34Sopenharmony_ci cmd = on_mouse_key(); 559c72fcc34Sopenharmony_ci else if (key < (int)ARRAY_SIZE(mixer_bindings)) 560c72fcc34Sopenharmony_ci cmd = mixer_bindings[key]; 561c72fcc34Sopenharmony_ci else 562c72fcc34Sopenharmony_ci return; 563c72fcc34Sopenharmony_ci 564c72fcc34Sopenharmony_ci arg = CMD_GET_ARGUMENT(cmd); 565c72fcc34Sopenharmony_ci cmd = CMD_GET_COMMAND(cmd); 566c72fcc34Sopenharmony_ci 567c72fcc34Sopenharmony_ci switch (cmd) { 568c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_DOWN_LEFT: 569c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_DOWN_RIGHT: 570c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_DOWN: 571c72fcc34Sopenharmony_ci arg = (-arg); 572c72fcc34Sopenharmony_ci /* fall through */ 573c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_UP_LEFT: 574c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_UP_RIGHT: 575c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_UP: 576c72fcc34Sopenharmony_ci change_control_relative(arg, cmd % 4); 577c72fcc34Sopenharmony_ci break; 578c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_SET_PERCENT_LEFT: 579c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_SET_PERCENT_RIGHT: 580c72fcc34Sopenharmony_ci case CMD_MIXER_CONTROL_SET_PERCENT: 581c72fcc34Sopenharmony_ci change_control_to_percent(arg, cmd % 4); 582c72fcc34Sopenharmony_ci break; 583c72fcc34Sopenharmony_ci case CMD_MIXER_CLOSE: 584c72fcc34Sopenharmony_ci mixer_widget.close(); 585c72fcc34Sopenharmony_ci break; 586c72fcc34Sopenharmony_ci case CMD_MIXER_HELP: 587c72fcc34Sopenharmony_ci show_help(); 588c72fcc34Sopenharmony_ci break; 589c72fcc34Sopenharmony_ci case CMD_MIXER_SYSTEM_INFORMATION: 590c72fcc34Sopenharmony_ci create_proc_files_list(); 591c72fcc34Sopenharmony_ci break; 592c72fcc34Sopenharmony_ci case CMD_MIXER_TOGGLE_VIEW_MODE: 593c72fcc34Sopenharmony_ci arg = (view_mode + 1) % VIEW_MODE_COUNT; 594c72fcc34Sopenharmony_ci /* fall through */ 595c72fcc34Sopenharmony_ci case CMD_MIXER_SET_VIEW_MODE: 596c72fcc34Sopenharmony_ci set_view_mode((enum view_mode)(arg)); 597c72fcc34Sopenharmony_ci break; 598c72fcc34Sopenharmony_ci case CMD_MIXER_SELECT_CARD: 599c72fcc34Sopenharmony_ci create_card_select_list(); 600c72fcc34Sopenharmony_ci break; 601c72fcc34Sopenharmony_ci case CMD_MIXER_REFRESH: 602c72fcc34Sopenharmony_ci clearok(mixer_widget.window, TRUE); 603c72fcc34Sopenharmony_ci display_controls(); 604c72fcc34Sopenharmony_ci break; 605c72fcc34Sopenharmony_ci case CMD_MIXER_PREVIOUS: 606c72fcc34Sopenharmony_ci arg = (-arg); 607c72fcc34Sopenharmony_ci /* fall through */ 608c72fcc34Sopenharmony_ci case CMD_MIXER_NEXT: 609c72fcc34Sopenharmony_ci arg = focus_control_index + arg; 610c72fcc34Sopenharmony_ci /* fall through */ 611c72fcc34Sopenharmony_ci case CMD_MIXER_FOCUS_CONTROL: 612c72fcc34Sopenharmony_ci focus_control_index = arg; 613c72fcc34Sopenharmony_ci if (focus_control_index < 0) 614c72fcc34Sopenharmony_ci focus_control_index = 0; 615c72fcc34Sopenharmony_ci else if (focus_control_index >= (int)controls_count) 616c72fcc34Sopenharmony_ci focus_control_index = controls_count - 1; 617c72fcc34Sopenharmony_ci refocus_control(); 618c72fcc34Sopenharmony_ci break; 619c72fcc34Sopenharmony_ci case CMD_MIXER_TOGGLE_MUTE: 620c72fcc34Sopenharmony_ci toggle_mute(arg); 621c72fcc34Sopenharmony_ci break; 622c72fcc34Sopenharmony_ci case CMD_MIXER_TOGGLE_CAPTURE: 623c72fcc34Sopenharmony_ci toggle_capture(arg); 624c72fcc34Sopenharmony_ci break; 625c72fcc34Sopenharmony_ci case CMD_MIXER_BALANCE_CONTROL: 626c72fcc34Sopenharmony_ci balance_volumes(); 627c72fcc34Sopenharmony_ci break; 628c72fcc34Sopenharmony_ci } 629c72fcc34Sopenharmony_ci} 630c72fcc34Sopenharmony_ci 631c72fcc34Sopenharmony_cistatic void create(void) 632c72fcc34Sopenharmony_ci{ 633c72fcc34Sopenharmony_ci static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " "; 634c72fcc34Sopenharmony_ci 635c72fcc34Sopenharmony_ci widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0, 636c72fcc34Sopenharmony_ci attrs.mixer_frame, WIDGET_BORDER); 637c72fcc34Sopenharmony_ci if (screen_cols >= (int)(sizeof(title) - 1) + 2) { 638c72fcc34Sopenharmony_ci wattrset(mixer_widget.window, attrs.mixer_active); 639c72fcc34Sopenharmony_ci mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title); 640c72fcc34Sopenharmony_ci } 641c72fcc34Sopenharmony_ci init_mixer_layout(); 642c72fcc34Sopenharmony_ci display_card_info(); 643c72fcc34Sopenharmony_ci set_view_mode(view_mode); 644c72fcc34Sopenharmony_ci} 645c72fcc34Sopenharmony_ci 646c72fcc34Sopenharmony_cistatic void on_window_size_changed(void) 647c72fcc34Sopenharmony_ci{ 648c72fcc34Sopenharmony_ci create(); 649c72fcc34Sopenharmony_ci} 650c72fcc34Sopenharmony_ci 651c72fcc34Sopenharmony_cistatic void on_close(void) 652c72fcc34Sopenharmony_ci{ 653c72fcc34Sopenharmony_ci widget_free(&mixer_widget); 654c72fcc34Sopenharmony_ci} 655c72fcc34Sopenharmony_ci 656c72fcc34Sopenharmony_civoid mixer_shutdown(void) 657c72fcc34Sopenharmony_ci{ 658c72fcc34Sopenharmony_ci free_controls(); 659c72fcc34Sopenharmony_ci if (mixer) 660c72fcc34Sopenharmony_ci snd_mixer_close(mixer); 661c72fcc34Sopenharmony_ci if (current_selem_id) 662c72fcc34Sopenharmony_ci snd_mixer_selem_id_free(current_selem_id); 663c72fcc34Sopenharmony_ci} 664c72fcc34Sopenharmony_ci 665c72fcc34Sopenharmony_cistruct widget mixer_widget = { 666c72fcc34Sopenharmony_ci .handle_key = on_handle_key, 667c72fcc34Sopenharmony_ci .window_size_changed = on_window_size_changed, 668c72fcc34Sopenharmony_ci .close = on_close, 669c72fcc34Sopenharmony_ci}; 670c72fcc34Sopenharmony_ci 671c72fcc34Sopenharmony_civoid create_mixer_widget(void) 672c72fcc34Sopenharmony_ci{ 673c72fcc34Sopenharmony_ci create(); 674c72fcc34Sopenharmony_ci} 675