├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── defs.ninja ├── mk ├── pacmixer.1 ├── src ├── backend.c ├── backend.h ├── enums.h ├── frontend.h ├── frontend.mm ├── log.c ├── log.h ├── main.h ├── main.mm ├── middleware.h ├── middleware.m ├── settings.cpp ├── settings.h ├── types.h ├── types.m └── widgets │ ├── channels.h │ ├── channels.mm │ ├── checkbox.h │ ├── checkbox.mm │ ├── menu.h │ ├── menu.m │ ├── misc.h │ ├── misc.m │ ├── notice.h │ ├── notice.m │ ├── options.h │ ├── options.mm │ ├── widget.h │ └── widget.mm ├── tests ├── mock_pulseaudio.c ├── mock_pulseaudio.h ├── mock_variables.c ├── mock_variables.h ├── test_backend.cpp ├── test_main.mm ├── test_middleware.mm └── test_types.mm └── vendor ├── catch.hpp ├── cpptoml.h └── mkdirp.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: KenjiTakahashi 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: setup GNUStep 11 | run: > 12 | sudo apt update && 13 | sudo apt install -y --no-install-recommends \ 14 | ninja-build \ 15 | gnustep-make \ 16 | libgnustep-base-dev \ 17 | gobjc++ 18 | 19 | - uses: actions/checkout@v3 20 | - name: build 21 | run: ./mk tests 22 | - name: test 23 | run: ./pacmixer_run_tests 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pacmixer 2 | pacmixer_run_tests 3 | a.out 4 | .ninja* 5 | build.ninja 6 | build/ 7 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 0.1 (28.09.2012): 2 | - Initial release. 3 | 4 | == 0.1.1 (01.10.2012): 5 | - Fixed infinite loop with -O2. 6 | 7 | == 0.1.2 (25.10.2012): 8 | - Fixed controls names sometimes not displayed on 32bit systems. 9 | - Added 1/2/3/4/5 as synonym for F1/F2/F3/F4/F5 (by @arandomuser). 10 | - Properly removing observers from notification center. 11 | 12 | == 0.1.3 (24.11.2012): 13 | - Fixed controls sometimes displaying wrong values at start. 14 | - Fixed Monitor controls changing monitored sink values. 15 | - Using @protocol to avoid "multiple definitions" warnings. 16 | - Fixed single channel controls being printed too much to the left. 17 | - Fixed a corner case in disappearing controls removal. 18 | - Fixed some setFilter corner cases causing screen corruptions. 19 | - Fixed hidden widgets getting reprinted when changed outside the app. 20 | 21 | == 0.2 (05.02.2013): 22 | - Added ability to filter out PA internals/Monitors. 23 | - Properly dealing with non-existing/lost connections. 24 | - Fixed m/j/k crash when used on empty page. 25 | - Fixed possible mix-up of SINK and SINK_INPUT controls. 26 | - Fixed hidden controls sometimes not properly removed when gone. 27 | - Added ability to get/set card-wise options (e.g. profiles). 28 | 29 | == 0.3 (24.02.2013): 30 | - Fixed crash when trying to go inside on SETTINGS tab. 31 | - Fixed a small screen corruption introduced in 0.2. 32 | - Added ability to get/set control-wise options (e.g. ports). 33 | 34 | == 0.4 (17.05.2013): 35 | - Adjusted Makefile to work better on different distros. 36 | - Fixed some possible memory leaks. 37 | - Added a test suite. 38 | - Fixed screen corruptions happening on terminal resizes. 39 | - Fixed settings sometimes disappearing after card profile change. 40 | 41 | == 0.4.1 (09.03.2014): 42 | - Fixed INPUTS filtered as OUTPUTS and vice versa. 43 | - Added ability to set SINKs and SOURCEs as default/fallback. 44 | - Fixed segfault on changing single channel volume. 45 | 46 | == 0.5 (04.06.2014): 47 | - Fixed showing newly appeared card profile when SETTINGS panel is active. 48 | - Fixed RECORDING threated as PLAYBACK. 49 | - Added ability to set current device for PLAYBACK/RECORDING. 50 | - Deal with options (e.g. ports, profiles) changing dynamically. 51 | - Fixed segfault on gnustep-base < 1.24 [mainly Debian]. 52 | 53 | == 0.6 (08.06.2015): 54 | - Moved to ninja for building. 55 | - Better logging mechanism. 56 | - Added ability to configure start view (Fixes #8). 57 | - Reworked configuration. 58 | - Fixed crash when moving up (`h`) through options in settings view. 59 | - Added ability to change volume at a different pace (using J/K shortcuts). 60 | - Added ability to configure volume changing speed (both j/k and J/K). 61 | - Prevented ability to set volume above max value under some circumstances. 62 | - Added ability to filter options. 63 | - Added a manpage. 64 | 65 | == 0.6.1 (14.06.2015): 66 | - Fixed crash when $XDG_CONFIG_HOME variable is not set. 67 | - Fixed crash when $XDG_CONFIG_HOME points at non-existing directory. 68 | - Fixed crash when "[Log].Dir" does not exist. 69 | 70 | == 0.6.2 (30.08.2015): 71 | - Fixed log occasionally producing files with garbage names. 72 | - Fixed crash when moving up/down in card profiles. 73 | 74 | == 0.6.3 (15.11.2015): 75 | - Fixed possible memory leak in backend. 76 | - Fixed possible SEGFAULT when new client appears. 77 | 78 | == 0.6.4 (30.03.2023): 79 | - Fixed possible corruption due to invalid notification formatting (by @FuzzyNovaGoblin). 80 | - Fixed possible SEGFAULT on a PA server restart. 81 | - Fixed screen corruption on a PA server restart. 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/KenjiTakahashi/pacmixer/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/KenjiTakahashi/pacmixer/actions/workflows/tests.yml) 2 | 3 | **pacmixer** is an [alsamixer][alsamixer] alike for PulseAudio. 4 | 5 | I started this, because I've found no full-blown, terminal based mixers available for PA. All there are are either [CLI][CLI] or some kinds of GNOME/KDE applets. That's not what I wanted, so in the end I decided to go for it myself. 6 | 7 | It was also a good starting point to finally learn myself some ObjC :). 8 | 9 | Back in the old days, there were a good mixer for ALSA (alsamixer), so I thought about taking some of their ideas, mix it with mine, and see what happens. 10 | 11 | **Updating past 0.5:** Configuration mechanism has been reworked to be more flexible and integrate better with GNU/Linux environment. It means that: 12 | 13 | * Pacmixer configuration is no longer available through settings tab (it is reserved for PA options). 14 | * Settings are now configured using [configuration file](https://github.com/KenjiTakahashi/pacmixer#configuration). 15 | * Settings configured with version <= 0.5 will be reset to defaults after update. 16 | * Settings storage follows XDG => No more creepy "GNUStep" directory. 17 | 18 | ## screenshot 19 | ![screenshot](http://img.kenji.sx/pacmixer2.png) 20 | 21 | ## requirements 22 | * libpulse 23 | * ncurses 24 | * gnustep-base 25 | * gcc-objc (for compilation) 26 | * ninja (for compilation) 27 | 28 | ## installation 29 | ```sh 30 | # ./mk install 31 | ``` 32 | 33 | ## usage 34 | **Note:** There is also an introductory video available [here](http://www.youtube.com/watch?v=s3qk_Fn1Yeo), thanks to [**@gotbletu**](https://github.com/gotbletu). 35 | 36 | **Note:** `man pacmixer` is your friend now as well. 37 | 38 | **pacmixer** comes with built-in help, but here's the shortcuts reference, just in case. 39 | 40 | ``` 41 | h (or Left): Moves to the previous control. 42 | l (or Right): Moves to the next control. 43 | k (or Up): Increases the volume standardly. 44 | K (or Shift+Up): Increases the volume fastly. 45 | j (or Down): Decreases the volume standardly. 46 | J (or Shift+Down): Decreases the volume fastly. 47 | m: Mutes the volume. 48 | d: Sets as default. 49 | i: Enters inside mode. 50 | s: Enters settings mode. 51 | q (or Esc): Exits settings/inside mode or exit the application. 52 | F1-F5 (or 1-5): Switches to All/Playback/Recording/Outputs/Inputs view, respectively. 53 | F12 (or 0): Switches to Settings view. 54 | ``` 55 | 56 | #### settings view 57 | Used to change card wise settings (e.g. profiles). 58 | 59 | ``` 60 | h (or Left): Moves to the previous group of settings. 61 | l (or Right): Moves to the next group of settings. 62 | k (or Up): Moves to the previous setting within group. 63 | j (or Down): Moves to the next setting within group. 64 | space: (Un)checks highlighted setting. 65 | q (or Esc): Exits the application. 66 | ``` 67 | 68 | #### inside mode 69 | Used to adjust specific channel's volume. 70 | 71 | All shortcuts (besides `q`) work the same, except that they affect single channel instead of the whole sink/source. 72 | 73 | #### settings mode 74 | Used to change controls settings (e.g. ports). 75 | 76 | Shortcuts work like in outside mode, except that: 77 | 78 | * `space` is used to check setting. 79 | * `h` and `l` iterate only over controls which actually have settings. 80 | 81 | ## configuration 82 | Pacmixer uses [toml](https://github.com/toml-lang/toml) based configuration file stored in either `$XDG_CONFIG_HOME/pacmixer/settings.toml` or `$HOME/.config/pacmixer/settings.toml`. 83 | 84 | When run for the first time, it will create a new file with all configuration options set to their defaults. You can use this file as a basis and/or checkout the reference below. 85 | 86 | **[Display]** 87 | 88 | `StartScreen` (string) - Sets which screen should be visible when starting pacmixer. Available values are "All", "Playback", "Recording", "Outputs", "Inputs" and "Settings". 89 | 90 | **[Control]** 91 | 92 | `UpSpeed` (integer) - Sets the speed of the standard volume up command. 93 | 94 | `FastUpSpeed` (integer) - Sets the speed of the fast volume up command. 95 | 96 | `DownSpeed` (integer) - Sets the speed of the standard volume down command. 97 | 98 | `FastDownSpeed` (integer) - Sets the speed of the standard volume down command. 99 | 100 | **[Filter]** 101 | 102 | `Monitors` (boolean) - Filters out Monitor controls. 103 | 104 | `Internals` (boolean) - Filters out PA internal controls. 105 | 106 | `Options` (boolean) - Filters out Options part of the controls. 107 | 108 | **[Log]** 109 | 110 | `Dir` (string) - Directory where log file(s) will be stored. Leave empty string to disable logging. 111 | 112 | ## something's broken? 113 | 114 | Please include the log file (`$HOME/.local/share/pacmixer/pacmixer.log` by default) with your bug report. 115 | 116 | If it crashes hard (i.e. SEGFAULTS), it might also be useful to compile in debug mode and run through `gdb` to retrieve stacktrace. 117 | 118 | ```sh 119 | $ ./mk debug 120 | $ gdb ./pacmixer 121 | gdb$ run 122 | gdb$ bt 123 | ``` 124 | 125 | That will make it easier to identify the problem. 126 | 127 | ## tests 128 | 129 | Type 130 | ```sh 131 | $ ./mk tests 132 | $ ./pacmixer_run_tests 133 | ``` 134 | 135 | **Note:** It is a [Catch][catch] executable, all [options] apply. 136 | 137 | [alsamixer]: http://en.wikipedia.org/wiki/Alsamixer 138 | [CLI]: http://en.wikipedia.org/wiki/Command-line_interface 139 | [catch]: https://github.com/philsquared/Catch 140 | [options]: https://github.com/philsquared/Catch/blob/master/docs/command-line.md 141 | -------------------------------------------------------------------------------- /defs.ninja: -------------------------------------------------------------------------------- 1 | # pacmixer 2 | # Copyright (C) 2015 Karol 'Kenji Takahashi' Woźniak 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining 5 | # a copy of this software and associated documentation files (the "Software"), 6 | # to deal in the Software without restriction, including without limitation 7 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | # and/or sell copies of the Software, and to permit persons to whom the 9 | # Software is furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included 12 | # in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | cflags = -std=c99 23 | cppflags = -std=c++11 24 | mflags = `gnustep-config --objc-flags` 25 | mmflags = -fobjc-exceptions -fobjc-call-cxx-cdtors -D __STDC_LIMIT_MACROS 26 | 27 | rule c 28 | depfile = $out.d 29 | command = gcc -MMD -MF $out.d $flags $cflags -c $in -o $out 30 | 31 | rule cpp 32 | depfile = $out.d 33 | command = g++ -MMD -MF $out.d $flags $cppflags -c $in -o $out 34 | 35 | rule m 36 | depfile = $out.d 37 | command = gcc -MMD -MF $out.d $flags $cflags $mflags -c $in -o $out 38 | 39 | rule mm 40 | depfile = $out.d 41 | command = gcc -MMD -MF $out.d $flags $cppflags $mflags $mmflags -c $in -o $out 42 | 43 | ldflags = -lgnustep-base -lobjc -lpanel -lcurses -lstdc++ 44 | 45 | rule link 46 | command = g++ $in $ldflags $ldflags2 -o $out 47 | -------------------------------------------------------------------------------- /mk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # pacmixer 3 | # Copyright (C) 2015 Karol 'Kenji Takahashi' Woźniak 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining 6 | # a copy of this software and associated documentation files (the "Software"), 7 | # to deal in the Software without restriction, including without limitation 8 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | # and/or sell copies of the Software, and to permit persons to whom the 10 | # Software is furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included 13 | # in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 21 | # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | set -e 24 | 25 | FLAGS=${FLAGS:-$(echo "$*" | grep -q 'debug' && echo "-Wall -O0 -ggdb3" || echo "-Wall -O2")} 26 | BUILDFILE="build.ninja" 27 | EXCLUDES="src/backend.c src/main.mm" 28 | 29 | printf "include defs.ninja\n" > "${BUILDFILE}" 30 | 31 | echo "$*" | grep -q 'tests' && TESTS="tests/" 32 | for TYPE in c cpp m mm; do 33 | for FILE in $(find src/ ${TESTS} -type f -name "*.${TYPE}"); do 34 | if !([ -n "${TESTS}" ] && echo "${EXCLUDES}" | grep -q "${FILE}"); then 35 | OUTFILE="build/${TESTS}$(basename ${FILE}).o" 36 | OBJECTS="${OBJECTS} ${OUTFILE}" 37 | printf "build ${OUTFILE}: ${TYPE} ${FILE}\n" >> "${BUILDFILE}" 38 | fi 39 | done 40 | done 41 | 42 | if [ -n "$TESTS" ]; then 43 | printf "flags = ${FLAGS} -D TESTS=1\n" >> "${BUILDFILE}" 44 | printf "build pacmixer_run_tests: link ${OBJECTS}\n" >> "${BUILDFILE}" 45 | else 46 | printf "flags = ${FLAGS}\nldflags2 = -lpulse\n" >> "${BUILDFILE}" 47 | printf "build pacmixer: link ${OBJECTS}\n" >> "${BUILDFILE}" 48 | fi 49 | 50 | ninja -v 51 | 52 | if echo "$*" | grep -q 'install'; then 53 | PREFIX=${PREFIX:-/usr/local} 54 | MANPREFIX=${MANPREFIX:-"${PREFIX}/share/man"} 55 | DIR="${DESTDIR}${PREFIX}" 56 | BIN="${DIR}/bin" 57 | MAN="${DESTDIR}${MANPREFIX}/man1" 58 | echo "Installing executable file into ${BIN}" 59 | mkdir -p "${BIN}" 60 | install -m 755 pacmixer "${BIN}" 61 | echo "Installing man page file into ${MAN}" 62 | mkdir -p "${MAN}" 63 | install -m 644 pacmixer.1 "${MAN}" 64 | fi 65 | -------------------------------------------------------------------------------- /pacmixer.1: -------------------------------------------------------------------------------- 1 | .TH PACMIXER 1 2 | .SH NAME 3 | pacmixer \- PulseAudio Curses Mixer. 4 | .SH SYNOPSIS 5 | pacmixer 6 | .SH DESCRIPTION 7 | pacmixer is an interactive controller for PulseAudio mixers and settings. 8 | 9 | It operates in three modes: OUTSIDE, INSIDE and SETTINGS. The OUTSIDE mode is the default (except for Settings view, where default is SETTINGS mode) and allows changing options and values on a per control basis. The INSIDE mode can be used to control individual channels. The SETTINGS mode can be used to adjust control settings. 10 | .SH KEYBOARD COMMANDS 11 | .TP 12 | .BR h ", " Left 13 | Moves to the previous control in OUTSIDE mode, channel in INSIDE mode, group in SETTINGS mode. 14 | .TP 15 | .BR l ", " Right 16 | Moves to the next control in OUTSIDE mode, channel in INSIDE mode, group in SETTINGS mode. 17 | .TP 18 | .BR k ", " Up 19 | Increases the volume in OUTSIDE and INSIDE modes standardly. Moves to the previous setting in SETTINGS mode. 20 | .TP 21 | .BR K ", " Shift+Up 22 | Increases the volume in OUTSIDE and INSIDE modes fastly. 23 | .TP 24 | .BR j ", " Down 25 | Decreases the volume in OUTSIDE and INSIDE modes standardly. Moves to the next setting in SETTINGS mode. 26 | .TP 27 | .BR J ", " Shift+Down 28 | Decreases the volume in OUTSIDE and INSIDE modes fastly. 29 | .TP 30 | .B m 31 | Mutes the volume. 32 | .TP 33 | .B d 34 | Sets control as default. 35 | .TP 36 | .B i 37 | Enters INSIDE mode. 38 | .TP 39 | .B s 40 | Enters SETTINGS mode. 41 | .TP 42 | .BR q ", " Esc 43 | Exits pacmixer in OUTSIDE mode. Moves back to OUTSIDE mode in INSIDE and SETTINGS modes. 44 | .TP 45 | .BR F1 \- F5 ", " 1 \- 5 46 | Switches to All/Playback/Recording/Outputs/Inputs view, respectively. 47 | .TP 48 | .BR F12 ", " 0 49 | Switches to Settings view. 50 | .TP 51 | .B Space 52 | (Un)checks setting in SETTINGS mode. 53 | .SH CONFIGURATION 54 | pacmixer uses toml based configuration file stored in either 55 | .I $XDG_CONFIG_HOME/pacmixer/settings.toml 56 | or 57 | .I $HOME/.config/pacmixer/settings.toml. 58 | 59 | When run for the first time, it will create a new file with all configuration options set to their defaults. This file can be used as a basis along with the reference below. 60 | .SS [Display] 61 | .TP 62 | .B StartView 63 | .I string 64 | Sets the view that should be visible when starting pacmixer. Available values are "All", "Playback", "Recording", "Outputs", "Inputs" and "Settings". 65 | .SS [Control] 66 | .TP 67 | .B UpSpeed 68 | .I integer 69 | Sets the speed of the standard volume up command. 70 | .TP 71 | .B FastUpSpeed 72 | .I integer 73 | Sets the speed of the fast volume up command. 74 | .TP 75 | .B DownSpeed 76 | .I integer 77 | Sets the speed of the standard volume down command. 78 | .TP 79 | .B FastDownSpeed 80 | .I integer 81 | Sets the speed of the fast volume down command. 82 | .SS [Filter] 83 | .TP 84 | .B Monitors 85 | .I boolean 86 | Filters out Monitor controls. 87 | .TP 88 | .B Internals 89 | .I boolean 90 | Filters out PA internal controls. 91 | .TP 92 | .B Options 93 | .I boolean 94 | Filters out Options part of the controls. 95 | .SS [Log] 96 | .TP 97 | .B Dir 98 | .I string 99 | Directory where log file(s) will be stored. Leave empty string to disable logging. 100 | .SH AUTHOR 101 | Karol 'Kenji Takahashi' Woźniak @ 102 | .I kenji.sx 103 | .SH HOMEPAGE 104 | .I https://github.com/KenjiTakahashi/pacmixer 105 | -------------------------------------------------------------------------------- /src/backend.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2012 - 2015 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "backend.h" 21 | 22 | 23 | _CB_DEVICE(_cb_sink, pa_sink_info, _CB1, SINK); 24 | _CB_DEVICE(_cb_u_sink, pa_sink_info, _CB_U, SINK); 25 | _CB_DEVICE(_cb_source, pa_source_info, _CB1, SOURCE); 26 | _CB_DEVICE(_cb_u_source, pa_source_info, _CB_U, SOURCE); 27 | 28 | 29 | _CB_STREAM(_cb_sink_input, pa_sink_input_info, _CB_STREAM_, SINK_INPUT); 30 | _CB_STREAM(_cb_u_sink_input, pa_sink_input_info, _CB_STREAM_U, SINK_INPUT); 31 | _CB_STREAM(_cb_source_output, pa_source_output_info, _CB_STREAM_, SOURCE_OUTPUT); 32 | _CB_STREAM(_cb_u_source_output, pa_source_output_info, _CB_STREAM_U, SOURCE_OUTPUT); 33 | 34 | _CB_SET_VOLUME(_cb_s_sink, pa_sink_info, sink, _by_index); 35 | _CB_SET_VOLUME(_cb_s_sink_input, pa_sink_input_info, sink_input, ); 36 | _CB_SET_VOLUME(_cb_s_source, pa_source_info, source, _by_index); 37 | _CB_SET_VOLUME(_cb_s_source_output, pa_source_output_info, source_output, ); 38 | 39 | 40 | context_t *backend_new(callback_t *callback) { 41 | context_t *context = (context_t*)malloc(sizeof(context_t)); 42 | context->loop = pa_threaded_mainloop_new(); 43 | context->state = PA_CONTEXT_UNCONNECTED; 44 | backend_init(context, callback); 45 | pa_threaded_mainloop_start(context->loop); 46 | return context; 47 | } 48 | 49 | void backend_init(context_t *context, callback_t* callback) { 50 | pa_mainloop_api *api = pa_threaded_mainloop_get_api(context->loop); 51 | context->context = pa_context_new(api, "pacmixer"); 52 | pa_context_set_state_callback(context->context, _cb_state_changed, callback); 53 | pa_context_connect(context->context, NULL, PA_CONTEXT_NOFAIL, NULL); 54 | } 55 | 56 | void backend_destroy(context_t *context) { 57 | pa_threaded_mainloop_stop(context->loop); 58 | pa_context_disconnect(context->context); 59 | pa_context_unref(context->context); 60 | pa_threaded_mainloop_free(context->loop); 61 | free(context); 62 | } 63 | 64 | void backend_volume_set(context_t *c, backend_entry_type type, uint32_t idx, int i, int v) { 65 | volume_callback_t *volume = (volume_callback_t*)malloc(sizeof(volume_callback_t)); 66 | volume->index = i; 67 | volume->value = v; 68 | switch(type) { 69 | case SINK: 70 | pa_context_get_sink_info_by_index(c->context, idx, _cb_s_sink, volume); 71 | break; 72 | case SINK_INPUT: 73 | pa_context_get_sink_input_info(c->context, idx, _cb_s_sink_input, volume); 74 | break; 75 | case SOURCE: 76 | pa_context_get_source_info_by_index(c->context, idx, _cb_s_source, volume); 77 | break; 78 | case SOURCE_OUTPUT: 79 | pa_context_get_source_output_info(c->context, idx, _cb_s_source_output, volume); 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | 86 | void backend_volume_setall(context_t *c, backend_entry_type type, uint32_t idx, int *v, int chnum) { 87 | pa_cvolume volume; 88 | volume.channels = chnum; 89 | for(int i = 0; i < chnum; ++i) { 90 | volume.values[i] = v[i]; 91 | } 92 | switch(type) { 93 | case SINK: 94 | pa_context_set_sink_volume_by_index(c->context, idx, &volume, NULL, NULL); 95 | break; 96 | case SINK_INPUT: 97 | pa_context_set_sink_input_volume(c->context, idx, &volume, NULL, NULL); 98 | break; 99 | case SOURCE: 100 | pa_context_set_source_volume_by_index(c->context, idx, &volume, NULL, NULL); 101 | break; 102 | case SOURCE_OUTPUT: 103 | pa_context_set_source_output_volume(c->context, idx, &volume, NULL, NULL); 104 | break; 105 | default: 106 | break; 107 | } 108 | } 109 | 110 | void backend_mute_set(context_t *c, backend_entry_type type, uint32_t idx, int v) { 111 | switch(type) { 112 | case SINK: 113 | pa_context_set_sink_mute_by_index(c->context, idx, v, NULL, NULL); 114 | break; 115 | case SINK_INPUT: 116 | pa_context_set_sink_input_mute(c->context, idx, v, NULL, NULL); 117 | break; 118 | case SOURCE: 119 | pa_context_set_source_mute_by_index(c->context, idx, v, NULL, NULL); 120 | break; 121 | case SOURCE_OUTPUT: 122 | pa_context_set_source_output_mute(c->context, idx, v, NULL, NULL); 123 | break; 124 | default: 125 | break; 126 | } 127 | } 128 | 129 | void backend_card_profile_set(context_t *c, backend_entry_type type, uint32_t idx, const char *active) { 130 | pa_context_set_card_profile_by_index(c->context, idx, active, NULL, NULL); 131 | } 132 | 133 | void backend_default_set(context_t *c, backend_entry_type type, const char *internalName) { 134 | switch(type) { 135 | case SINK: 136 | pa_context_set_default_sink(c->context, internalName, NULL, NULL); 137 | break; 138 | case SOURCE: 139 | pa_context_set_default_source(c->context, internalName, NULL, NULL); 140 | break; 141 | default: 142 | break; 143 | } 144 | } 145 | 146 | void backend_port_set(context_t *c, backend_entry_type type, uint32_t idx, const char *active) { 147 | switch(type) { 148 | case SINK: 149 | pa_context_set_sink_port_by_index(c->context, idx, active, NULL, NULL); 150 | break; 151 | case SOURCE: 152 | pa_context_set_source_port_by_index(c->context, idx, active, NULL, NULL); 153 | break; 154 | default: 155 | break; 156 | } 157 | } 158 | 159 | void backend_device_set(context_t *c, backend_entry_type type, uint32_t idx, const char *active) { 160 | switch(type) { 161 | case SINK_INPUT: 162 | pa_context_move_sink_input_by_name(c->context, idx, active, NULL, NULL); 163 | break; 164 | case SOURCE_OUTPUT: 165 | pa_context_move_source_output_by_name(c->context, idx, active, NULL, NULL); 166 | break; 167 | default: 168 | break; 169 | } 170 | } 171 | 172 | void _cb_state_changed(pa_context *context, void *userdata) { 173 | callback_t *callback = (callback_t*)userdata; 174 | pa_context_state_t state = pa_context_get_state(context); 175 | PACMIXER_LOG("B:server state changed to %d", state); 176 | switch(state) { 177 | case PA_CONTEXT_READY: 178 | pa_context_set_subscribe_callback(context, _cb_event, callback); 179 | pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_ALL, NULL, NULL); 180 | pa_context_get_sink_input_info_list(context, _cb_sink_input, callback); 181 | pa_context_get_sink_info_list(context, _cb_sink, callback); 182 | pa_context_get_source_info_list(context, _cb_source, callback); 183 | pa_context_get_source_output_info_list(context, _cb_source_output, callback); 184 | pa_context_get_card_info_list(context, _cb_card, callback); 185 | pa_context_get_server_info(context, _cb_server, callback); 186 | ((tstate_callback_func)(callback->state))(callback->self, S_CAME); 187 | break; 188 | case PA_CONTEXT_FAILED: 189 | case PA_CONTEXT_TERMINATED: 190 | pa_context_unref(context); 191 | ((tstate_callback_func)(callback->state))(callback->self, S_GONE); 192 | break; 193 | default: 194 | break; 195 | } 196 | } 197 | 198 | void _cb_client(pa_context *c, const pa_client_info *info, int eol, void *userdata) { 199 | if(!eol && info->index != PA_INVALID_INDEX) { 200 | client_callback_t *client_callback = (client_callback_t*)userdata; 201 | callback_t *callback = client_callback->callback; 202 | PACMIXER_LOG("B:%d:%s appeared", client_callback->index, info->name); 203 | backend_data_t data; 204 | data.channels = client_callback->channels; 205 | data.volumes = client_callback->volumes; 206 | data.channels_num = client_callback->chnum; 207 | data.device = client_callback->device; 208 | data.option = NULL; 209 | data.internalName = (char*)malloc((strlen(info->name) + 1) * sizeof(char)); 210 | strcpy(data.internalName, info->name); 211 | ((tcallback_add_func)(callback->add))(callback->self, info->name, client_callback->type, client_callback->index, &data); 212 | free(data.internalName); 213 | free(client_callback->channels); 214 | free(client_callback->volumes); 215 | free(client_callback); 216 | } 217 | } 218 | 219 | void _cb_card(pa_context *c, const pa_card_info *info, int eol, void *userdata) { 220 | if(!eol && info->index != PA_INVALID_INDEX) { 221 | callback_t *callback = (callback_t*)userdata; 222 | int n = info->n_profiles; 223 | backend_data_t data; 224 | if(n > 0) { 225 | data.option = _do_card(info, n); 226 | } else { 227 | data.option = NULL; 228 | } 229 | const char *desc = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION); 230 | ((tcallback_add_func)(callback->add))(callback->self, desc, CARD, info->index, &data); 231 | if(n > 0) { 232 | _do_option_free(data.option, n); 233 | } 234 | } 235 | } 236 | 237 | void _cb_u_card(pa_context *c, const pa_card_info *info, int eol, void *userdata) { 238 | if(!eol && info->index != PA_INVALID_INDEX) { 239 | callback_t *callback = (callback_t*)userdata; 240 | int n = info->n_profiles; 241 | backend_data_t data; 242 | if(n > 0) { 243 | data.option = _do_card(info, n); 244 | } else { 245 | data.option = NULL; 246 | } 247 | ((tcallback_update_func)(callback->update))(callback->self, CARD, info->index, &data); 248 | if(n > 0) { 249 | _do_option_free(data.option, n); 250 | } 251 | } 252 | } 253 | 254 | void _cb_server(pa_context *c, const pa_server_info *info, void *userdata) { 255 | callback_t *callback = (callback_t*)userdata; 256 | 257 | backend_data_t data; 258 | data.defaults = (backend_default_t*)malloc(sizeof(backend_default_t)); 259 | 260 | const char *sink_name = info->default_sink_name; 261 | data.defaults->sink = (char*)malloc((strlen(sink_name) + 1) * sizeof(char)); 262 | strcpy(data.defaults->sink, sink_name); 263 | 264 | const char *source_name = info->default_source_name; 265 | data.defaults->source = (char*)malloc((strlen(source_name) + 1) * sizeof(char)); 266 | strcpy(data.defaults->source, source_name); 267 | 268 | ((tcallback_update_func)(callback->update))(callback->self, SERVER, 0, &data); 269 | 270 | free(data.defaults->source); 271 | free(data.defaults->sink); 272 | free(data.defaults); 273 | } 274 | 275 | #define _CB_SINGLE_EVENT(event, type, by_index)\ 276 | if(t__ == PA_SUBSCRIPTION_EVENT_ ## event) {\ 277 | if(t_ == PA_SUBSCRIPTION_EVENT_CHANGE && idx != PA_INVALID_INDEX) {\ 278 | pa_context_get_ ## type ## _info ## by_index(c, idx, _cb_u_ ## type, userdata);\ 279 | }\ 280 | if(t_ == PA_SUBSCRIPTION_EVENT_REMOVE && idx != PA_INVALID_INDEX) {\ 281 | callback_t *callback = (callback_t*)userdata;\ 282 | ((tcallback_remove_func)(callback->remove))(callback->self, idx, event);\ 283 | }\ 284 | if(t_ == PA_SUBSCRIPTION_EVENT_NEW && idx != PA_INVALID_INDEX) {\ 285 | pa_context_get_ ## type ## _info ## by_index(c, idx, _cb_ ## type, userdata);\ 286 | }\ 287 | }\ 288 | 289 | void _cb_event(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { 290 | int t_ = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; 291 | int t__ = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; 292 | _CB_SINGLE_EVENT(CARD, card, _by_index); 293 | _CB_SINGLE_EVENT(SINK, sink, _by_index); 294 | _CB_SINGLE_EVENT(SINK_INPUT, sink_input, ); 295 | _CB_SINGLE_EVENT(SOURCE, source, _by_index); 296 | _CB_SINGLE_EVENT(SOURCE_OUTPUT, source_output, ); 297 | if(t__ == PA_SUBSCRIPTION_EVENT_SERVER) { 298 | pa_context_get_server_info(c, _cb_server, userdata); 299 | } 300 | } 301 | 302 | backend_channel_t *_do_channels(pa_cvolume volume, uint8_t chnum) { 303 | backend_channel_t *channels = (backend_channel_t*)malloc(chnum * sizeof(backend_channel_t)); 304 | for(int i = 0; i < chnum; ++i) { 305 | channels[i].maxLevel = PA_VOLUME_UI_MAX; 306 | channels[i].normLevel = PA_VOLUME_NORM; 307 | channels[i].isMutable = 1; 308 | } 309 | return channels; 310 | } 311 | 312 | backend_volume_t *_do_volumes(pa_cvolume volume, uint8_t chnum, int mute) { 313 | backend_volume_t *volumes = (backend_volume_t*)malloc(chnum * sizeof(backend_volume_t)); 314 | for(int i = 0; i < chnum; ++i) { 315 | volumes[i].level = volume.values[i]; 316 | volumes[i].mute = mute; 317 | } 318 | return volumes; 319 | } 320 | 321 | backend_option_t *_do_card(const pa_card_info *info, int n) { 322 | backend_option_t *card = (backend_option_t*)malloc(sizeof(backend_option_t)); 323 | pa_card_profile_info *profiles = info->profiles; 324 | card->descriptions = (char**)malloc(n * sizeof(char*)); 325 | card->names = (char**)malloc(n * sizeof(char*)); 326 | for(int i = 0; i < n; ++i) { 327 | const char *desc = profiles[i].description; 328 | card->descriptions[i] = (char*)malloc((strlen(desc) + 1) * sizeof(char)); 329 | strcpy(card->descriptions[i], desc); 330 | const char *name = profiles[i].name; 331 | card->names[i] = (char*)malloc((strlen(name) + 1) * sizeof(char)); 332 | strcpy(card->names[i], name); 333 | } 334 | const char *active = info->active_profile->description; 335 | card->active = (char*)malloc((strlen(active) + 1) * sizeof(char)); 336 | strcpy(card->active, active); 337 | card->size = n; 338 | return card; 339 | } 340 | 341 | void _do_option_free(backend_option_t *option, int n) { 342 | if(option == NULL) { 343 | return; 344 | } 345 | free(option->active); 346 | for(int i = 0; i < n; ++i) { 347 | free(option->descriptions[i]); 348 | free(option->names[i]); 349 | } 350 | free(option->descriptions); 351 | free(option->names); 352 | free(option); 353 | } 354 | -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2012 - 2015 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifdef TESTS 21 | #include "../tests/mock_pulseaudio.h" 22 | #else 23 | #include 24 | #endif 25 | #include 26 | #include 27 | #include "log.h" 28 | 29 | 30 | typedef enum { 31 | S_CAME, 32 | S_GONE 33 | } server_state; 34 | 35 | /** 36 | * Holds information necessary to communicate with PA server. 37 | * Returned from backend_init() and passed to all other manipulation 38 | * functions. 39 | * 40 | * @see backend_new() 41 | */ 42 | typedef struct CONTEXT { 43 | pa_threaded_mainloop *loop; /**< Ref to PA event loop. */ 44 | pa_context *context; /**< PA context. */ 45 | pa_context_state_t state; /**< PA server state. */ 46 | } context_t; 47 | 48 | /** 49 | * Holds references to higher level callbacks and the middleware object. 50 | */ 51 | typedef struct CALLBACK { 52 | void *add; 53 | void *update; 54 | void *remove; 55 | void *state; 56 | void *self; 57 | } callback_t; 58 | 59 | /** 60 | * Holds information about channel. 61 | */ 62 | typedef struct BACKEND_CHANNEL { 63 | int maxLevel; 64 | int normLevel; 65 | int isMutable; 66 | } backend_channel_t; 67 | 68 | /** 69 | * Holds information about channel's current setting. 70 | */ 71 | typedef struct BACKEND_VOLUME { 72 | int level; 73 | int mute; 74 | } backend_volume_t; 75 | 76 | /** 77 | * Holds a list of options with their names and descriptions. 78 | * Used to store cards profiles and controls ports. 79 | */ 80 | typedef struct BACKEND_OPTION { 81 | char **descriptions; 82 | char **names; 83 | char *active; 84 | uint8_t size; 85 | } backend_option_t; 86 | 87 | /** 88 | * Holds information about default/fallback options of the PA server. 89 | */ 90 | typedef struct BACKEND_DEFAULT { 91 | char *sink; 92 | char *source; 93 | } backend_default_t; 94 | 95 | /** 96 | * Structure used to leverage data to higher level. 97 | * Middleware should check if all the structures inside are initialized, 98 | * because it is not guaranted in any way and it is OK if some are not. 99 | */ 100 | typedef struct BACKEND_DATA { 101 | backend_channel_t *channels; 102 | backend_volume_t *volumes; 103 | uint8_t channels_num; 104 | backend_option_t *option; 105 | backend_default_t *defaults; 106 | char *internalName; 107 | uint32_t device; 108 | } backend_data_t; 109 | 110 | /** 111 | * Controls types. 112 | */ 113 | typedef enum { 114 | SINK = 0, 115 | SINK_INPUT = 1, 116 | SOURCE = 2, 117 | SOURCE_OUTPUT = 3, 118 | CARD, /**< Virtual type representing whole sound card. */ 119 | SERVER /**< Virtual type representing whole PA server. */ 120 | } backend_entry_type; 121 | 122 | /** 123 | * Initializes all necessary mechanisms and data. 124 | * 125 | * @param callback Struct with higher level (middleware) callbacks. 126 | * Only the 'state' callback is used here for dealing with lost/failed 127 | * connections. 128 | * 129 | * @return CONTEXT which contains all necessary information about connection. 130 | * It will be passed as first argument to all other API parts. 131 | */ 132 | context_t *backend_new(callback_t*); 133 | 134 | /** 135 | * Starts the PA event loop and subscribes appropriate events. 136 | * 137 | * @param context CONTEXT as returned by backend_init(). 138 | * @param callback Struct with higher level (middleware) callbacks. 139 | */ 140 | void backend_init(context_t*, callback_t*); 141 | 142 | /** 143 | * Stops PA event loop and frees resources. 144 | * 145 | * @param context CONTEXT as returned by backend_init(). 146 | */ 147 | void backend_destroy(context_t*); 148 | 149 | /** 150 | * Sets volume for a single channel. 151 | * 152 | * @param c CONTEXT as returned by backend_init(). 153 | * @param type Type of the control. 154 | * By specifing type, we can use this one function for every control. 155 | * @param idx PA internal control index. 156 | * @param i Channel index (starting from 0). 157 | * @param v New value to set. 158 | * 159 | * @see backend_volume_setall() 160 | */ 161 | void backend_volume_set(context_t*, backend_entry_type, uint32_t, int, int); 162 | 163 | /** 164 | * Sets volume for multiple channels at once. 165 | * Might be useful to avoid clashes, 166 | * e.g. when there are multiple channels and they're locked together. 167 | * 168 | * @param c CONTEXT as returned by backend_init(). 169 | * @param type Type of the control. 170 | * @param idx PA internal control index. 171 | * @param v Array of new values to set. 172 | * @param chnum Number of items in @param v. 173 | * 174 | * @see backend_volume_set() 175 | */ 176 | void backend_volume_setall(context_t*, backend_entry_type, uint32_t, int*, int); 177 | 178 | /** 179 | * Sets mute value for control. 180 | * Note that there is not way to mute a single channel! 181 | * 182 | * @param c CONTEXT as returned by backend_init(). 183 | * @param type Type of the control. 184 | * @param idx PA internal control index. 185 | * @param v New value to set. It's boolean: 0=False, otherwise=True. 186 | */ 187 | void backend_mute_set(context_t*, backend_entry_type, uint32_t, int); 188 | 189 | /** 190 | * Sets active profile for a card. 191 | * 192 | * @param c CONTEXT as returned by backend_init(). 193 | * @param type Type of the control. It is always CARD here. 194 | * @param idx PA internal control index. 195 | * @param active Active profile's name. 196 | */ 197 | void backend_card_profile_set(context_t*, backend_entry_type, uint32_t, const char*); 198 | 199 | /** 200 | * Sets default sink/source for given context. 201 | * Only SINK and SOURCE entry types have this property, 202 | * for other types this function does nothing. 203 | * 204 | * @param c CONTEXT as returned by backend_init(). 205 | * @param type Type of the control. 206 | * @param internalName Name of the control to be set as default. 207 | */ 208 | void backend_default_set(context_t*, backend_entry_type, const char*); 209 | 210 | /** 211 | * Sets active port value for a control. 212 | * 213 | * @param c CONTEXT as returned by backend_init(). 214 | * @param type Type of the control. Should be SINK or SOURCE. 215 | * @param idx PA internal control index. 216 | * @param active Active port's name. 217 | */ 218 | void backend_port_set(context_t*, backend_entry_type, uint32_t, const char*); 219 | 220 | /** 221 | * Sets active device value for a control. 222 | * @param c CONTEXT as returned by backend_init(). 223 | * @param type Type of the control. Should be SINK_INPUT or SOURCE_OUTPUT. 224 | * @param idx PA internal control index. 225 | * @param active Active device's name. 226 | */ 227 | void backend_device_set(context_t*, backend_entry_type, uint32_t, const char*); 228 | 229 | typedef void (*tcallback_add_func)(void*, const char*, backend_entry_type, uint32_t, const backend_data_t*); 230 | typedef void (*tcallback_update_func)(void*, backend_entry_type, uint32_t, backend_data_t*); 231 | typedef void (*tcallback_remove_func)(void*, uint32_t, backend_entry_type); 232 | typedef void (*tstate_callback_func)(void*, server_state); 233 | 234 | typedef struct CLIENT_CALLBACK { 235 | callback_t *callback; 236 | backend_channel_t *channels; 237 | backend_volume_t *volumes; 238 | uint8_t chnum; 239 | uint32_t index; 240 | backend_entry_type type; 241 | uint32_t device; 242 | } client_callback_t; 243 | 244 | typedef struct VOLUME_CALLBACK { 245 | int index; 246 | int value; 247 | } volume_callback_t; 248 | 249 | 250 | /** 251 | * Internal macro. 252 | * Generates internal device adding/updating functions. 253 | * Generated function documentation follows. 254 | * 255 | * Callback. Fired after getting info about new or updated `type` device. 256 | * 257 | * @param c PA context. It is NOT our backend CONTEXT. 258 | * @param info `type` info. 259 | * @param eol Stop indicator. 260 | * @param userdata Additional data of type CALLBACK. 261 | * 262 | * @see _cb1() 263 | * @see _cb_u() 264 | */ 265 | #define _CB_DEVICE(func, info_type, _CB, type)\ 266 | void func(pa_context *c, const info_type *info, int eol, void *userdata) {\ 267 | if(!eol) {\ 268 | uint32_t n = info->n_ports;\ 269 | backend_option_t *optdata = NULL;\ 270 | if(n > 0) {\ 271 | optdata = (backend_option_t*)malloc(sizeof(backend_option_t));\ 272 | optdata->descriptions = (char**)malloc(n * sizeof(char*));\ 273 | optdata->names = (char**)malloc(n * sizeof(char*));\ 274 | for(uint32_t i = 0; i < n; ++i) {\ 275 | const char *desc = info->ports[i]->description;\ 276 | optdata->descriptions[i] = (char*)malloc((strlen(desc) + 1) * sizeof(char));\ 277 | strcpy(optdata->descriptions[i], desc);\ 278 | const char *name = info->ports[i]->name;\ 279 | optdata->names[i] = (char*)malloc((strlen(name) + 1) * sizeof(char));\ 280 | strcpy(optdata->names[i], name);\ 281 | }\ 282 | const char *active_opt = info->active_port->description;\ 283 | optdata->active = (char*)malloc((strlen(active_opt) + 1) * sizeof(char));\ 284 | strcpy(optdata->active, active_opt);\ 285 | optdata->size = n;\ 286 | }\ 287 | _CB(info, type, optdata, userdata);\ 288 | _do_option_free(optdata, n);\ 289 | }\ 290 | }\ 291 | 292 | /** 293 | * Internal macro. 294 | * Generates internal stream adding/updating functions. 295 | * Generated function documentation follows. 296 | * 297 | * Callback. Fired after getting info about new or updated `type` stream. 298 | * For new streams, it creates necessary structures and fires _cb_client. 299 | * For updated streams, it just calls _cb_u(). 300 | * 301 | * @param c PA context. It is NOT our backend CONTEXT. 302 | * @param info `type` info. 303 | * @param eol Stop indicator. 304 | * @param userdata Additional data of type CALLBACK. 305 | * 306 | * @see _cb_u() 307 | */ 308 | #define MAP_STREAM_SINK_INPUT sink 309 | #define MAP_STREAM_SOURCE_OUTPUT source 310 | #define _CB_STREAM_(c, info, type_, userdata)\ 311 | if(info->index != PA_INVALID_INDEX) {\ 312 | /* TODO: We'll need this name once status line is done. */\ 313 | if(info->client != PA_INVALID_INDEX) {\ 314 | callback_t *callback = (callback_t*)userdata;\ 315 | uint8_t chnum = info->volume.channels;\ 316 | backend_channel_t *channels = _do_channels(info->volume, chnum);\ 317 | backend_volume_t *volumes = _do_volumes(info->volume, chnum, info->mute);\ 318 | client_callback_t *client_cb = (client_callback_t*)malloc(sizeof(client_callback_t));\ 319 | client_cb->callback = callback;\ 320 | client_cb->channels = channels;\ 321 | client_cb->volumes = volumes;\ 322 | client_cb->chnum = chnum;\ 323 | client_cb->index = info->index;\ 324 | client_cb->device = info->MAP_STREAM_ ## type_;\ 325 | client_cb->type = type_;\ 326 | pa_context_get_client_info(c, info->client, _cb_client, client_cb);\ 327 | }\ 328 | } 329 | #define _CB_STREAM_U(c, info, type, userdata) _CB_U(info, type, NULL, userdata); 330 | #define _CB_STREAM(func, info_type, _CB, type)\ 331 | void func(pa_context *c, const info_type *info, int eol, void *userdata) {\ 332 | if(!eol) {\ 333 | _CB(c, info, type, userdata);\ 334 | }\ 335 | } 336 | 337 | /** 338 | * Internal macro. 339 | * Generates internal volume setting functions. 340 | * Generated function documentation follows. 341 | * 342 | * Callback. Fired after getting `type` info for setter. 343 | * Note: It frees userdata. 344 | * 345 | * @param c PA context. It is NOT our backend CONTEXT. 346 | * @param info `type` info. 347 | * @param eol Stop indicator. 348 | * @param userdata Additional data of type VOLUME_CALLBACK. 349 | * 350 | * @see backend_volume_set() 351 | */ 352 | #define _CB_SET_VOLUME(func, info_type, type, by_index)\ 353 | void func(pa_context *c, const info_type *info, int eol, void *userdata) {\ 354 | if(!eol) {\ 355 | volume_callback_t *volume = (volume_callback_t*)userdata;\ 356 | if(info->index != PA_INVALID_INDEX) {\ 357 | pa_cvolume cvolume = info->volume;\ 358 | cvolume.values[volume->index] = volume->value;\ 359 | pa_context_set_ ## type ## _volume ## by_index(c, info->index, &cvolume, NULL, NULL);\ 360 | }\ 361 | free(userdata);\ 362 | }\ 363 | }\ 364 | 365 | /** 366 | * Internal function. 367 | * Callback. Fired when PA server changes state. 368 | * Tests the state change and fires higher level state callback, if needed. 369 | * 370 | * @param c PA context. It is NOT our backend CONTEXT. 371 | * @param userdata Additional data of type STATE_CALLBACK. 372 | */ 373 | void _cb_state_changed(pa_context*, void*); 374 | 375 | /** 376 | * Internal function. 377 | * Callback. Fired after getting client info. 378 | * Checks if everything went smoothly, then fires higher level add callback. 379 | * Also frees client data arrays on our side. 380 | * 381 | * @param c PA context. It is NOT our backend CONTEXT. 382 | * @param info INPUT/OUTPUT client info. 383 | * @param eol Stop indicator. 384 | * @param userdata Additional data of type CLIENT_CALLBACK. 385 | */ 386 | void _cb_client(pa_context*, const pa_client_info*, int, void*); 387 | 388 | /** 389 | * Internal function. 390 | * Callback. Fired after getting info about new CARD. 391 | * 392 | * @param c PA context. It is NOT our backend CONTEXT. 393 | * @param info CARD info. 394 | * @param eol Stop indicator. 395 | * @param userdata Additional data of type CALLBACK. 396 | */ 397 | void _cb_card(pa_context*, const pa_card_info*, int, void*); 398 | 399 | /** 400 | * Internal function. 401 | * Callback. Fired after getting update info about existing CARD. 402 | * 403 | * @param c PA context. It is not our backend CONTEXT. 404 | * @param info CARD info. 405 | * @param eol Stop indicator. 406 | * @param userdata Additional data of type CALLBACK. 407 | */ 408 | void _cb_u_card(pa_context*, const pa_card_info*, int, void*); 409 | 410 | /** 411 | * Internal function. 412 | * Callback. Fired after getting info about PA server settings updates. 413 | * 414 | * @param c PA context. It is not our backend CONTEXT. 415 | * @param info SERVER info. 416 | * @param userdata Additional data of type CALLBACK. 417 | */ 418 | void _cb_server(pa_context*, const pa_server_info*, void *userdata); 419 | 420 | 421 | /** 422 | * Internal function. 423 | * Callback. Fired when PA server emits an event. 424 | * Checks event type and facility, then calls appropriate function: 425 | * [*] Specific PA get function for NEW and CHANGE events 426 | * [*] Higher level remove callback for REMOVE events. 427 | * 428 | * @param c PA context. It is NOT our backend CONTEXT. 429 | * @param t Event type mask. 430 | * @param idx PA internal control index. 431 | * @param userdata Additional data of type CALLBACK. 432 | */ 433 | void _cb_event(pa_context*, pa_subscription_event_type_t, uint32_t, void*); 434 | 435 | 436 | /** 437 | * Helper function. 438 | * Creates BACKEND_CHANNEL used by higher level callbacks. 439 | * 440 | * @param volume PA volume representation array. Not used ATM. 441 | * @param chnum Number of items in @param volume. 442 | * 443 | * @return Array of BACKEND_CHANNEL. 444 | */ 445 | backend_channel_t *_do_channels(pa_cvolume, uint8_t); 446 | 447 | /** 448 | * Helper function. 449 | * Creates BACKEND_VOLUME used by higher level callbacks 450 | * from low level PA representation. 451 | * 452 | * @param volume PA volume representation array. 453 | * @param chnum Number of items in @param volume. 454 | * @param mute Control's current mute value. 455 | * 456 | * @return Array of BACKEND_VOLUME. 457 | */ 458 | backend_volume_t *_do_volumes(pa_cvolume, uint8_t, int); 459 | 460 | /** 461 | * Helper function. 462 | * Creates BACKEND_OPTION used by higher level callbacks 463 | * for low level PA representation. 464 | * 465 | * @param info Card info structure received from PA. 466 | * @param n Number of profiles. 467 | * 468 | * @return Array of BACKEND_OPTION. 469 | */ 470 | backend_option_t *_do_card(const pa_card_info*, int); 471 | 472 | /** 473 | * Frees an array of BACKEND_OPTION. 474 | * 475 | * @param card Array of BACKEND_OPTION. 476 | * @param n Number of profiles. 477 | */ 478 | void _do_option_free(backend_option_t*, int n); 479 | 480 | 481 | /** 482 | * Internal macro. 483 | * Used to propagate info about control's current volume and mute values. 484 | * Makes up necessary information using BACKEND_VOLUME type 485 | * and calls higher level update callback. 486 | * 487 | * @param info PA info structure. 488 | * @param type Type of the control. 489 | * @param optdata Options data. Can be NULL. 490 | * @param userdata Additional data of type CALLBACK. 491 | * 492 | * @see _do_volumes() 493 | */ 494 | #define _CB_U_DEVICE_SINK 0 495 | #define _CB_U_DEVICE_SOURCE 0 496 | #define _CB_U_DEVICE_SINK_INPUT info->sink 497 | #define _CB_U_DEVICE_SOURCE_OUTPUT info->source 498 | #define _CB_U(info, type, optdata, userdata)\ 499 | if(info->index != PA_INVALID_INDEX) {\ 500 | callback_t *callback = (callback_t*)userdata;\ 501 | uint8_t chnum = info->volume.channels;\ 502 | backend_data_t data;\ 503 | data.volumes = _do_volumes(info->volume, chnum, info->mute);\ 504 | data.channels_num = chnum;\ 505 | data.option = optdata;\ 506 | data.device = _CB_U_DEVICE_ ## type;\ 507 | ((tcallback_update_func)(callback->update))(callback->self, type, info->index, &data);\ 508 | free(data.volumes);\ 509 | } 510 | 511 | /** 512 | * Internal macro. 513 | * Used to propagate info about newly appearing SINKs and SOURCEs. 514 | * Makes up necessary information using BACKEND_CHANNEL type 515 | * and calls higher level add callback. 516 | * 517 | * @param info PA info structure. 518 | * @param type Type of the control. 519 | * @param options Options data (BACKEND_OPTION). 520 | * @param userdata Additional data of type CALLBACK. 521 | * 522 | * @see _do_channels() 523 | * @see _do_volumes() 524 | */ 525 | #define _CB1(info, type, optdata, userdata)\ 526 | if(info->index != PA_INVALID_INDEX) {\ 527 | callback_t *callback = (callback_t*)userdata;\ 528 | uint8_t chnum = info->volume.channels;\ 529 | backend_data_t data;\ 530 | data.channels = _do_channels(info->volume, chnum);\ 531 | data.volumes = _do_volumes(info->volume, chnum, info->mute);\ 532 | data.channels_num = chnum;\ 533 | data.option = optdata;\ 534 | data.internalName = (char*)malloc((strlen(info->name) + 1) * sizeof(char));\ 535 | strcpy(data.internalName, info->name);\ 536 | ((tcallback_add_func)(callback->add))(callback->self, info->description, type, info->index, &data);\ 537 | free(data.internalName);\ 538 | free(data.channels);\ 539 | free(data.volumes);\ 540 | } 541 | -------------------------------------------------------------------------------- /src/enums.h: -------------------------------------------------------------------------------- 1 | // pacmixer 2 | // Copyright (C) 2015 Karol 'Kenji Takahashi' Woźniak 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #ifndef __PACMIXER_ENUMS_H__ 24 | #define __PACMIXER_ENUMS_H__ 25 | 26 | 27 | typedef enum { 28 | OUTPUTS = 0, 29 | PLAYBACK = 1, 30 | INPUTS = 2, 31 | RECORDING = 3, 32 | SETTINGS, 33 | ALL, 34 | } View; 35 | 36 | 37 | typedef enum { 38 | MODE_INSIDE, 39 | MODE_OUTSIDE, 40 | MODE_SETTINGS 41 | } Mode; 42 | 43 | 44 | #endif // __PACMIXER_ENUMS_H__ 45 | -------------------------------------------------------------------------------- /src/frontend.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import "enums.h" 27 | #import "log.h" 28 | #import "widgets/menu.h" 29 | #import "widgets/widget.h" 30 | #import "widgets/notice.h" 31 | #import "widgets/options.h" 32 | #import "settings.h" 33 | 34 | 35 | @interface TUI: NSObject { 36 | @private 37 | NSMutableArray *widgets; 38 | Top *top; 39 | Bottom *bottom; 40 | NSMutableArray *xpaddingStates; 41 | NSMutableArray *ypaddingStates; 42 | unsigned int highlight; 43 | Notice *notice; 44 | } 45 | 46 | -(TUI*) init; 47 | -(void) dealloc; 48 | -(void) addWaiter: (NSNotification*) _; 49 | -(void) removeWaiter: (NSNotification*) _; 50 | -(void) reprint; 51 | +(void) refresh; 52 | -(void) clear; 53 | -(BOOL) applySettings: (NSString*) name; 54 | -(Widget*) addWidgetWithName: (NSString*) name 55 | andType: (View) type 56 | andId: (NSString*) id_; 57 | -(void) removeWidget: (NSString*) id_; 58 | -(id) addProfiles: (NSArray*) profiles 59 | withActive: (NSString*) active 60 | andName: (NSString*) name 61 | andId: (NSString*) id_; 62 | -(void) adjustOptions; 63 | -(void) setCurrent: (int) i; 64 | -(void) setFirst; 65 | -(void) setFilter: (View) type; 66 | -(void) setDefaults: (NSNotification*) notification; 67 | -(NSArray*) getWidgetsWithType: (View) type; 68 | -(NSArray*) getWidgetsAttr: (SEL) selector 69 | withType: (View) type; 70 | +(Widget*) getWidgetWithId: (NSString*) id_; 71 | -(void) showSettings; 72 | -(void) switchSetting; 73 | -(void) previous; 74 | -(void) next; 75 | -(void) up: (int64_t) speed; 76 | -(void) down: (int64_t) speed; 77 | -(void) mute; 78 | -(void) setAsDefault; 79 | -(void) inside; 80 | -(void) settings; 81 | -(BOOL) outside; 82 | @end 83 | -------------------------------------------------------------------------------- /src/frontend.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "frontend.h" 19 | 20 | 21 | static WINDOW *pad; 22 | static WINDOW *win; 23 | static PANEL *pan; 24 | static int xpadding; 25 | static int ypadding; 26 | static NSMutableArray *allWidgets; 27 | 28 | 29 | @implementation TUI 30 | -(TUI*) init { 31 | self = [super init]; 32 | allWidgets = [[NSMutableArray alloc] init]; 33 | widgets = [[NSMutableArray alloc] init]; 34 | initscr(); 35 | cbreak(); 36 | noecho(); 37 | curs_set(0); 38 | keypad(stdscr, TRUE); 39 | start_color(); 40 | use_default_colors(); 41 | // default background/foreground (COLOR_PAIR(0) doesn't seem to work) 42 | init_pair(1, -1, -1); 43 | init_pair(2, -1, COLOR_GREEN); // low level volume/not muted 44 | init_pair(3, -1, COLOR_YELLOW); // medium level volume 45 | init_pair(4, -1, COLOR_RED); // high level volume/muted 46 | init_pair(5, COLOR_BLACK, COLOR_MAGENTA); // extreme (>100%) level volume 47 | init_pair(6, COLOR_BLACK, COLOR_BLUE); // outside mode 48 | init_pair(7, COLOR_BLACK, COLOR_WHITE); // inside mode 49 | refresh(); 50 | int my; 51 | int mx; 52 | getmaxyx(stdscr, my, mx); 53 | pad = newpad(my - 4, 1); 54 | win = newwin(my - 4, mx - 2, 2, 1); 55 | pan = new_panel(win); 56 | xpadding = 0; 57 | ypadding = 0; 58 | xpaddingStates = [[NSMutableArray alloc] init]; 59 | ypaddingStates = [[NSMutableArray alloc] init]; 60 | bottom = [[Bottom alloc] init]; 61 | top = [[Top alloc] initWithView: pacmixer::setting("Display.StartView")]; 62 | [[NSNotificationCenter defaultCenter] addObserver: self 63 | selector: @selector(removeWaiter:) 64 | name: @"backendAppeared" 65 | object: nil]; 66 | [[NSNotificationCenter defaultCenter] addObserver: self 67 | selector: @selector(addWaiter:) 68 | name: @"backendGone" 69 | object: nil]; 70 | [self addWaiter: nil]; 71 | return self; 72 | } 73 | 74 | -(void) dealloc { 75 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 76 | del_panel(pan); 77 | delwin(win); 78 | delwin(pad); 79 | endwin(); 80 | [bottom release]; 81 | [top release]; 82 | [widgets release]; 83 | [allWidgets release]; 84 | [ypaddingStates release]; 85 | [xpaddingStates release]; 86 | [super dealloc]; 87 | } 88 | 89 | -(void) addWaiter: (NSNotification*) _ { 90 | [allWidgets removeAllObjects]; 91 | [widgets removeAllObjects]; 92 | wclear(pad); 93 | NSString *message = @"Waiting for connection..."; 94 | notice = [[Notice alloc] initWithMessage: message 95 | andParent: pad]; 96 | [[self class] refresh]; 97 | } 98 | 99 | -(void) removeWaiter: (NSNotification*) _ { 100 | [notice release]; 101 | notice = nil; 102 | wclear(pad); 103 | [[self class] refresh]; 104 | } 105 | 106 | -(void) reprint { 107 | werase(stdscr); 108 | int my; 109 | int mx; 110 | getmaxyx(stdscr, my, mx); 111 | int pmx = getmaxx(pad); 112 | PACMIXER_LOG("F:reprinting TUI at %dx%d", mx, my); 113 | wresize(win, my - 4, mx - 2); 114 | wresize(pad, my - 4, pmx); 115 | for(unsigned int i = 0; i < [widgets count]; ++i) { 116 | [(Widget*)[widgets objectAtIndex: i] reprint: my - 4]; 117 | } 118 | [top reprint]; 119 | [bottom reprint]; 120 | [[self class] refresh]; 121 | } 122 | 123 | +(void) refresh { 124 | int my; 125 | int mx; 126 | getmaxyx(stdscr, my, mx); 127 | int py; 128 | int px; 129 | getmaxyx(pad, py, px); 130 | if(py > my - 4) { 131 | py = my - 4; 132 | } 133 | if(px > mx - 2) { 134 | px = mx - 2; 135 | } 136 | copywin(pad, win, ypadding, xpadding, 0, 0, py - 1, px - 1, 0); 137 | bottom_panel(pan); 138 | update_panels(); 139 | doupdate(); 140 | } 141 | 142 | -(void) clear { 143 | if([widgets count]) { 144 | id widget = [widgets objectAtIndex: highlight]; 145 | [widget setHighlighted: NO]; 146 | } 147 | wclear(pad); 148 | [widgets removeAllObjects]; 149 | } 150 | 151 | -(BOOL) applySettings: (NSString*) name { 152 | bool filter1 = pacmixer::setting("Filter.Internals"); 153 | bool filter2 = pacmixer::setting("Filter.Monitors"); 154 | filter1 = filter1 && [name hasPrefix: @"PulseAudio"]; 155 | filter2 = filter2 && [name hasPrefix: @"Monitor of"]; 156 | return !filter1 && !filter2; 157 | } 158 | 159 | -(Widget*) addWidgetWithName: (NSString*) name 160 | andType: (View) type 161 | andId: (NSString*) id_ { 162 | int x = [[widgets lastObject] endPosition]; 163 | Widget *widget = [[Widget alloc] initWithPosition: x 164 | andName: name 165 | andType: type 166 | andId: id_ 167 | andParent: pad]; 168 | SEL sel = @selector(setValuesByNotification:); 169 | NSString *nname = [NSString stringWithFormat: 170 | @"%@%@", @"controlChanged", id_]; 171 | [[NSNotificationCenter defaultCenter] addObserver: widget 172 | selector: sel 173 | name: nname 174 | object: nil]; 175 | [allWidgets addObject: widget]; 176 | BOOL cond = [top view] == ALL || type == [top view]; 177 | cond = cond && [self applySettings: [widget name]]; 178 | if(cond) { 179 | [widgets addObject: widget]; 180 | if(x == 0) { 181 | [widget setHighlighted: YES]; 182 | } 183 | [widget show]; 184 | } 185 | [widget release]; 186 | return widget; 187 | } 188 | 189 | -(void) removeWidget: (NSString*) id_ { 190 | NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; 191 | wclear(pad); 192 | for(unsigned int i = 0; i < [widgets count]; ++i) { 193 | id widget = [widgets objectAtIndex: i]; 194 | if([[widget internalId] isEqualToString: id_]) { 195 | PACMIXER_LOG("F:%s removed at index %d", [id_ UTF8String], i); 196 | [indexes addIndex: i]; 197 | if(highlight >= i) { 198 | if(highlight > 0) { 199 | highlight -= 1; 200 | } else if(highlight == 0 && (int)[widgets count] - 1 > 1) { 201 | highlight += 1; 202 | } 203 | } 204 | } 205 | } 206 | [widgets removeObjectsAtIndexes: indexes]; 207 | [indexes removeAllIndexes]; 208 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 209 | Widget *widget = [allWidgets objectAtIndex: i]; 210 | if([[widget internalId] isEqualToString: id_]) { 211 | [indexes addIndex: i]; 212 | PACMIXER_LOG("F:%s removed at index %d", [id_ UTF8String], i); 213 | } 214 | } 215 | [allWidgets removeObjectsAtIndexes: indexes]; 216 | int x = 0; 217 | for(unsigned int i = 0; i < [widgets count]; ++i) { 218 | Widget *w = [widgets objectAtIndex: i]; 219 | [w setPosition: x]; 220 | [w show]; 221 | x = [w endPosition]; 222 | } 223 | if([widgets count]) { 224 | [[widgets objectAtIndex: highlight] setHighlighted: YES]; 225 | } 226 | } 227 | 228 | -(id) addProfiles: (NSArray*) profiles 229 | withActive: (NSString*) active 230 | andName: (NSString*) name 231 | andId: (NSString*) id_ { 232 | int ypos = 0; 233 | if([top view] == SETTINGS) { 234 | ypos = [[widgets lastObject] endPosition]; 235 | } 236 | Options *widget = [[Options alloc] initWithPosition: ypos 237 | andName: name 238 | andValues: profiles 239 | andId: id_ 240 | andParent: pad]; 241 | [widget setCurrentByName: active]; 242 | SEL sel = @selector(setCurrentByNotification:); 243 | NSString *nname = [NSString stringWithFormat: 244 | @"%@%@", @"cardProfileChanged", id_]; 245 | [[NSNotificationCenter defaultCenter] addObserver: widget 246 | selector: sel 247 | name: nname 248 | object: nil]; 249 | [allWidgets addObject: widget]; 250 | if([top view] == SETTINGS) { 251 | [widgets addObject: widget]; 252 | [widget show]; 253 | } 254 | [widget release]; 255 | 256 | return widget; 257 | } 258 | 259 | -(void) adjustOptions { 260 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 261 | Widget *widget = [allWidgets objectAtIndex: i]; 262 | View wtype = [widget type]; 263 | if(wtype == OUTPUTS || wtype == INPUTS) { 264 | View option_type = wtype == OUTPUTS ? PLAYBACK : RECORDING; 265 | NSArray *tc_widgets = [self getWidgetsWithType: option_type]; 266 | NSArray *values = [self getWidgetsAttr: @selector(name) 267 | withType: wtype]; 268 | NSArray *mapvalues = [self getWidgetsAttr: @selector(internalName) 269 | withType: wtype]; 270 | for(unsigned int j = 0; j < [tc_widgets count]; ++j) { 271 | Widget *tc_widget = [tc_widgets objectAtIndex: j]; 272 | [tc_widget replaceOptions: values]; 273 | [[tc_widget options] replaceMapping: mapvalues]; 274 | } 275 | } 276 | } 277 | } 278 | 279 | -(void) setCurrent: (int) i { 280 | Widget *owidget = [widgets objectAtIndex: highlight]; 281 | highlight = i; 282 | Widget *nwidget = [widgets objectAtIndex: highlight]; 283 | [owidget setHighlighted: NO]; 284 | [nwidget setHighlighted: YES]; 285 | if([bottom mode] == MODE_SETTINGS) { 286 | [owidget outside]; 287 | [nwidget settings]; 288 | } 289 | } 290 | 291 | -(void) setFirst { 292 | highlight = 0; 293 | if([widgets count]) { 294 | [[widgets objectAtIndex: highlight] setHighlighted: YES]; 295 | } 296 | [[self class] refresh]; 297 | } 298 | 299 | -(void) setFilter: (View) type { 300 | if([bottom mode] == MODE_SETTINGS) { 301 | [(Widget*)[widgets objectAtIndex: highlight] outside]; 302 | [bottom outside]; 303 | } 304 | [top setView: type]; 305 | [bottom setView: ALL]; 306 | [self clear]; 307 | int x = 0; 308 | if(notice != nil) { 309 | [notice print]; 310 | [[self class] refresh]; 311 | } else { 312 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 313 | Widget *w = [allWidgets objectAtIndex: i]; 314 | BOOL cond = [top view] == ALL && [w type] != SETTINGS; 315 | cond = cond || [w type] == [top view]; 316 | cond = cond && [self applySettings: [w name]]; 317 | if(cond) { 318 | [w setPosition: x]; 319 | [w show]; 320 | [widgets addObject: w]; 321 | x = [w endPosition]; 322 | } else { 323 | [w hide]; 324 | } 325 | } 326 | [self setFirst]; 327 | } 328 | } 329 | 330 | -(void) setDefaults: (NSNotification*) notification { 331 | NSDictionary *userInfo = [notification userInfo]; 332 | NSDictionary *defaults = [userInfo objectForKey: @"defaults"]; 333 | NSString *default_sink = [defaults objectForKey: @"sink"]; 334 | NSString *default_source = [defaults objectForKey: @"source"]; 335 | 336 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 337 | Widget *w = [allWidgets objectAtIndex: i]; 338 | if([w type] == INPUTS) { 339 | [w setDefault: [default_source isEqualToString: w.internalName]]; 340 | } else if([w type] == OUTPUTS) { 341 | [w setDefault: [default_sink isEqualToString: w.internalName]]; 342 | } 343 | } 344 | } 345 | 346 | -(NSArray*) getWidgetsWithType: (View) type { 347 | NSMutableArray *results = [NSMutableArray arrayWithCapacity: 0]; 348 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 349 | Widget *widget = [allWidgets objectAtIndex: i]; 350 | if([widget type] == type) { 351 | [results addObject: widget]; 352 | } 353 | } 354 | return results; 355 | } 356 | 357 | -(NSArray*) getWidgetsAttr: (SEL) selector 358 | withType: (View) type { 359 | NSMutableArray *results = [NSMutableArray arrayWithCapacity: 0]; 360 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 361 | Widget *widget = [allWidgets objectAtIndex: i]; 362 | if([widget type] == type) { 363 | [results addObject: [widget performSelector: selector]]; 364 | } 365 | } 366 | return results; 367 | } 368 | 369 | +(Widget*) getWidgetWithId: (NSString*) id_ { 370 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 371 | Widget *widget = [allWidgets objectAtIndex: i]; 372 | if([[widget internalId] isEqualToString: id_]) { 373 | return widget; 374 | } 375 | } 376 | return nil; 377 | } 378 | 379 | -(void) showSettings { 380 | if([bottom mode] == MODE_SETTINGS) { 381 | [(Widget*)[widgets objectAtIndex: highlight] outside]; 382 | [bottom outside]; 383 | } 384 | [top setView: SETTINGS]; 385 | [bottom setView: SETTINGS]; 386 | [self clear]; 387 | int y = 0; 388 | for(unsigned int i = 0; i < [allWidgets count]; ++i) { 389 | Widget *w = [allWidgets objectAtIndex: i]; 390 | if([w type] == SETTINGS) { 391 | [w setPosition: y]; 392 | [w show]; 393 | [widgets addObject: w]; 394 | y = [w endPosition]; 395 | } else { 396 | [w hide]; 397 | } 398 | } 399 | [self setFirst]; 400 | } 401 | 402 | -(void) switchSetting { 403 | id widget = [widgets objectAtIndex: highlight]; 404 | if([top view] == SETTINGS || [bottom mode] == MODE_SETTINGS) { 405 | [widget switchValue]; 406 | } 407 | [[self class] refresh]; 408 | } 409 | 410 | -(void) previous { 411 | if([bottom mode] == MODE_INSIDE) { 412 | [(id)[widgets objectAtIndex: highlight] previous]; 413 | } else if(highlight > 0) { 414 | if([bottom mode] == MODE_SETTINGS) { 415 | BOOL flag = NO; 416 | for(int i = highlight - 1; i >= 0; --i) { 417 | Widget *widget = [widgets objectAtIndex: i]; 418 | if([widget canGoSettings]) { 419 | [self setCurrent: i]; 420 | flag = YES; 421 | break; 422 | } 423 | } 424 | if(!flag) { 425 | return; 426 | } 427 | } else { 428 | [self setCurrent: highlight - 1]; 429 | } 430 | id w = [widgets objectAtIndex: highlight]; 431 | BOOL cond = [w respondsToSelector: @selector(endHPosition)]; 432 | cond = cond && [w endHPosition] - [w width] <= xpadding; 433 | if(cond) { 434 | int count = [xpaddingStates count] - 1; 435 | int delta = [[xpaddingStates objectAtIndex: count] intValue]; 436 | [xpaddingStates removeObjectAtIndex: count]; 437 | xpadding -= delta; 438 | } 439 | cond = [w respondsToSelector: @selector(endVPosition)]; 440 | cond = cond && [w endVPosition] - [w height] < ypadding; 441 | if(cond) { 442 | int count = [ypaddingStates count] - 1; 443 | int delta = [[ypaddingStates objectAtIndex: count] intValue]; 444 | [ypaddingStates removeObjectAtIndex: count]; 445 | ypadding -= delta; 446 | } 447 | } 448 | [[self class] refresh]; 449 | } 450 | 451 | -(void) next { 452 | if([bottom mode] == MODE_INSIDE) { 453 | [(id)[widgets objectAtIndex: highlight] next]; 454 | } else if(highlight < [widgets count] - 1) { 455 | int start = [[widgets objectAtIndex: highlight] endPosition]; 456 | if([bottom mode] == MODE_SETTINGS) { 457 | BOOL flag = NO; 458 | for(int i = highlight + 1; i < (int)[widgets count]; ++i) { 459 | Widget *widget = [widgets objectAtIndex: i]; 460 | if([widget canGoSettings]) { 461 | [self setCurrent: i]; 462 | flag = YES; 463 | break; 464 | } 465 | } 466 | if(!flag) { 467 | return; 468 | } 469 | start = [[widgets objectAtIndex: highlight - 1] endPosition]; 470 | } else { 471 | [self setCurrent: highlight + 1]; 472 | } 473 | id w = [widgets objectAtIndex: highlight]; 474 | int my; 475 | int mx; 476 | getmaxyx(stdscr, my, mx); 477 | BOOL cond = [w respondsToSelector: @selector(endHPosition)]; 478 | cond = cond && [w endHPosition] - xpadding >= mx; 479 | if(cond) { 480 | int delta = [w width] - (mx - start - 3 + xpadding); 481 | xpadding += delta; 482 | [xpaddingStates addObject: [NSNumber numberWithInt: delta]]; 483 | } 484 | cond = [w respondsToSelector: @selector(endVPosition)]; 485 | cond = cond && [w endVPosition] - ypadding >= my - 1; 486 | if(cond) { 487 | int delta = [w height] - (my - start - 3 + ypadding); 488 | ypadding += delta; 489 | [ypaddingStates addObject: [NSNumber numberWithInt: delta]]; 490 | } 491 | } 492 | [[self class] refresh]; 493 | } 494 | 495 | -(void) up: (int64_t) speed { 496 | if([widgets count]) { 497 | id widget = [widgets objectAtIndex: highlight]; 498 | if([widget respondsToSelector: @selector(up:)]) { 499 | [widget up: speed]; 500 | } else { 501 | [widget up]; 502 | } 503 | [[self class] refresh]; 504 | } 505 | } 506 | 507 | -(void) down: (int64_t) speed { 508 | if([widgets count]) { 509 | id widget = [widgets objectAtIndex: highlight]; 510 | if([widget respondsToSelector: @selector(down:)]) { 511 | [widget down: speed]; 512 | } else { 513 | [widget down]; 514 | } 515 | [[self class] refresh]; 516 | } 517 | } 518 | 519 | -(void) mute { 520 | if([widgets count]) { 521 | [(id)[widgets objectAtIndex: highlight] mute]; 522 | [[self class] refresh]; 523 | } 524 | } 525 | 526 | -(void) setAsDefault { 527 | if([widgets count]) { 528 | [[widgets objectAtIndex: highlight] switchDefault]; 529 | [[self class] refresh]; 530 | } 531 | } 532 | 533 | -(void) inside { 534 | if([top view] != SETTINGS && [widgets count]) { 535 | Widget *widget = [widgets objectAtIndex: highlight]; 536 | if([widget canGoInside]) { 537 | [bottom inside]; 538 | [widget inside]; 539 | } 540 | [[self class] refresh]; 541 | } 542 | } 543 | 544 | -(void) settings { 545 | if([top view] != SETTINGS && [widgets count]) { 546 | Widget *widget = [widgets objectAtIndex: highlight]; 547 | if([widget canGoSettings]) { 548 | [bottom settings]; 549 | [widget settings]; 550 | } 551 | [[self class] refresh]; 552 | } 553 | } 554 | 555 | -(BOOL) outside { 556 | BOOL outside = [bottom outside]; 557 | if(!outside) { 558 | Widget *widget = [widgets objectAtIndex: highlight]; 559 | [widget outside]; 560 | [[self class] refresh]; 561 | } 562 | return outside; 563 | } 564 | @end 565 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | // pacmixer 2 | // Copyright (C) 2012, 2015 Karol 'Kenji Takahashi' Woźniak 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #include "log.h" 24 | 25 | 26 | struct Logger pacmixer_logger = { NULL }; 27 | 28 | void pacmixer_log_set_path(const char *path) { 29 | if(path == NULL || strcmp(path, "") == 0) { 30 | return; 31 | } 32 | char *home = path[0] != '/' ? getenv("HOME") : ""; 33 | char *dir = malloc((strlen(home) + strlen(path) + 11) * sizeof(char)); 34 | sprintf(dir, "%s/%s/pacmixer", home, path); 35 | pacmixer_logger.path = malloc((strlen(dir) + 15) * sizeof(char)); 36 | sprintf(pacmixer_logger.path, "%s/pacmixer.log", dir); 37 | mkdirp(dir); 38 | free(dir); 39 | } 40 | 41 | void pacmixer_log_free() { 42 | if(pacmixer_logger.path != NULL) { 43 | free(pacmixer_logger.path); 44 | } 45 | } 46 | 47 | void pacmixer_log_push(const char* file, int line, const char* fmt, ...) { 48 | if(pacmixer_logger.path == NULL) { 49 | return; 50 | } 51 | time_t t = time(NULL); 52 | struct tm* utc = gmtime(&t); 53 | char stamp[20]; 54 | strftime(stamp, 20, "%F %T", utc); 55 | va_list args; 56 | va_start(args, fmt); 57 | FILE *f = fopen(pacmixer_logger.path, "a"); 58 | fprintf(f, "[%s](%s:%d):", stamp, file, line); 59 | vfprintf(f, fmt, args); 60 | fprintf(f, "\n"); 61 | fclose(f); 62 | va_end(args); 63 | } 64 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | // pacmixer 2 | // Copyright (C) 2012 - 2013, 2015 Karol 'Kenji Takahashi' Woźniak 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #ifndef __PACMIXER_LOG_H__ 24 | #define __PACMIXER_LOG_H__ 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "../vendor/mkdirp.h" 37 | 38 | 39 | struct Logger { 40 | char *path; 41 | }; 42 | 43 | extern struct Logger pacmixer_logger; 44 | 45 | void pacmixer_log_set_path(const char *path); 46 | void pacmixer_log_free(); 47 | void pacmixer_log_push(const char* file, int line, const char* fmt, ...); 48 | #define PACMIXER_LOG(fmt, ...) pacmixer_log_push(__FILE__, __LINE__, fmt, ##__VA_ARGS__) 49 | 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | #endif // __PACMIXER_LOG_H__ 55 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import "frontend.h" 23 | #import "log.h" 24 | #import "middleware.h" 25 | 26 | 27 | @interface Dispatcher: NSObject { 28 | @private 29 | Middleware *middleware; 30 | TUI *tui; 31 | NSAutoreleasePool *pool; 32 | } 33 | 34 | -(Dispatcher*) init; 35 | -(void) dealloc; 36 | -(void) addWidget: (NSNotification*) notification; 37 | -(void) removeWidget: (NSNotification*) notification; 38 | -(void) run; 39 | @end 40 | 41 | 42 | int main(int argc, char const *argv[]); 43 | -------------------------------------------------------------------------------- /src/main.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "main.h" 19 | 20 | 21 | @implementation Dispatcher 22 | -(Dispatcher*) init { 23 | self = [super init]; 24 | pool = [[NSAutoreleasePool alloc] init]; 25 | tui = [[TUI alloc] init]; 26 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 27 | [center addObserver: self 28 | selector: @selector(addWidget:) 29 | name: @"controlAppeared" 30 | object: nil]; 31 | [center addObserver: self 32 | selector: @selector(removeWidget:) 33 | name: @"controlDisappeared" 34 | object: nil]; 35 | [center addObserver: self 36 | selector: @selector(addWidget:) 37 | name: @"cardAppeared" 38 | object: nil]; 39 | [center addObserver: tui 40 | selector: @selector(setDefaults:) 41 | name: @"serverDefaultsAppeared" 42 | object: nil]; 43 | middleware = [[Middleware alloc] init]; 44 | return self; 45 | } 46 | 47 | -(void) dealloc { 48 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 49 | [tui release]; 50 | [middleware release]; 51 | [pool drain]; 52 | [super dealloc]; 53 | } 54 | 55 | -(void) addWidget: (NSNotification*) notification { 56 | NSDictionary *info = [notification userInfo]; 57 | NSNumber *id_ = [info objectForKey: @"id"]; 58 | backend_entry_type typeb = (backend_entry_type)[[info objectForKey: @"type"] intValue]; 59 | View type; 60 | switch(typeb) { 61 | case SINK: 62 | type = OUTPUTS; 63 | break; 64 | case SINK_INPUT: 65 | type = PLAYBACK; 66 | break; 67 | case SOURCE: 68 | type = INPUTS; 69 | break; 70 | case SOURCE_OUTPUT: 71 | type = RECORDING; 72 | break; 73 | case CARD: 74 | type = SETTINGS; 75 | break; 76 | default: 77 | type = ALL; 78 | break; 79 | } 80 | NSString *name = [info objectForKey: @"name"]; 81 | NSString *internalId = [NSString stringWithFormat: 82 | @"%@_%d", id_, typeb]; 83 | PACMIXER_LOG("D:%d:%s passed", [id_ intValue], [name UTF8String]); 84 | NSArray *channels = [info objectForKey: @"channels"]; 85 | NSArray *volumes = [info objectForKey: @"volumes"]; 86 | if(channels != nil && volumes != nil) { 87 | Widget *w = [tui addWidgetWithName: name 88 | andType: type 89 | andId: internalId]; 90 | w.internalName = [info objectForKey: @"internalName"]; 91 | Channels *channelsWidgets = [w addChannels: channels]; 92 | for(unsigned int i = 0; i < [channels count]; ++i) { 93 | volume_t *volume = [volumes objectAtIndex: i]; 94 | [channelsWidgets setLevel: [[volume level] intValue] 95 | forChannel: i]; 96 | [channelsWidgets setMute: [volume mute] 97 | forChannel: i]; 98 | } 99 | NSArray *port_names = [info objectForKey: @"portNames"]; 100 | NSArray *port_descs = [info objectForKey: @"portDescriptions"]; 101 | NSString *active_port = [info objectForKey: @"activePort"]; 102 | if(port_names != nil && port_descs != nil && active_port != nil) { 103 | id opt = [w addOptions: port_descs withName: @"Ports"]; 104 | [opt replaceMapping: port_names]; 105 | [opt setCurrentByName: active_port]; 106 | } else if(type == PLAYBACK || type == RECORDING) { 107 | View option_type = type == PLAYBACK ? OUTPUTS : INPUTS; 108 | NSArray *options = [tui getWidgetsAttr: @selector(name) 109 | withType: option_type]; 110 | id opt = [w addOptions: options 111 | withName: type == PLAYBACK ? @"Output" : @"Input"]; 112 | NSString *current_id = [NSString stringWithFormat: 113 | @"%@_%d", [info objectForKey: @"deviceIndex"], option_type]; 114 | Widget *current_widget = [TUI getWidgetWithId: current_id]; 115 | [opt setCurrentByName: [current_widget name]]; 116 | } 117 | } else { 118 | NSArray *profile_names = [info objectForKey: @"profileNames"]; 119 | NSArray *profile_descs = [info objectForKey: @"profileDescriptions"]; 120 | NSString *active_profile = [info objectForKey: @"activeProfile"]; 121 | if(profile_names != nil && profile_descs != nil && active_profile != nil) { 122 | id opt = [tui addProfiles: profile_descs 123 | withActive: active_profile 124 | andName: name 125 | andId: internalId]; 126 | [opt replaceMapping: profile_names]; 127 | } 128 | } 129 | [tui adjustOptions]; 130 | [TUI refresh]; 131 | } 132 | 133 | -(void) removeWidget: (NSNotification*) notification { 134 | NSDictionary *info = [notification userInfo]; 135 | [tui removeWidget: [info objectForKey: @"id"]]; 136 | [tui adjustOptions]; 137 | [TUI refresh]; 138 | } 139 | 140 | -(void) run { 141 | int ch; 142 | BOOL quit = NO; 143 | while(!quit) { 144 | ch = getch(); 145 | switch(ch) { 146 | case 27: 147 | case 'q': 148 | quit = [tui outside]; 149 | break; 150 | case 'h': 151 | case KEY_LEFT: 152 | [tui previous]; 153 | break; 154 | case 'l': 155 | case KEY_RIGHT: 156 | [tui next]; 157 | break; 158 | case 'k': 159 | case KEY_UP: 160 | [tui up: pacmixer::setting("Control.UpSpeed")]; 161 | break; 162 | case 'K': 163 | case KEY_SR: 164 | [tui up: pacmixer::setting("Control.FastUpSpeed")]; 165 | break; 166 | case 'j': 167 | case KEY_DOWN: 168 | [tui down: pacmixer::setting("Control.DownSpeed")]; 169 | break; 170 | case 'J': 171 | case KEY_SF: 172 | [tui down: pacmixer::setting("Control.FastDownSpeed")]; 173 | break; 174 | case 'm': 175 | [tui mute]; 176 | break; 177 | case 'i': 178 | [tui inside]; 179 | break; 180 | case 's': 181 | [tui settings]; 182 | break; 183 | case 'd': 184 | [tui setAsDefault]; 185 | break; 186 | case KEY_F(1): 187 | case '1': 188 | [tui setFilter: ALL]; 189 | break; 190 | case KEY_F(2): 191 | case '2': 192 | [tui setFilter: PLAYBACK]; 193 | break; 194 | case KEY_F(3): 195 | case '3': 196 | [tui setFilter: RECORDING]; 197 | break; 198 | case KEY_F(4): 199 | case '4': 200 | [tui setFilter: OUTPUTS]; 201 | break; 202 | case KEY_F(5): 203 | case '5': 204 | [tui setFilter: INPUTS]; 205 | break; 206 | case KEY_F(12): 207 | case '0': 208 | [tui showSettings]; 209 | break; 210 | case ' ': 211 | [tui switchSetting]; 212 | break; 213 | case KEY_RESIZE: 214 | [tui reprint]; 215 | break; 216 | } 217 | } 218 | } 219 | @end 220 | 221 | 222 | int main(int argc, char const *argv[]) { 223 | pacmixer_log_set_path(pacmixer::setting("Log.Dir").c_str()); 224 | 225 | Dispatcher *dispatcher = [[Dispatcher alloc] init]; 226 | [dispatcher run]; 227 | [dispatcher release]; 228 | 229 | pacmixer_log_free(); 230 | return 0; 231 | } 232 | -------------------------------------------------------------------------------- /src/middleware.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #if GNUSTEP_BASE_MINOR_VERSION < 24 26 | #import 27 | #endif 28 | #import "log.h" 29 | #import "types.h" 30 | #import "backend.h" 31 | 32 | 33 | void callback_add_func(void*, const char*, backend_entry_type, uint32_t, backend_data_t*); 34 | void callback_update_func(void*, backend_entry_type, uint32_t, const backend_data_t*); 35 | void callback_remove_func(void*, uint32_t, backend_entry_type); 36 | void callback_state_func(void*, server_state); 37 | 38 | @interface Block: NSObject { 39 | @private 40 | context_t *context; 41 | uint32_t idx; 42 | int i; 43 | backend_entry_type type; 44 | NSMutableDictionary *data; 45 | } 46 | 47 | -(Block*) initWithContext: (context_t*) context_ 48 | andId: (uint32_t) idx_ 49 | andIndex: (int) i_ 50 | andType: (backend_entry_type) type_; 51 | -(void) dealloc; 52 | -(void) addDataByCArray: (int) n 53 | withValues: (char**const) values 54 | andKeys: (char**const) keys; 55 | -(void) setVolume: (NSNotification*) notification; 56 | -(void) setVolumes: (NSNotification*) notification; 57 | -(void) setMute: (NSNotification*) notification; 58 | -(void) setCardActiveProfile: (NSNotification*) notification; 59 | -(void) setActivePort: (NSNotification*) notification; 60 | -(void) setActiveDevice: (NSNotification*) notification; 61 | -(void) setDefaults: (NSNotification*) notification; 62 | @end 63 | 64 | @interface Middleware: NSObject { 65 | @private 66 | context_t *context; 67 | callback_t *callback; 68 | NSMutableArray *blocks; 69 | } 70 | 71 | -(Middleware*) init; 72 | -(void) restart: (NSNotification*) _; 73 | -(void) dealloc; 74 | -(Block*) addBlockWithId: (uint32_t) idx 75 | andIndex: (int) i 76 | andType: (backend_entry_type) type; 77 | @end 78 | -------------------------------------------------------------------------------- /src/middleware.m: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "middleware.h" 19 | 20 | 21 | #define CAPITALIZE_port @"Port" 22 | #define CAPITALIZE_profile @"Profile" 23 | #define _CALLBACK_DO_OPTION(__option__)\ 24 | char ** const names = data->option->names;\ 25 | char ** const descs = data->option->descriptions;\ 26 | const char *active = data->option->active;\ 27 | uint8_t pnum = data->option->size;\ 28 | NSMutableArray *pd = [NSMutableArray arrayWithCapacity: pnum];\ 29 | NSMutableArray *pn = [NSMutableArray arrayWithCapacity: pnum];\ 30 | for(int i = 0; i < pnum; ++i) {\ 31 | [pn addObject: [NSString stringWithUTF8String: names[i]]];\ 32 | [pd addObject: [NSString stringWithUTF8String: descs[i]]];\ 33 | }\ 34 | NSString *a = [NSString stringWithUTF8String: active];\ 35 | [s setObject: pn forKey: @#__option__"Names"];\ 36 | [s setObject: pd forKey: @#__option__"Descriptions"];\ 37 | [s setObject: a forKey: @"active"CAPITALIZE_ ## __option__]; 38 | 39 | void callback_add_func( 40 | void *self_, const char *name, backend_entry_type type, 41 | uint32_t idx, backend_data_t *data 42 | ) { 43 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 44 | Middleware *self = self_; 45 | [self retain]; 46 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 47 | NSString *sname = nil; 48 | SEL ssel; 49 | //This block is used to set control-wise values, so index is 50 | //not important. This is *channel's* number, nothing to do with idx. 51 | Block *block = [self addBlockWithId: idx 52 | andIndex: -1 53 | andType: type]; 54 | if(type == CARD && data->option != NULL) { 55 | sname = [NSString stringWithFormat: 56 | @"activeOptionChanged%d_%d", idx, type]; 57 | ssel = @selector(setCardActiveProfile:); 58 | 59 | NSMutableDictionary *s = [NSMutableDictionary dictionaryWithObjectsAndKeys: 60 | [NSString stringWithUTF8String: name], @"name", 61 | [NSNumber numberWithInt: idx], @"id", 62 | [NSNumber numberWithInt: type], @"type", nil]; 63 | _CALLBACK_DO_OPTION(profile); 64 | 65 | [center postNotificationName: @"cardAppeared" 66 | object: self 67 | userInfo: s]; 68 | } else if(data->channels != NULL && data->volumes != NULL) { 69 | uint8_t chnum = data->channels_num; 70 | NSMutableArray *ch = [NSMutableArray arrayWithCapacity: chnum]; 71 | NSMutableArray *chv = [NSMutableArray arrayWithCapacity: chnum]; 72 | for(uint8_t i = 0; i < chnum; ++i) { 73 | const backend_channel_t chn = data->channels[i]; 74 | const backend_volume_t vol = data->volumes[i]; 75 | [ch addObject: [[channel_t alloc] initWithMaxLevel: chn.maxLevel 76 | andNormLevel: chn.normLevel 77 | andMutable: chn.isMutable]]; 78 | [chv addObject: [[volume_t alloc] initWithLevel: vol.level 79 | andMute: vol.mute]]; 80 | Block *block = [self addBlockWithId: idx 81 | andIndex: i 82 | andType: type]; 83 | NSString *siname = [NSString stringWithFormat: 84 | @"volumeChanged%d_%d_%d", idx, type, i]; 85 | [center addObserver: block 86 | selector: @selector(setVolume:) 87 | name: siname 88 | object: nil]; 89 | PACMIXER_LOG("M:%s observer added", [siname UTF8String]); 90 | } 91 | sname = [NSString stringWithFormat: 92 | @"volumeChanged%d_%d", idx, type]; 93 | ssel = @selector(setVolumes:); 94 | NSString *mname = [NSString stringWithFormat: 95 | @"muteChanged%d_%d", idx, type]; 96 | [center addObserver: block 97 | selector: @selector(setMute:) 98 | name: mname 99 | object: nil]; 100 | PACMIXER_LOG("M:%s observer added", [mname UTF8String]); 101 | PACMIXER_LOG("M:%d:%s:%s received", idx, name, data->internalName); 102 | NSMutableDictionary *s = [ 103 | NSMutableDictionary dictionaryWithObjectsAndKeys: 104 | [NSString stringWithUTF8String: name], @"name", 105 | [NSNumber numberWithInt: idx], @"id", 106 | ch, @"channels", chv, @"volumes", 107 | [NSNumber numberWithInt: type], @"type", 108 | [NSString stringWithUTF8String: data->internalName], 109 | @"internalName", 110 | nil]; 111 | if(type == SINK_INPUT || type == SOURCE_OUTPUT) { 112 | NSNumber *device = [NSNumber numberWithInt: data->device]; 113 | [s setObject: device forKey: @"deviceIndex"]; 114 | NSString *psname = [NSString stringWithFormat: 115 | @"activeOptionChanged%d_%d", idx, type]; 116 | [center addObserver: block 117 | selector: @selector(setActiveDevice:) 118 | name: psname 119 | object: nil]; 120 | } 121 | if(data->option != NULL) { 122 | _CALLBACK_DO_OPTION(port); 123 | 124 | NSString *psname = [NSString stringWithFormat: 125 | @"activeOptionChanged%d_%d", idx, type]; 126 | [center addObserver: block 127 | selector: @selector(setActivePort:) 128 | name: psname 129 | object: nil]; 130 | } 131 | [center postNotificationName: @"controlAppeared" 132 | object: self 133 | userInfo: s]; 134 | } 135 | if(sname != nil) { 136 | [center addObserver: block 137 | selector: ssel 138 | name: sname 139 | object: nil]; 140 | } 141 | PACMIXER_LOG("M:%s observer added", [sname UTF8String]); 142 | [pool release]; 143 | } 144 | 145 | void callback_update_func( 146 | void *self_, backend_entry_type type, 147 | uint32_t idx, const backend_data_t *data 148 | ) { 149 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 150 | Middleware *self = self_; 151 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 152 | if(type == SERVER && data->defaults != NULL) { 153 | NSString *default_sink = [NSString stringWithUTF8String: 154 | data->defaults->sink]; 155 | NSString *default_source = [NSString stringWithUTF8String: 156 | data->defaults->source]; 157 | 158 | NSDictionary *p = [NSDictionary dictionaryWithObjectsAndKeys: 159 | default_sink, @"sink", default_source, @"source", nil]; 160 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 161 | p, @"defaults", nil]; 162 | NSString *nname = @"serverDefaultsAppeared"; 163 | 164 | [center postNotificationName: nname 165 | object: self 166 | userInfo: s]; 167 | } else if(type == CARD && data->option != NULL) { 168 | NSMutableDictionary *s = [NSMutableDictionary dictionary]; 169 | _CALLBACK_DO_OPTION(profile); 170 | 171 | NSString *nname = [NSString stringWithFormat: 172 | @"cardProfileChanged%d_%d", idx, type]; 173 | [center postNotificationName: nname 174 | object: self 175 | userInfo: s]; 176 | PACMIXER_LOG("M:%s notification posted", [nname UTF8String]); 177 | } else if(data->volumes != NULL) { 178 | uint8_t chnum = data->channels_num; 179 | NSMutableArray *volumes = [[NSMutableArray alloc] init]; 180 | for(int i = 0; i < chnum; ++i) { 181 | const backend_volume_t vol = data->volumes[i]; 182 | volume_t *v = [[volume_t alloc] initWithLevel: vol.level 183 | andMute: vol.mute]; 184 | [volumes addObject: v]; 185 | [v release]; 186 | } 187 | NSMutableDictionary *s = [ 188 | NSMutableDictionary dictionaryWithObjectsAndKeys: 189 | volumes, @"volumes", nil]; 190 | [volumes release]; 191 | if(type == SINK_INPUT || type == SOURCE_OUTPUT) { 192 | NSNumber *device = [NSNumber numberWithInt: data->device]; 193 | [s setObject: device forKey: @"deviceIndex"]; 194 | } 195 | if(data->option != NULL) { 196 | _CALLBACK_DO_OPTION(port); 197 | } 198 | NSString *nname = [NSString stringWithFormat: 199 | @"controlChanged%d_%d", idx, type]; 200 | [center postNotificationName: nname 201 | object: self 202 | userInfo: s]; 203 | PACMIXER_LOG("M:%s notification posted", [nname UTF8String]); 204 | } 205 | [pool release]; 206 | } 207 | 208 | void callback_remove_func(void *self_, uint32_t idx, backend_entry_type type) { 209 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 210 | Middleware *self = self_; 211 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 212 | [NSString stringWithFormat: @"%d_%d", idx, type], @"id", nil]; 213 | NSString *nname = @"controlDisappeared"; 214 | [[NSNotificationCenter defaultCenter] postNotificationName: nname 215 | object: self 216 | userInfo: s]; 217 | PACMIXER_LOG("M:%d:%s notification posted", idx, [nname UTF8String]); 218 | [pool release]; 219 | } 220 | 221 | void callback_state_func(void *self_, server_state state) { 222 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 223 | #if GNUSTEP_BASE_MINOR_VERSION < 24 224 | [NSThread _createThreadForCurrentPthread]; 225 | #endif 226 | Middleware *self = self_; 227 | NSString *name = state == S_CAME ? @"backendAppeared" : @"backendGone"; 228 | [[NSNotificationCenter defaultCenter] postNotificationName: name 229 | object: self 230 | userInfo: nil]; 231 | [pool release]; 232 | } 233 | 234 | @implementation Block 235 | -(Block*) initWithContext: (context_t*) context_ 236 | andId: (uint32_t) idx_ 237 | andIndex: (int) i_ 238 | andType: (backend_entry_type) type_ { 239 | self = [super init]; 240 | context = context_; 241 | idx = idx_; 242 | i = i_; 243 | type = type_; 244 | data = [[NSMutableDictionary alloc] init]; 245 | return self; 246 | } 247 | 248 | -(void) dealloc { 249 | [data release]; 250 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 251 | [super dealloc]; 252 | } 253 | 254 | -(void) addDataByCArray: (int) n 255 | withValues: (char**const) values 256 | andKeys: (char**const) keys { 257 | for(int j = 0; j < n; ++j) { 258 | NSString *val = [NSString stringWithUTF8String: values[j]]; 259 | NSString *key = [NSString stringWithUTF8String: keys[j]]; 260 | [data setObject: val forKey: key]; 261 | } 262 | } 263 | 264 | -(void) setVolume: (NSNotification*) notification { 265 | NSNumber *v = [[notification userInfo] objectForKey: @"volume"]; 266 | backend_volume_set(context, type, idx, i, [v intValue]); 267 | } 268 | 269 | -(void) setVolumes: (NSNotification*) notification { 270 | NSArray *v = [[notification userInfo] objectForKey: @"volume"]; 271 | int count = [v count]; 272 | int *values = malloc(count * sizeof(int)); 273 | for(int j = 0; j < count; ++j) { 274 | values[j] = [[v objectAtIndex: j] intValue]; 275 | } 276 | backend_volume_setall(context, type, idx, values, count); 277 | free(values); 278 | } 279 | 280 | -(void) setMute: (NSNotification*) notification { 281 | BOOL v = [[[notification userInfo] objectForKey: @"mute"] boolValue]; 282 | backend_mute_set(context, type, idx, v ? 1 : 0); 283 | } 284 | 285 | -(void) setCardActiveProfile: (NSNotification*) notification { 286 | NSString *active = [[notification userInfo] objectForKey: @"option"]; 287 | backend_card_profile_set(context, type, idx, [active UTF8String]); 288 | } 289 | 290 | -(void) setActivePort: (NSNotification*) notification { 291 | NSString *active = [[notification userInfo] objectForKey: @"option"]; 292 | backend_port_set(context, type, idx, [active UTF8String]); 293 | } 294 | 295 | -(void) setActiveDevice: (NSNotification*) notification { 296 | NSString *active = [[notification userInfo] objectForKey: @"option"]; 297 | backend_device_set(context, type, idx, [active UTF8String]); 298 | } 299 | 300 | -(void) setDefaults: (NSNotification*) notification { 301 | NSDictionary *userInfo = [notification userInfo]; 302 | NSDictionary *defaults = [userInfo objectForKey: @"defaults"]; 303 | NSString *default_sink = [defaults objectForKey: @"sink"]; 304 | NSString *default_source = [defaults objectForKey: @"source"]; 305 | 306 | if(default_sink != nil) { 307 | backend_default_set(context, SINK, [default_sink UTF8String]); 308 | } 309 | if(default_source != nil) { 310 | backend_default_set(context, SOURCE, [default_source UTF8String]); 311 | } 312 | } 313 | @end 314 | 315 | @implementation Middleware 316 | -(Middleware*) init { 317 | self = [super init]; 318 | blocks = [[NSMutableArray alloc] init]; 319 | 320 | callback = malloc(sizeof(callback_t)); 321 | callback->add = callback_add_func; 322 | callback->update = callback_update_func; 323 | callback->remove = callback_remove_func; 324 | callback->state = callback_state_func; 325 | callback->self = self; 326 | 327 | context = backend_new(callback); 328 | 329 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 330 | Block *block = [self addBlockWithId: -1 331 | andIndex: -1 332 | andType: CARD]; //It does not matter. 333 | [center addObserver: block 334 | selector: @selector(setDefaults:) 335 | name: @"serverDefaultsChanged" 336 | object: nil]; 337 | 338 | [center addObserver: self 339 | selector: @selector(restart:) 340 | name: @"backendGone" 341 | object: self]; 342 | return self; 343 | } 344 | 345 | -(void) restart: (NSNotification*) _ { 346 | backend_init(context, callback); 347 | } 348 | 349 | -(void) dealloc { 350 | [blocks release]; 351 | if(context != NULL) { 352 | backend_destroy(context); 353 | } 354 | free(callback); 355 | [super dealloc]; 356 | } 357 | 358 | -(Block*) addBlockWithId: (uint32_t) idx 359 | andIndex: (int) i 360 | andType: (backend_entry_type) type { 361 | Block *block = [[Block alloc] initWithContext: context 362 | andId: idx 363 | andIndex: i 364 | andType: type]; 365 | [blocks addObject: block]; 366 | [block release]; 367 | return block; 368 | } 369 | @end 370 | -------------------------------------------------------------------------------- /src/settings.cpp: -------------------------------------------------------------------------------- 1 | // pacmixer 2 | // Copyright (C) 2015 Karol 'Kenji Takahashi' Woźniak 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #include "settings.h" 24 | #include "../vendor/cpptoml.h" 25 | 26 | 27 | pacmixer::Settings::Settings() { 28 | std::string home; 29 | auto chome = getenv("XDG_CONFIG_HOME"); 30 | if(chome != NULL) { 31 | home = std::string(chome); 32 | } 33 | if(home.empty()) { 34 | home = std::string(getenv("HOME")) + "/.config"; 35 | } 36 | auto dir = home + "/pacmixer"; 37 | this->fn = dir + "/settings.toml"; 38 | 39 | try { 40 | this->g = std::make_shared(cpptoml::parse_file(this->fn)); 41 | } catch(const cpptoml::parse_exception &e) { 42 | std::cerr << "ERROR parsing config file: " << e.what() << std::endl; 43 | std::cerr << "Writing default config file" << std::endl; 44 | std::cerr << "Old file will be moved to settings.toml~" << std::endl; 45 | 46 | rename(this->fn.c_str(), (dir + "/settings.toml~").c_str()); 47 | mkdirp(dir.c_str()); 48 | 49 | std::ofstream defaults(this->fn); 50 | defaults << "[Display]\n"; 51 | defaults << "StartView = \"All\"\n"; 52 | defaults << "\n"; 53 | defaults << "[Control]\n"; 54 | defaults << "UpSpeed = 1\n"; 55 | defaults << "FastUpSpeed = 10\n"; 56 | defaults << "DownSpeed = 1\n"; 57 | defaults << "FastDownSpeed = 10\n"; 58 | defaults << "[Filter]\n"; 59 | defaults << "Monitors = false\n"; 60 | defaults << "Internals = true\n"; 61 | defaults << "Options = false\n"; 62 | defaults << "\n"; 63 | defaults << "[Log]\n"; 64 | defaults << "Dir = \".local/share\"\n"; 65 | defaults.close(); 66 | 67 | this->g = std::make_shared(cpptoml::parse_file(this->fn)); 68 | } 69 | } 70 | 71 | template T pacmixer::Settings::value(std::string key) const { 72 | return this->g->get_qualified(key)->as()->get(); 73 | } 74 | 75 | View pacmixer::Settings::value(std::string key) const { 76 | auto val = this->g->get_qualified(key)->as()->get(); 77 | if(val == "Playback") { 78 | return PLAYBACK; 79 | } 80 | if(val == "Recording") { 81 | return RECORDING; 82 | } 83 | if(val == "Outputs") { 84 | return OUTPUTS; 85 | } 86 | if(val == "Inputs") { 87 | return INPUTS; 88 | } 89 | if(val == "Settings") { 90 | return SETTINGS; 91 | } 92 | return ALL; 93 | } 94 | 95 | template bool pacmixer::Settings::value(std::string key) const; 96 | template std::string pacmixer::Settings::value(std::string key) const; 97 | template int64_t pacmixer::Settings::value(std::string key) const; 98 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | // pacmixer 2 | // Copyright (C) 2015 Karol 'Kenji Takahashi' Woźniak 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the "Software"), 6 | // to deal in the Software without restriction, including without limitation 7 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | // and/or sell copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #ifndef __PACMIXER_SETTINGS_H__ 24 | #define __PACMIXER_SETTINGS_H__ 25 | 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "enums.h" 32 | #include "../vendor/mkdirp.h" 33 | 34 | // TODO: Use this "normally" when we move more code to CPP. 35 | // Right now it produces compatibility problems between cpptoml 36 | // code and ObjC++ compiler... 37 | // This file should probably be header only, because templates... 38 | namespace cpptoml { 39 | class table; 40 | } 41 | 42 | namespace pacmixer { 43 | class Settings { 44 | std::string fn; 45 | // TODO: We do not need pointer here, 46 | // but we do, because forward decl. 47 | std::shared_ptr g; 48 | 49 | Settings(); 50 | Settings(const Settings&) = delete; 51 | void operator=(const Settings&) = delete; 52 | 53 | public: 54 | static Settings& get() { 55 | static Settings settings; 56 | return settings; 57 | } 58 | 59 | template T value(std::string key) const; 60 | View value(std::string key) const; 61 | }; 62 | 63 | template T setting(std::string key) { 64 | return Settings::get().value(key); 65 | } 66 | template<> inline View setting(std::string key) { 67 | return Settings::get().value(key); 68 | } 69 | } 70 | 71 | 72 | #endif // __PACMIXER_SETTINGS_H__ 73 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2014 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | 22 | 23 | @interface channel_t: NSObject { 24 | @private 25 | NSNumber *maxLevel; 26 | NSNumber *normLevel; 27 | BOOL isMutable; 28 | } 29 | 30 | -(channel_t*) initWithMaxLevel: (int) maxLevel_ 31 | andNormLevel: (int) normLevel_ 32 | andMutable: (int) mutable_; 33 | -(void) dealloc; 34 | -(NSNumber*) maxLevel; 35 | -(NSNumber*) normLevel; 36 | -(BOOL) mutable; 37 | @end 38 | 39 | 40 | @interface volume_t: NSObject { 41 | @private 42 | NSNumber *level; 43 | BOOL mute; 44 | } 45 | 46 | -(volume_t*) initWithLevel: (int) level_ 47 | andMute: (int) mute_; 48 | -(void) dealloc; 49 | -(NSNumber*) level; 50 | -(BOOL) mute; 51 | @end 52 | -------------------------------------------------------------------------------- /src/types.m: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2014 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "types.h" 19 | 20 | 21 | @implementation channel_t 22 | -(channel_t*) initWithMaxLevel: (int) maxLevel_ 23 | andNormLevel: (int) normLevel_ 24 | andMutable: (int) mutable_ { 25 | self = [super init]; 26 | maxLevel = [[NSNumber alloc] initWithInt: maxLevel_]; 27 | normLevel = [[NSNumber alloc] initWithInt: normLevel_]; 28 | isMutable = mutable_ ? YES : NO; 29 | return self; 30 | } 31 | 32 | -(void) dealloc { 33 | [normLevel release]; 34 | [maxLevel release]; 35 | [super dealloc]; 36 | } 37 | 38 | -(NSNumber*) maxLevel { 39 | return maxLevel; 40 | } 41 | 42 | -(NSNumber*) normLevel { 43 | return normLevel; 44 | } 45 | 46 | -(BOOL) mutable { 47 | return isMutable; 48 | } 49 | @end 50 | 51 | 52 | @implementation volume_t 53 | -(volume_t*) initWithLevel: (int) level_ 54 | andMute: (int) mute_ { 55 | self = [super init]; 56 | level = [[NSNumber alloc] initWithInt: level_]; 57 | mute = mute_ ? YES : NO; 58 | return self; 59 | } 60 | 61 | -(void) dealloc { 62 | [level release]; 63 | [super dealloc]; 64 | } 65 | 66 | -(NSNumber*) level { 67 | return level; 68 | } 69 | 70 | -(BOOL) mute { 71 | return mute; 72 | } 73 | @end 74 | -------------------------------------------------------------------------------- /src/widgets/channels.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import "../types.h" 26 | #import "misc.h" 27 | 28 | 29 | @class TUI; 30 | 31 | 32 | @interface Channel: NSObject { 33 | @private 34 | int my; 35 | WINDOW *win; 36 | int currentLevel; 37 | int maxLevel; 38 | int normLevel; 39 | int delta; 40 | BOOL mute; 41 | BOOL isMutable; 42 | BOOL inside; 43 | BOOL propagate; 44 | BOOL hidden; 45 | NSString *signal; 46 | } 47 | 48 | -(Channel*) initWithIndex: (int) i 49 | andMaxLevel: (NSNumber*) mlevel_ 50 | andNormLevel: (NSNumber*) nlevel_ 51 | andMute: (NSNumber*) mute_ // it's BOOL, but we need a pointer 52 | andSignal: (NSString*) signal_ 53 | andParent: (WINDOW*) parent; 54 | -(void) dealloc; 55 | -(void) print; 56 | -(void) reprint: (int) height; 57 | -(void) adjust: (int) i; 58 | -(void) setMute: (BOOL) mute_; 59 | -(void) setLevel: (int) level_; 60 | -(int) level; 61 | -(void) setLevel: (int) level_ andMute: (BOOL) mute_; 62 | -(void) setPropagation: (BOOL) p; 63 | -(void) inside; 64 | -(void) outside; 65 | -(void) up: (int64_t) speed; 66 | -(void) down: (int64_t) speed; 67 | -(void) mute; 68 | -(BOOL) isMuted; 69 | -(void) show; 70 | -(void) hide; 71 | @end 72 | 73 | 74 | typedef enum { 75 | UP, 76 | DOWN 77 | } UpDown; 78 | 79 | @interface Channels: NSObject { 80 | @private 81 | WINDOW *win; 82 | NSMutableArray *channels; 83 | BOOL inside; 84 | BOOL hasPeak; 85 | BOOL hasMute; 86 | BOOL hidden; 87 | int position; 88 | int y; 89 | int my; 90 | int mx; 91 | unsigned int highlight; 92 | NSString *internalId; 93 | } 94 | 95 | -(Channels*) initWithChannels: (NSArray*) channels_ 96 | andPosition: (int) position_ 97 | andId: (NSString*) id_ 98 | andDefault: (BOOL) default_ 99 | andParent: (WINDOW*) parent; 100 | -(void) dealloc; 101 | -(void) print; 102 | -(void) reprint: (int) height; 103 | -(void) setMute: (BOOL) mute forChannel: (int) channel; 104 | -(void) setLevel: (int) level forChannel: (int) channel; 105 | -(void) adjust; 106 | -(void) notify: (NSArray*) values; 107 | -(BOOL) previous; 108 | -(BOOL) next; 109 | -(void) upDown_: (UpDown) updown speed: (int64_t) speed; 110 | -(void) up: (int64_t) sp; 111 | -(void) down: (int64_t) sp; 112 | -(void) inside; 113 | -(void) outside; 114 | -(void) mute; 115 | -(void) show; 116 | -(void) hide; 117 | @end 118 | -------------------------------------------------------------------------------- /src/widgets/channels.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "channels.h" 19 | #import "../frontend.h" 20 | 21 | 22 | @implementation Channel 23 | -(Channel*) initWithIndex: (int) i 24 | andMaxLevel: (NSNumber*) mlevel_ 25 | andNormLevel: (NSNumber*) nlevel_ 26 | andMute: (NSNumber*) mute_ 27 | andSignal: (NSString*) signal_ 28 | andParent: (WINDOW*) parent { 29 | self = [super init]; 30 | signal = [signal_ copy]; 31 | propagate = YES; 32 | hidden = YES; 33 | my = getmaxy(parent) - 1; 34 | win = derwin(parent, my, 1, 0, i + 1); 35 | if(mute_ != nil) { 36 | isMutable = YES; 37 | } else { 38 | isMutable = NO; 39 | } 40 | if(mlevel_ != nil) { 41 | maxLevel = [mlevel_ intValue]; 42 | normLevel = [nlevel_ intValue]; 43 | delta = maxLevel / 100; 44 | } 45 | [self print]; 46 | return self; 47 | } 48 | 49 | -(void) dealloc { 50 | delwin(win); 51 | [signal release]; 52 | [super dealloc]; 53 | } 54 | 55 | -(void) print { 56 | if(hidden) { 57 | return; 58 | } 59 | mvwaddch(win, my - 1, 0, ' ' | (mute ? COLOR_PAIR(4) : COLOR_PAIR(2))); 60 | int currentPos = my - 1; 61 | if(isMutable) { 62 | currentPos -= 2; 63 | } 64 | float dy = (float)currentPos / (float)maxLevel; 65 | int limit = dy * currentLevel; 66 | int high = dy * normLevel; 67 | int medium = high * (4. / 5.); 68 | int low = high * (2. / 5.); 69 | for(int i = 0; i < my - 3; ++i) { 70 | int color = COLOR_PAIR(2); 71 | if(i < limit) { 72 | if(i >= high) { 73 | color = COLOR_PAIR(5); 74 | } else if(i >= medium) { 75 | color = COLOR_PAIR(4); 76 | } else if(i >= low) { 77 | color = COLOR_PAIR(3); 78 | } 79 | } else { 80 | color = COLOR_PAIR(1); 81 | } 82 | mvwaddch(win, currentPos - i, 0, ' ' | color); 83 | } 84 | [TUI refresh]; 85 | } 86 | 87 | -(void) reprint: (int) height { 88 | my = height - 1; 89 | wresize(win, my, 1); 90 | [self print]; 91 | } 92 | 93 | -(void) adjust: (int) i { 94 | mvderwin(win, 0, i + 1); 95 | } 96 | 97 | -(void) setMute: (BOOL) mute_ { 98 | if(isMutable) { 99 | mute = mute_; 100 | [self print]; 101 | } 102 | } 103 | 104 | -(void) setLevel: (int) level_ { 105 | currentLevel = level_; 106 | [self print]; 107 | if(propagate) { 108 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 109 | [NSNumber numberWithInt: currentLevel], @"volume", nil]; 110 | [[NSNotificationCenter defaultCenter] postNotificationName: signal 111 | object: self 112 | userInfo: s]; 113 | } 114 | } 115 | 116 | -(int) level { 117 | return currentLevel; 118 | } 119 | 120 | -(void) setLevel: (int) level_ 121 | andMute: (BOOL) mute_ { 122 | currentLevel = level_; 123 | mute = mute_; 124 | [self print]; 125 | } 126 | 127 | -(void) setPropagation: (BOOL) p { 128 | propagate = p; 129 | } 130 | 131 | -(void) inside { 132 | wattron(win, A_BLINK); 133 | [self print]; 134 | } 135 | 136 | -(void) outside { 137 | wattroff(win, A_BLINK); 138 | [self print]; 139 | } 140 | 141 | -(void) up: (int64_t) speed { 142 | if(currentLevel + delta * speed < maxLevel) { 143 | [self setLevel: currentLevel + delta * speed]; 144 | } else if(currentLevel < maxLevel) { 145 | [self setLevel: maxLevel]; 146 | } 147 | } 148 | 149 | -(void) down: (int64_t) speed { 150 | if(currentLevel > delta * speed) { 151 | [self setLevel: currentLevel - delta * speed]; 152 | } else if(currentLevel > 0) { 153 | [self setLevel: 0]; 154 | } 155 | } 156 | 157 | -(void) mute { 158 | [self setMute: !mute]; 159 | } 160 | 161 | -(BOOL) isMuted { 162 | return mute; 163 | } 164 | 165 | -(void) show { 166 | hidden = NO; 167 | [self print]; 168 | } 169 | 170 | -(void) hide { 171 | hidden = YES; 172 | } 173 | @end 174 | 175 | 176 | @implementation Channels 177 | -(Channels*) initWithChannels: (NSArray*) channels_ 178 | andPosition: (int) position_ 179 | andId: (NSString*) id_ 180 | andDefault: (BOOL) default_ 181 | andParent: (WINDOW*) parent { 182 | self = [super init]; 183 | highlight = 0; 184 | position = position_; 185 | getmaxyx(parent, my, mx); 186 | my -= default_ ? 4 : 1; 187 | mx = [channels_ count] + 2; 188 | hasPeak = NO; 189 | hasMute = NO; 190 | hidden = YES; 191 | for(unsigned int i = 0; i < [channels_ count]; ++i) { 192 | channel_t *obj = [channels_ objectAtIndex: i]; 193 | if([obj maxLevel] != nil) { 194 | hasPeak = YES; 195 | } 196 | if([obj mutable]) { 197 | hasMute = YES; 198 | } 199 | if(hasPeak && hasMute) { 200 | break; 201 | } 202 | } 203 | if(!hasMute) { 204 | my -= 2; 205 | } 206 | y = 0; 207 | if(!hasPeak) { 208 | y = my - 3; 209 | my = 3; 210 | } 211 | win = derwin(parent, my, mx, y, position); 212 | [self print]; 213 | internalId = [id_ copy]; 214 | channels = [[NSMutableArray alloc] init]; 215 | for(unsigned int i = 0; i < [channels_ count]; ++i) { 216 | channel_t *obj = [channels_ objectAtIndex: i]; 217 | NSNumber *mute; 218 | if([obj mutable]) { 219 | mute = [NSNumber numberWithBool: YES]; 220 | } else { 221 | mute = nil; 222 | } 223 | NSString *bname = [NSString stringWithFormat: 224 | @"%@_%d", internalId, i]; 225 | NSString *csignal = [NSString stringWithFormat: 226 | @"%@%@", @"volumeChanged", bname]; 227 | Channel *channel = [[Channel alloc] initWithIndex: i 228 | andMaxLevel: [obj maxLevel] 229 | andNormLevel: [obj normLevel] 230 | andMute: mute 231 | andSignal: csignal 232 | andParent: win]; 233 | [channels addObject: channel]; 234 | } 235 | return self; 236 | } 237 | 238 | -(void) dealloc { 239 | for(unsigned int i = 0; i < [channels count]; ++i) { 240 | Channel *channel = [channels objectAtIndex: i]; 241 | [[NSNotificationCenter defaultCenter] removeObserver: channel]; 242 | } 243 | delwin(win); 244 | [channels release]; 245 | [internalId release]; 246 | [super dealloc]; 247 | } 248 | 249 | -(void) print { 250 | if(!hidden) { 251 | box(win, 0, 0); 252 | if(hasPeak && hasMute) { 253 | mvwaddch(win, my - 3, 0, ACS_LTEE); 254 | mvwhline(win, my - 3, 1, 0, mx - 2); 255 | mvwaddch(win, my - 3, mx - 1, ACS_RTEE); 256 | } 257 | [TUI refresh]; 258 | } 259 | } 260 | 261 | -(void) reprint: (int) height { 262 | height -= 1; 263 | if(!hasMute) { 264 | height -= 2; 265 | } 266 | my = height; 267 | if(hasPeak) { 268 | wresize(win, height, mx); 269 | } else { 270 | mvderwin(win, height - 4, position); 271 | } 272 | for(unsigned int i = 0; i < [channels count]; ++i) { 273 | [(Channel*)[channels objectAtIndex: i] reprint: height]; 274 | } 275 | [self print]; 276 | } 277 | 278 | -(void) setMute: (BOOL) mute forChannel: (int) channel { 279 | [(Channel*)[channels objectAtIndex: channel] setMute: mute]; 280 | } 281 | 282 | -(void) setLevel: (int) level forChannel: (int) channel { 283 | Channel *ch = [channels objectAtIndex: channel]; 284 | [ch setPropagation: NO]; 285 | [ch setLevel: level]; 286 | [ch setPropagation: YES]; 287 | } 288 | 289 | -(void) adjust { 290 | mvderwin(win, y, position); 291 | for(unsigned int i = 0; i < [channels count]; ++i) { 292 | [[channels objectAtIndex: i] adjust: i]; 293 | } 294 | } 295 | 296 | -(void) notify: (NSArray*) values { 297 | NSString *nname = [NSString stringWithFormat: 298 | @"%@%@", @"volumeChanged", internalId]; 299 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 300 | values, @"volume", nil]; 301 | [[NSNotificationCenter defaultCenter] postNotificationName: nname 302 | object: self 303 | userInfo: s]; 304 | } 305 | 306 | -(BOOL) previous { 307 | if(inside && highlight > 0) { 308 | [(Channel*)[channels objectAtIndex: highlight] outside]; 309 | highlight -= 1; 310 | [(Channel*)[channels objectAtIndex: highlight] inside]; 311 | return NO; 312 | } 313 | return YES; 314 | } 315 | 316 | -(BOOL) next { 317 | if(inside && highlight < [channels count] - 1) { 318 | [(Channel*)[channels objectAtIndex: highlight] outside]; 319 | highlight += 1; 320 | [(Channel*)[channels objectAtIndex: highlight] inside]; 321 | return NO; 322 | } 323 | return YES; 324 | } 325 | 326 | -(void) upDown_: (UpDown) updown speed: (int64_t) speed { 327 | if(inside) { 328 | Channel *channel = [channels objectAtIndex: highlight]; 329 | if(updown == UP) { 330 | [channel up: speed]; 331 | } else { 332 | [channel down: speed]; 333 | } 334 | } else { 335 | int count = [channels count]; 336 | NSMutableArray *values = [NSMutableArray arrayWithCapacity: count]; 337 | for(int i = 0; i < count; ++i) { 338 | Channel *channel = [channels objectAtIndex: i]; 339 | [channel setPropagation: NO]; 340 | if(updown == UP) { 341 | [channel up: speed]; 342 | } else { 343 | [channel down: speed]; 344 | } 345 | [values addObject: [NSNumber numberWithInt: [channel level]]]; 346 | [channel setPropagation: YES]; 347 | } 348 | [self notify: values]; 349 | } 350 | } 351 | 352 | -(void) up: (int64_t) speed { 353 | [self upDown_: UP speed: speed]; 354 | } 355 | 356 | -(void) down: (int64_t) speed { 357 | [self upDown_: DOWN speed: speed]; 358 | } 359 | 360 | -(void) inside { 361 | if(!inside) { 362 | inside = YES; 363 | [(Channel*)[channels objectAtIndex: highlight] inside]; 364 | } 365 | } 366 | 367 | -(void) outside { 368 | if(inside) { 369 | inside = NO; 370 | [(Channel*)[channels objectAtIndex: highlight] outside]; 371 | } 372 | } 373 | 374 | -(void) mute { 375 | for(unsigned int i = 0; i < [channels count]; ++i) { 376 | [(Channel*)[channels objectAtIndex: i] mute]; 377 | } 378 | NSString *nname = [NSString stringWithFormat: 379 | @"%@%@", @"muteChanged", internalId]; 380 | BOOL muted = [(Channel*)[channels objectAtIndex: 0] isMuted]; 381 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 382 | [NSNumber numberWithBool: muted], @"mute", nil]; 383 | [[NSNotificationCenter defaultCenter] postNotificationName: nname 384 | object: self 385 | userInfo: s]; 386 | } 387 | 388 | -(void) show { 389 | hidden = NO; 390 | [self print]; 391 | for(unsigned int i = 0; i < [channels count]; ++i) { 392 | Channel *channel = [channels objectAtIndex: i]; 393 | [channel print]; 394 | [channel show]; 395 | } 396 | } 397 | 398 | -(void) hide { 399 | hidden = YES; 400 | for(unsigned int i = 0; i < [channels count]; ++i) { 401 | [(Channel*)[channels objectAtIndex: i] hide]; 402 | } 403 | } 404 | @end 405 | -------------------------------------------------------------------------------- /src/widgets/checkbox.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import "misc.h" 25 | 26 | 27 | @class TUI; 28 | 29 | 30 | @interface CheckBox: NSObject { 31 | @private 32 | WINDOW *win; 33 | NSString *label; 34 | NSArray *names; 35 | NSString *internalId; 36 | NSMutableArray *values; 37 | BOOL highlighted; 38 | BOOL hidden; 39 | unsigned int highlight; 40 | int position; 41 | } 42 | 43 | -(CheckBox*) initWithPosition: (int) ypos 44 | andName: (NSString*) label_ 45 | andValues: (NSArray*) names_ 46 | andId: (NSString*) id_ 47 | andParent: (WINDOW*) parent; 48 | -(void) dealloc; 49 | -(void) print; 50 | -(void) printCheck: (int) i; 51 | -(void) setCurrent: (int) i; 52 | -(void) up; 53 | -(void) down; 54 | -(void) setHighlighted: (BOOL) active; 55 | -(void) setPosition: (int) position_; 56 | -(void) setValue: (BOOL) value atIndex: (int) index; 57 | -(void) switchValue; 58 | -(int) height; 59 | -(int) endVPosition; 60 | -(int) endPosition; 61 | -(NSString*) internalId; 62 | -(void) show; 63 | -(void) hide; 64 | @end 65 | -------------------------------------------------------------------------------- /src/widgets/checkbox.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "checkbox.h" 19 | #import "../frontend.h" 20 | 21 | 22 | @implementation CheckBox 23 | -(CheckBox*) initWithPosition: (int) ypos 24 | andName: (NSString*) label_ 25 | andValues: (NSArray*) names_ 26 | andId: (NSString*) id_ 27 | andParent: (WINDOW*) parent { 28 | self = [super init]; 29 | label = [label_ copy]; 30 | names = [names_ retain]; 31 | internalId = [id_ copy]; 32 | values = [[NSMutableArray alloc] init]; 33 | highlighted = NO; 34 | highlight = 0; 35 | int width = 0; 36 | int height = [names count] + 2; 37 | for(unsigned int i = 0; i < [names count]; ++i) { 38 | int length = [[names objectAtIndex: i] length]; 39 | if(length > width) { 40 | width = length; 41 | } 42 | [values addObject: [NSNumber numberWithBool: NO]]; 43 | } 44 | int my; 45 | int mx; 46 | getmaxyx(parent, my, mx); 47 | width += 5; 48 | BOOL resizeParent = NO; 49 | if(width > mx) { 50 | width = mx; 51 | resizeParent = YES; 52 | } 53 | if(ypos + height > my) { 54 | mx = ypos + height; 55 | resizeParent = YES; 56 | } 57 | if(resizeParent) { 58 | wresize(parent, my, mx); 59 | } 60 | position = ypos; 61 | win = derwin(parent, [values count] + 2, width, position, 0); 62 | hidden = YES; 63 | [self print]; 64 | return self; 65 | } 66 | 67 | -(void) dealloc { 68 | delwin(win); 69 | [values release]; 70 | [internalId release]; 71 | [names release]; 72 | [label release]; 73 | [super dealloc]; 74 | } 75 | 76 | -(void) print { 77 | if(!hidden) { 78 | box(win, 0, 0); 79 | mvwprintw(win, 0, 1, "%@", label); 80 | for(unsigned int i = 0; i < [values count]; ++i) { 81 | mvwprintw( 82 | win, i + 1, 1, "[%c]%@", 83 | [[values objectAtIndex: i] boolValue] ? 'X' : ' ', 84 | [names objectAtIndex: i] 85 | ); 86 | } 87 | [TUI refresh]; 88 | } 89 | } 90 | 91 | -(void) printCheck: (int) i { 92 | if(highlighted) { 93 | wattron(win, A_REVERSE); 94 | } 95 | mvwaddch(win, i + 1, 2, 96 | [[values objectAtIndex: i] boolValue] ? 'X' : ' ' 97 | ); 98 | wattroff(win, A_REVERSE); 99 | } 100 | 101 | -(void) setCurrent: (int) i { 102 | mvwaddch(win, highlight + 1, 2, 103 | [[values objectAtIndex: highlight] boolValue] ? 'X' : ' ' | A_NORMAL 104 | ); 105 | highlight = i; 106 | [self printCheck: highlight]; 107 | } 108 | 109 | -(void) up { 110 | if(highlight > 0) { 111 | [self setCurrent: highlight - 1]; 112 | } 113 | } 114 | 115 | -(void) down { 116 | if(highlight < [names count] - 1) { 117 | [self setCurrent: highlight + 1]; 118 | } 119 | } 120 | 121 | -(void) setHighlighted: (BOOL) active { 122 | highlighted = active; 123 | [self setCurrent: highlight]; 124 | } 125 | 126 | -(void) setPosition: (int) position_ { 127 | } 128 | 129 | -(void) setValue: (BOOL) value atIndex: (int) index { 130 | NSNumber *newValue = [NSNumber numberWithBool: value]; 131 | [values replaceObjectAtIndex: index withObject: newValue]; 132 | NSString *name = @"SettingChanged"; 133 | NSString *fullkey = [NSString stringWithFormat: 134 | @"%@/%@", label, [names objectAtIndex: index]]; 135 | NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: 136 | [NSNumber numberWithBool: value], fullkey, nil]; 137 | [[NSNotificationCenter defaultCenter] postNotificationName: name 138 | object: self 139 | userInfo: info]; 140 | [self printCheck: index]; 141 | } 142 | 143 | -(void) switchValue { 144 | BOOL currentValue = [[values objectAtIndex: highlight] boolValue]; 145 | [self setValue: !currentValue atIndex: highlight]; 146 | } 147 | 148 | -(int) height { 149 | return [names count] + 2; 150 | } 151 | 152 | -(int) endVPosition { 153 | return position + [self height]; 154 | } 155 | 156 | -(int) endPosition { 157 | return [self endVPosition]; 158 | } 159 | 160 | -(NSString*) internalId { 161 | return internalId; 162 | } 163 | 164 | -(void) show { 165 | hidden = NO; 166 | [self print]; 167 | } 168 | 169 | -(void) hide { 170 | hidden = YES; 171 | } 172 | @end 173 | -------------------------------------------------------------------------------- /src/widgets/menu.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import "../enums.h" 22 | 23 | 24 | @interface Top: NSObject { 25 | @private 26 | WINDOW *win; 27 | View view; 28 | } 29 | 30 | -(Top*) initWithView: (View) view_; 31 | -(void) dealloc; 32 | -(void) printString: (NSString*) str 33 | withView: (View) view_; 34 | -(void) print; 35 | -(void) reprint; 36 | -(void) setView: (View) type_; 37 | -(View) view; 38 | @end 39 | 40 | 41 | @interface Bottom: NSObject { 42 | @private 43 | WINDOW *win; 44 | Mode mode; 45 | View view; 46 | } 47 | 48 | -(Bottom*) init; 49 | -(void) dealloc; 50 | -(void) print; 51 | -(void) reprint; 52 | -(void) inside; 53 | -(void) settings; 54 | -(BOOL) outside; 55 | -(void) setView: (View) view_; 56 | -(Mode) mode; 57 | @end 58 | -------------------------------------------------------------------------------- /src/widgets/menu.m: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2013, 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "menu.h" 19 | 20 | 21 | @implementation Top 22 | -(Top*) initWithView: (View) view_ { 23 | self = [super init]; 24 | view = view_; 25 | int mx = getmaxx(stdscr); 26 | win = newwin(1, mx, 0, 0); 27 | [self print]; 28 | return self; 29 | } 30 | 31 | -(void) dealloc { 32 | delwin(win); 33 | [super dealloc]; 34 | } 35 | 36 | -(void) printString: (NSString*) str 37 | withView: (View) view_ { 38 | if(view == view_) { 39 | wprintw(win, " [%@] ", str); 40 | } else { 41 | wprintw(win, " %@ ", str); 42 | } 43 | } 44 | 45 | -(void) print { 46 | wmove(win, 0, 0); 47 | NSString *all = @"F1: All"; 48 | NSString *playback = @"F2: Playback"; 49 | NSString *recording = @"F3: Recording"; 50 | NSString *outputs = @"F4: Outputs"; 51 | NSString *inputs = @"F5: Inputs"; 52 | NSString *settings = @"F12: Settings"; 53 | [self printString: all withView: ALL]; 54 | [self printString: playback withView: PLAYBACK]; 55 | [self printString: recording withView: RECORDING]; 56 | [self printString: outputs withView: OUTPUTS]; 57 | [self printString: inputs withView: INPUTS]; 58 | [self printString: settings withView: SETTINGS]; 59 | wrefresh(win); 60 | } 61 | 62 | -(void) reprint { 63 | int mx = getmaxx(stdscr); 64 | wresize(win, 1, mx); 65 | [self print]; 66 | } 67 | 68 | -(void) setView: (View) type_ { 69 | view = type_; 70 | [self print]; 71 | } 72 | 73 | -(View) view { 74 | return view; 75 | } 76 | @end 77 | 78 | 79 | @implementation Bottom 80 | -(Bottom*) init { 81 | self = [super init]; 82 | int my; 83 | int mx; 84 | getmaxyx(stdscr, my, mx); 85 | win = newwin(1, mx, my - 1, 0); 86 | mode = MODE_OUTSIDE; 87 | [self print]; 88 | return self; 89 | } 90 | 91 | -(void) dealloc { 92 | delwin(win); 93 | [super dealloc]; 94 | } 95 | 96 | -(void) print { 97 | NSString *line; 98 | char mode_ = 'o'; 99 | int color = COLOR_PAIR(6); 100 | if(view == SETTINGS) { 101 | line = 102 | @" h/l: previous/next group, " 103 | @"j/k: previous/next setting, " 104 | @"space: (un)check setting, " 105 | @"q: Exit"; 106 | } else if(mode == MODE_OUTSIDE) { 107 | line = 108 | @" i: inside mode, " 109 | @"s: settings mode, " 110 | @"h/l: previous/next control, " 111 | @"j/k: volume down/up, " 112 | @"m: (un)mute, " 113 | @"d: set as default, " 114 | @"q: Exit"; 115 | } else if(mode == MODE_INSIDE) { 116 | line = 117 | @" q: outside mode, " 118 | @"s: settings mode, " 119 | @"h/l: previous/next channel, " 120 | @"j/k: volume down/up, " 121 | @"m: (un)mute" 122 | @"d: set as default, "; 123 | mode_ = 'i'; 124 | color = COLOR_PAIR(7); 125 | } else if(mode == MODE_SETTINGS) { 126 | line = 127 | @" q: outside mode, " 128 | @"i: inside mode, " 129 | @"h/l: previous/next control, " 130 | @"j/k: previous/next setting, " 131 | @"space: (un)check setting"; 132 | mode_ = 's'; 133 | color = COLOR_PAIR(4); 134 | } else { 135 | line = @""; 136 | mode_ = '?'; 137 | } 138 | werase(win); 139 | wattron(win, color | A_BOLD); 140 | wprintw(win, " %c ", mode_); 141 | wattroff(win, color | A_BOLD); 142 | wprintw(win, "%@", line); 143 | wrefresh(win); 144 | } 145 | 146 | -(void) reprint { 147 | int my; 148 | int mx; 149 | getmaxyx(stdscr, my, mx); 150 | mvwin(win, my - 1, 0); 151 | wresize(win, 1, mx); 152 | [self print]; 153 | } 154 | 155 | -(void) inside { 156 | if(mode != MODE_INSIDE) { 157 | mode = MODE_INSIDE; 158 | [self print]; 159 | } 160 | } 161 | 162 | -(void) settings { 163 | if(mode != MODE_SETTINGS) { 164 | mode = MODE_SETTINGS; 165 | [self print]; 166 | } 167 | } 168 | 169 | -(BOOL) outside { 170 | if(mode != MODE_OUTSIDE) { 171 | mode = MODE_OUTSIDE; 172 | [self print]; 173 | return NO; 174 | } 175 | return YES; 176 | } 177 | 178 | -(void) setView: (View) view_ { 179 | view = view_; 180 | [self print]; 181 | } 182 | 183 | -(Mode) mode { 184 | return mode; 185 | } 186 | @end 187 | -------------------------------------------------------------------------------- /src/widgets/misc.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | 22 | 23 | @protocol Controlling 24 | -(BOOL) previous; 25 | -(BOOL) next; 26 | -(void) outside; 27 | -(void) mute; 28 | @end 29 | 30 | 31 | @protocol Hiding 32 | -(void) show; 33 | -(void) hide; 34 | @end 35 | 36 | 37 | @protocol Modal 38 | @property WINDOW *win; 39 | @property WINDOW *parent; 40 | @property int width; 41 | @property(readonly) int height; 42 | @property int position; 43 | 44 | -(void) reprint: (int) height_; 45 | -(void) setHighlighted: (BOOL) active; 46 | @end 47 | 48 | 49 | @interface Modal: NSObject { 50 | @private 51 | id window; 52 | int owidth; 53 | PANEL *pan; 54 | } 55 | 56 | -(Modal*) initWithWindow: (id) window_; 57 | -(void) dealloc; 58 | -(void) reprint: (int) height_; 59 | -(void) setHighlighted: (BOOL) active; 60 | -(void) adjust; 61 | -(void) forwardInvocation: (NSInvocation*) inv; 62 | @end 63 | -------------------------------------------------------------------------------- /src/widgets/misc.m: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2013 - 2014 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "misc.h" 19 | 20 | 21 | @implementation Modal 22 | -(Modal*) initWithWindow: (id) window_ { 23 | self = [super init]; 24 | window = [window_ retain]; 25 | return self; 26 | } 27 | 28 | -(void) dealloc { 29 | if(pan != NULL) { 30 | del_panel(pan); 31 | } 32 | [window release]; 33 | [super dealloc]; 34 | } 35 | 36 | -(void) reprint: (int) height_ { 37 | [window reprint: height_]; 38 | if(pan != NULL) { 39 | wresize(window.win, window.height, getmaxx(stdscr)); 40 | replace_panel(pan, window.win); 41 | move_panel(pan, window.position + 2, 0); 42 | } 43 | } 44 | 45 | -(void) setHighlighted: (BOOL) active { 46 | if(active) { 47 | owidth = window.width; 48 | window.width = getmaxx(stdscr); 49 | delwin(window.win); 50 | window.win = newwin( 51 | window.height, window.width, window.position + 2, 0 52 | ); 53 | pan = new_panel(window.win); 54 | } else { 55 | window.width = owidth; 56 | if(pan != NULL) { 57 | del_panel(pan); 58 | pan = NULL; 59 | } 60 | delwin(window.win); 61 | window.win = derwin( 62 | window.parent, window.height, window.width, window.position, 0 63 | ); 64 | } 65 | [window setHighlighted: active]; 66 | } 67 | 68 | -(void) adjust { 69 | if(pan == NULL) { 70 | mvderwin(window.win, window.position, 0); 71 | } 72 | } 73 | 74 | -(void) forwardInvocation: (NSInvocation*) inv { 75 | if([window respondsToSelector: [inv selector]]) { 76 | [inv invokeWithTarget: window]; 77 | } else { 78 | [super forwardInvocation: inv]; 79 | } 80 | } 81 | @end 82 | -------------------------------------------------------------------------------- /src/widgets/notice.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | 21 | 22 | @interface Notice: NSObject { 23 | @private 24 | WINDOW *win; 25 | NSString *message; 26 | } 27 | 28 | -(Notice*) initWithMessage: (NSString*) message_ 29 | andParent: (WINDOW*) parent; 30 | -(void) print; 31 | -(void) dealloc; 32 | @end 33 | -------------------------------------------------------------------------------- /src/widgets/notice.m: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "notice.h" 19 | 20 | 21 | @implementation Notice 22 | -(Notice*) initWithMessage: (NSString*) message_ 23 | andParent: (WINDOW*) parent { 24 | self = [super init]; 25 | message = [message_ copy]; 26 | int mx = getmaxx(stdscr); 27 | int py; 28 | int px; 29 | getmaxyx(parent, py, px); 30 | int l = [message length] + 2; 31 | int lines = 1; 32 | int width = l; 33 | while(l > mx) { 34 | l -= mx; 35 | lines += 1; 36 | } 37 | if(lines > 1) { 38 | width = mx; 39 | } 40 | if(px < width) { 41 | wresize(parent, py, width); 42 | px = width; 43 | } 44 | win = derwin(parent, lines + 2, width, (py - lines) / 2, (px - width) / 2); 45 | [self print]; 46 | return self; 47 | } 48 | 49 | -(void) print { 50 | wattron(win, A_REVERSE); 51 | box(win, 0, 0); 52 | mvwprintw(win, 1, 1, "%@", message); 53 | wattroff(win, A_REVERSE); 54 | } 55 | 56 | -(void) dealloc { 57 | delwin(win); 58 | [super dealloc]; 59 | } 60 | @end 61 | -------------------------------------------------------------------------------- /src/widgets/options.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import "misc.h" 25 | #import "../enums.h" 26 | #import "../types.h" 27 | 28 | 29 | @class TUI; 30 | 31 | 32 | @interface Options: NSObject { 33 | @protected 34 | WINDOW *_win; 35 | WINDOW *_parent; 36 | unsigned int _width; 37 | int _height; 38 | NSString *label; 39 | NSString *internalId; 40 | NSArray *options; 41 | NSArray *mapping; 42 | BOOL highlighted; 43 | unsigned int current; 44 | unsigned int highlight; 45 | int _position; 46 | BOOL hidden; 47 | } 48 | 49 | @property WINDOW *win; 50 | @property WINDOW *parent; 51 | @property unsigned int width; 52 | @property(readonly) int height; 53 | @property int position; 54 | 55 | -(id) initWithPosition: (int) ypos 56 | andName: (NSString*) label_ 57 | andValues: (NSArray*) options_ 58 | andId: (NSString*) id_ 59 | andParent: (WINDOW*) parent_; 60 | -(id) initWithWidth: (int) width_ 61 | andName: (NSString*) label_ 62 | andValues: (NSArray*) options_ 63 | andId: (NSString*) id_ 64 | andParent: (WINDOW*) parent_; 65 | -(id) initWithName: (NSString*) label_ 66 | andValues: (NSArray*) options_ 67 | andId: (NSString*) id_ 68 | andParent: (WINDOW*) parent_; 69 | -(void) dealloc; 70 | -(void) print; 71 | -(void) reprint: (int) height_; 72 | -(void) calculateDimensions; 73 | -(void) setCurrent: (int) i; 74 | -(void) setCurrentByName: (NSString*) name; 75 | -(void) setCurrentByNotification: (NSNotification*) notification; 76 | -(void) setHighlighted: (BOOL) active; 77 | -(void) setPosition: (int) position_; 78 | -(void) replaceValues: (NSArray*) values; 79 | -(void) replaceMapping: (NSArray*) values; 80 | -(void) up; 81 | -(void) down; 82 | -(void) switchValue; 83 | -(int) height; 84 | -(int) endVPosition; 85 | -(int) endPosition; 86 | -(View) type; 87 | -(NSString*) name; 88 | -(NSString*) internalId; 89 | -(void) show; 90 | -(void) hide; 91 | @end 92 | -------------------------------------------------------------------------------- /src/widgets/options.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "options.h" 19 | #import "../frontend.h" 20 | 21 | 22 | @implementation Options 23 | 24 | @synthesize win = _win; 25 | @synthesize parent = _parent; 26 | @synthesize width = _width; 27 | @synthesize position = _position; 28 | 29 | -(id) initWithPosition: (int) ypos 30 | andName: (NSString*) label_ 31 | andValues: (NSArray*) options_ 32 | andId: (NSString*) id_ 33 | andParent: (WINDOW*) parent_ { 34 | _width = 0; 35 | for(unsigned int i = 0; i < [options_ count]; ++i) { 36 | unsigned int length = [[options_ objectAtIndex: i] length]; 37 | if(length > _width) { 38 | _width = length; 39 | } 40 | } 41 | _position = ypos; 42 | return [self initWithName: label_ 43 | andValues: options_ 44 | andId: id_ 45 | andParent: parent_]; 46 | } 47 | 48 | -(id) initWithWidth: (int) width_ 49 | andName: (NSString*) label_ 50 | andValues: (NSArray*) options_ 51 | andId: (NSString*) id_ 52 | andParent: (WINDOW*) parent_ { 53 | _width = width_; 54 | _position = getmaxy(parent_) - [options_ count] - 3; 55 | return [self initWithName: label_ 56 | andValues: options_ 57 | andId: id_ 58 | andParent: parent_]; 59 | } 60 | 61 | -(id) initWithName: (NSString*) label_ 62 | andValues: (NSArray*) options_ 63 | andId: (NSString*) id_ 64 | andParent: (WINDOW*) parent_ { 65 | self = [super init]; 66 | _parent = parent_; 67 | label = [label_ copy]; 68 | internalId = [id_ copy]; 69 | highlighted = NO; 70 | highlight = 0; 71 | options = [options_ retain]; 72 | _width += 2; 73 | [self calculateDimensions]; 74 | _win = derwin(_parent, _height, _width, _position, 0); 75 | hidden = YES; 76 | [self print]; 77 | return self; 78 | } 79 | 80 | -(void) dealloc { 81 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 82 | delwin(_win); 83 | [internalId release]; 84 | [options release]; 85 | [mapping release]; 86 | [label release]; 87 | [super dealloc]; 88 | } 89 | 90 | -(void) print { 91 | if(!(hidden || pacmixer::setting("Filter.Options"))) { 92 | werase(self.win); 93 | box(self.win, 0, 0); 94 | if([label length] > self.width - 2) { 95 | mvwprintw(self.win, 0, 1, "%@", [label substringToIndex: self.width - 2]); 96 | } else { 97 | mvwprintw(self.win, 0, 1, "%@", label); 98 | } 99 | for(unsigned int i = 0; i < [options count]; ++i) { 100 | NSString *obj = [options objectAtIndex: i]; 101 | if(i == current) { 102 | wattron(self.win, COLOR_PAIR(6)); 103 | } 104 | if(highlighted && i == highlight) { 105 | wattroff(self.win, COLOR_PAIR(6)); 106 | wattron(self.win, A_REVERSE); 107 | } 108 | mvwprintw(self.win, i + 1, 1, " "); 109 | if([obj length] > self.width - 2) { 110 | mvwprintw(self.win, i + 1, 1, "%@", 111 | [obj substringToIndex: self.width - 2] 112 | ); 113 | } else { 114 | mvwprintw(self.win, i + 1, 1, "%@", obj); 115 | } 116 | wattroff(self.win, A_REVERSE); 117 | wattroff(self.win, COLOR_PAIR(6)); 118 | } 119 | [TUI refresh]; 120 | } 121 | } 122 | 123 | -(void) reprint: (int) height_ { 124 | [self setPosition: height_ - [options count] - 3]; 125 | wresize(self.win, self.height, self.width); 126 | [self print]; 127 | } 128 | 129 | -(void) calculateDimensions { 130 | _height = [options count] + 2; 131 | int my; 132 | unsigned int mx; 133 | getmaxyx(_parent, my, mx); 134 | BOOL resizeParent = NO; 135 | if(_width > mx) { 136 | mx = _width; 137 | resizeParent = YES; 138 | } 139 | if(_position + _height > my) { 140 | my = _position + _height; 141 | resizeParent = YES; 142 | } 143 | if(resizeParent) { 144 | wresize(_parent, my, mx); 145 | } 146 | } 147 | 148 | -(void) setCurrent: (int) i { 149 | highlight = i; 150 | [self print]; 151 | } 152 | 153 | -(void) setCurrentByName: (NSString*) name { 154 | highlight = [options indexOfObject: name]; 155 | current = highlight; 156 | [self print]; 157 | } 158 | 159 | -(void) setCurrentByNotification: (NSNotification*) notification { 160 | NSDictionary *info = [notification userInfo]; 161 | [self setCurrentByName: [info objectForKey: @"activeProfile"]]; 162 | } 163 | 164 | -(void) setHighlighted: (BOOL) active { 165 | highlighted = active; 166 | [self print]; 167 | } 168 | 169 | -(void) setPosition: (int) position_ { 170 | _position = position_; 171 | mvderwin(self.win, self.position, 0); 172 | } 173 | 174 | -(void) replaceValues: (NSArray*) values { 175 | [options release]; 176 | options = [values retain]; 177 | [self calculateDimensions]; 178 | } 179 | 180 | -(void) replaceMapping: (NSArray*) values { 181 | [mapping release]; 182 | mapping = [values retain]; 183 | } 184 | 185 | -(void) up { 186 | if(highlight > 0) { 187 | [self setCurrent: highlight - 1]; 188 | } 189 | } 190 | 191 | -(void) down { 192 | if(highlight < [options count] - 1) { 193 | [self setCurrent: highlight + 1]; 194 | } 195 | } 196 | 197 | -(void) switchValue { 198 | current = highlight; 199 | NSString *sname = [NSString stringWithFormat: 200 | @"%@%@", @"activeOptionChanged", internalId]; 201 | NSArray *source = mapping != nil ? mapping : options; 202 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 203 | [source objectAtIndex: highlight], @"option", nil]; 204 | [[NSNotificationCenter defaultCenter] postNotificationName: sname 205 | object: self 206 | userInfo: s]; 207 | [self print]; 208 | } 209 | 210 | -(int) height { 211 | if(pacmixer::setting("Filter.Options")) { 212 | return 0; 213 | } 214 | return [options count] + 2; 215 | } 216 | 217 | -(int) endVPosition { 218 | return self.position + self.height; 219 | } 220 | 221 | -(int) endPosition { 222 | return [self endVPosition]; 223 | } 224 | 225 | -(View) type { 226 | return SETTINGS; 227 | } 228 | 229 | -(NSString*) name { 230 | return label; 231 | } 232 | 233 | -(NSString*) internalId { 234 | return internalId; 235 | } 236 | 237 | -(void) show { 238 | hidden = NO; 239 | [self print]; 240 | } 241 | 242 | -(void) hide { 243 | hidden = YES; 244 | } 245 | @end 246 | -------------------------------------------------------------------------------- /src/widgets/widget.h: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "channels.h" 19 | #import "options.h" 20 | #import "../enums.h" 21 | 22 | 23 | @class TUI; 24 | 25 | 26 | @interface Widget: NSObject { 27 | @private 28 | WINDOW *win; 29 | int position; 30 | int height; 31 | int width; 32 | NSString *name; 33 | View type; 34 | NSString *internalId; 35 | NSString *_internalName; 36 | Channels *channels; 37 | id ports; 38 | BOOL highlighted; 39 | Mode mode; 40 | BOOL hidden; 41 | BOOL isDefault; 42 | BOOL hasDefault; 43 | WINDOW *parent; 44 | } 45 | 46 | @property(retain) NSString *internalName; 47 | 48 | -(Widget*) initWithPosition: (int) p 49 | andName: (NSString*) name_ 50 | andType: (View) type_ 51 | andId: (NSString*) id_ 52 | andParent: (WINDOW*) parent_; 53 | -(void) dealloc; 54 | -(void) print; 55 | -(void) reprint: (int) height_; 56 | -(void) printName; 57 | -(void) printDefault; 58 | -(Channels*) addChannels: (NSArray*) channels_; 59 | -(id) addOptions: (NSArray*) options_ 60 | withName: (NSString*) optname; 61 | -(void) replaceOptions: (NSArray*) values; 62 | -(void) setHighlighted: (BOOL) active; 63 | -(void) setPosition: (int) position_; 64 | -(void) setValuesByNotification: (NSNotification*) notification; 65 | -(void) setDefault: (BOOL) default_; 66 | -(void) switchDefault; 67 | -(BOOL) canGoInside; 68 | -(BOOL) canGoSettings; 69 | -(void) inside; 70 | -(void) settings; 71 | -(void) outside; 72 | -(void) previous; 73 | -(void) next; 74 | -(void) up: (int64_t) speed; 75 | -(void) down: (int64_t) speed; 76 | -(void) mute; 77 | -(void) switchValue; 78 | -(int) width; 79 | -(int) endHPosition; 80 | -(int) endPosition; 81 | -(View) type; 82 | -(NSString*) name; 83 | -(NSString*) internalId; 84 | -(id) options; 85 | -(void) show; 86 | -(void) hide; 87 | @end 88 | -------------------------------------------------------------------------------- /src/widgets/widget.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2012 - 2015 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "widget.h" 19 | #import "../frontend.h" 20 | 21 | 22 | @implementation Widget 23 | 24 | @synthesize internalName = _internalName; 25 | 26 | -(Widget*) initWithPosition: (int) p 27 | andName: (NSString*) name_ 28 | andType: (View) type_ 29 | andId: (NSString*) id_ 30 | andParent: (WINDOW*) parent_ { 31 | self = [super init]; 32 | position = p + 1; 33 | name = [name_ copy]; 34 | type = type_; 35 | internalId = [id_ copy]; 36 | parent = parent_; 37 | width = 8; 38 | hidden = YES; 39 | mode = MODE_OUTSIDE; 40 | hasDefault = type == INPUTS || type == OUTPUTS; 41 | isDefault = NO; 42 | [self print]; 43 | PACMIXER_LOG("F:%d:%s posted", [internalId intValue], [name UTF8String]); 44 | return self; 45 | } 46 | 47 | -(void) dealloc { 48 | [[NSNotificationCenter defaultCenter] removeObserver: self]; 49 | if(ports != nil) { 50 | [ports release]; 51 | } 52 | if(channels != nil) { 53 | [channels release]; 54 | } 55 | [name release]; 56 | [internalId release]; 57 | [_internalName release]; 58 | delwin(win); 59 | [super dealloc]; 60 | } 61 | 62 | -(void) print { 63 | int mx; 64 | getmaxyx(parent, height, mx); 65 | wresize(parent, height, mx + width + 1); 66 | if(win == NULL) { 67 | win = derwin(parent, height, width, 0, position); 68 | } else { 69 | wresize(win, height, width); 70 | } 71 | [TUI refresh]; 72 | } 73 | 74 | -(void) reprint: (int) height_ { 75 | werase(win); 76 | height = height_; 77 | wresize(win, height, width); 78 | [self printName]; 79 | [self printDefault]; 80 | int ch_height = height - ([ports height] > 2 ? [ports height] : 0); 81 | ch_height -= hasDefault ? 3 : 0; 82 | [channels reprint: ch_height]; 83 | [ports reprint: height]; 84 | } 85 | 86 | -(void) printName { 87 | if(hidden) { 88 | return; 89 | } 90 | int color = highlighted ? COLOR_PAIR(6) : COLOR_PAIR(5); 91 | int length = (width - (int)[name length]) / 2; 92 | if(length < 0) { 93 | length = 0; 94 | } 95 | wattron(win, color | A_BOLD); 96 | mvwprintw(win, height - 1, 0, "%@", 97 | [@"" stringByPaddingToLength: width 98 | withString: @" " 99 | startingAtIndex: 0] 100 | ); 101 | NSString *sn = [name length] > 8 ? [name substringToIndex: width] : name; 102 | mvwprintw(win, height - 1, length, "%@", sn); 103 | wattroff(win, color | A_BOLD); 104 | } 105 | 106 | -(void) printDefault { 107 | if(!hasDefault || hidden) { 108 | return; 109 | } 110 | int y = height - [ports height] - 4; 111 | mvwaddch(win, y, 0, ACS_ULCORNER); 112 | whline(win, 0, width - 2); 113 | mvwaddch(win, y++, width - 1, ACS_URCORNER); 114 | mvwaddch(win, y++, 0, ACS_VLINE); 115 | for(int _ = 0; _ < width - 2; ++_) { 116 | waddch(win, ' ' | (isDefault ? COLOR_PAIR(2) : COLOR_PAIR(4))); 117 | } 118 | waddch(win, ACS_VLINE); 119 | mvwaddch(win, y, 0, ACS_LLCORNER); 120 | whline(win, 0, width - 2); 121 | mvwaddch(win, y, width - 1, ACS_LRCORNER); 122 | [TUI refresh]; 123 | } 124 | 125 | -(Channels*) addChannels: (NSArray*) channels_ { 126 | int width_ = [channels_ count] + 2; 127 | int position_ = (width - width_) / 2; 128 | if(width_ > 8) { 129 | width = width_; 130 | [self print]; 131 | [self printName]; 132 | [self printDefault]; 133 | } 134 | channels = [[Channels alloc] initWithChannels: channels_ 135 | andPosition: position_ 136 | andId: internalId 137 | andDefault: hasDefault 138 | andParent: win]; 139 | if(!hidden) { 140 | [channels show]; 141 | } 142 | return channels; 143 | } 144 | 145 | -(id) addOptions: (NSArray*) options_ 146 | withName: (NSString*) optname { 147 | Options *p = [[Options alloc] initWithWidth: width - 2 148 | andName: optname 149 | andValues: options_ 150 | andId: internalId 151 | andParent: win]; 152 | ports = [[Modal alloc] initWithWindow: p]; 153 | [p release]; 154 | if(!hidden) { 155 | [ports show]; 156 | [self printDefault]; 157 | } 158 | [channels reprint: height - [ports height] - (hasDefault ? 3 : 0)]; 159 | PACMIXER_LOG("F:%d:%s options added", [internalId intValue], [name UTF8String]); 160 | return ports; 161 | } 162 | 163 | -(void) replaceOptions: (NSArray*) values { 164 | [ports replaceValues: values]; 165 | [ports reprint: height]; 166 | [channels reprint: height - [ports height] - (hasDefault ? 3 : 0)]; 167 | } 168 | 169 | -(void) setHighlighted: (BOOL) active { 170 | highlighted = active; 171 | [self printName]; 172 | } 173 | 174 | -(void) setPosition: (int) position_ { 175 | position = position_ + 1; 176 | mvderwin(win, 0, position); 177 | [channels adjust]; 178 | [ports adjust]; 179 | } 180 | 181 | -(void) setValuesByNotification: (NSNotification*) notification { 182 | NSDictionary* info = [notification userInfo]; 183 | NSArray *volumes = [info objectForKey: @"volumes"]; 184 | if(volumes != nil) { 185 | for(unsigned int i = 0; i < [volumes count]; ++i) { 186 | volume_t *vol = [volumes objectAtIndex: i]; 187 | [channels setLevel: [[vol level] intValue] forChannel: i]; 188 | [channels setMute: [vol mute] forChannel: i]; 189 | } 190 | } 191 | 192 | NSArray *port_names = [info objectForKey: @"portNames"]; 193 | NSArray *port_descs = [info objectForKey: @"portDescriptions"]; 194 | NSString *active_port = [info objectForKey: @"activePort"]; 195 | if(port_names != nil && port_descs != nil && active_port != nil) { 196 | [self replaceOptions: port_descs]; 197 | [ports replaceMapping: port_names]; 198 | [ports setCurrentByName: active_port]; 199 | } 200 | 201 | View option_type = type == PLAYBACK ? OUTPUTS : INPUTS; 202 | NSNumber *device = [info objectForKey: @"deviceIndex"]; 203 | if(device != nil) { 204 | NSString *current_id = [NSString stringWithFormat: 205 | @"%@_%d", device, option_type]; 206 | [ports setCurrentByName: [[TUI getWidgetWithId: current_id] name]]; 207 | } 208 | } 209 | 210 | -(void) setDefault: (BOOL) default_ { 211 | PACMIXER_LOG("F:%d:%s set as default", [internalId intValue], [name UTF8String]); 212 | isDefault = default_; 213 | [self printDefault]; 214 | } 215 | 216 | -(void) switchDefault { 217 | //We cannot un-set default in PA, so only switch if it is not set. 218 | if(!isDefault) { 219 | [self setDefault: YES]; 220 | 221 | NSString *dname = @"serverDefaultsChanged"; 222 | NSDictionary *p = [NSDictionary dictionaryWithObjectsAndKeys: 223 | self.internalName, type == INPUTS ? @"source" : @"sink", nil]; 224 | NSDictionary *s = [NSDictionary dictionaryWithObjectsAndKeys: 225 | p, @"defaults", nil]; 226 | [[NSNotificationCenter defaultCenter] postNotificationName: dname 227 | object: self 228 | userInfo: s]; 229 | } 230 | } 231 | 232 | -(BOOL) canGoInside { 233 | return channels != nil; 234 | } 235 | 236 | -(BOOL) canGoSettings { 237 | return ports != nil; 238 | } 239 | 240 | -(void) inside { 241 | if(mode == MODE_SETTINGS) { 242 | [ports setHighlighted: NO]; 243 | } 244 | if(mode != MODE_INSIDE) { 245 | mode = MODE_INSIDE; 246 | [channels inside]; 247 | } 248 | } 249 | 250 | -(void) settings { 251 | if(mode == MODE_INSIDE) { 252 | [channels outside]; 253 | } 254 | if(mode != MODE_SETTINGS) { 255 | mode = MODE_SETTINGS; 256 | [ports setHighlighted: YES]; 257 | } 258 | } 259 | 260 | -(void) outside { 261 | if(mode != MODE_OUTSIDE) { 262 | if(mode == MODE_INSIDE) { 263 | [channels outside]; 264 | } else if(mode == MODE_SETTINGS) { 265 | [ports setHighlighted: NO]; 266 | } 267 | mode = MODE_OUTSIDE; 268 | } 269 | } 270 | 271 | -(void) previous { 272 | [channels previous]; 273 | } 274 | 275 | -(void) next { 276 | [channels next]; 277 | } 278 | 279 | -(void) up: (int64_t) speed { 280 | if(mode == MODE_SETTINGS) { 281 | [ports up]; 282 | } else { 283 | [channels up: speed]; 284 | } 285 | } 286 | 287 | -(void) down: (int64_t) speed { 288 | if(mode == MODE_SETTINGS) { 289 | [ports down]; 290 | } else { 291 | [channels down: speed]; 292 | } 293 | } 294 | 295 | -(void) mute { 296 | [channels mute]; 297 | } 298 | 299 | -(void) switchValue { 300 | if(mode == MODE_SETTINGS) { 301 | [ports switchValue]; 302 | } 303 | } 304 | 305 | -(int) width { 306 | return width; 307 | } 308 | 309 | -(int) endHPosition { 310 | return position + width; 311 | } 312 | 313 | -(int) endPosition { 314 | return [self endHPosition]; 315 | } 316 | 317 | -(View) type { 318 | return type; 319 | } 320 | 321 | -(NSString*) name { 322 | return name; 323 | } 324 | 325 | -(NSString*) internalId { 326 | return internalId; 327 | } 328 | 329 | -(id) options { 330 | return ports; 331 | } 332 | 333 | -(void) show { 334 | hidden = NO; 335 | [self printName]; 336 | [self printDefault]; 337 | [channels show]; 338 | [ports show]; 339 | } 340 | 341 | -(void) hide { 342 | hidden = YES; 343 | [channels hide]; 344 | [ports hide]; 345 | } 346 | @end 347 | -------------------------------------------------------------------------------- /tests/mock_pulseaudio.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2013 - 2014 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "mock_pulseaudio.h" 21 | 22 | 23 | pa_threaded_mainloop *pa_threaded_mainloop_new() { 24 | return &s_instance; 25 | } 26 | 27 | pa_mainloop_api *pa_threaded_mainloop_get_api(pa_threaded_mainloop *loop) { 28 | return &s_api; 29 | } 30 | 31 | pa_context *pa_context_new(pa_mainloop_api *api, const char *name) { 32 | return &s_context; 33 | } 34 | 35 | int pa_context_connect(pa_context *context, void *s, int n, void *m) { 36 | return s_instance; 37 | } 38 | 39 | void pa_context_unref(pa_context *context) {} 40 | 41 | void pa_context_set_state_callback(pa_context *context, void(*s)(pa_context*, void*), void *m) {} 42 | 43 | void pa_threaded_mainloop_start(pa_threaded_mainloop *loop) {} 44 | 45 | void pa_context_set_subscribe_callback(pa_context *context, void(*s)(pa_context*, pa_subscription_event_type_t, uint32_t, void*), void *m) {} 46 | 47 | void pa_context_subscribe(pa_context *context, int mask, void *s, void *m) {} 48 | 49 | void pa_context_get_sink_input_info_list(pa_context *context, void(*s)(pa_context*, const pa_sink_input_info*, int, void*), void *m) {} 50 | 51 | void pa_context_get_source_output_info_list(pa_context *context, void(*s)(pa_context*, const pa_source_output_info*, int, void*), void *m) {} 52 | 53 | void pa_context_get_sink_info_list(pa_context *context, void(*s)(pa_context*, const pa_sink_info*, int, void*), void *m) {} 54 | 55 | void pa_context_get_source_info_list(pa_context *context, void(*s)(pa_context*, const pa_source_info*, int, void*), void *m) {} 56 | 57 | void pa_context_get_card_info_list(pa_context *context, void(*s)(pa_context*, const pa_card_info*, int, void*), void *m) {} 58 | 59 | void pa_threaded_mainloop_stop(pa_threaded_mainloop* loop) {} 60 | 61 | void pa_context_disconnect(pa_context *context) {} 62 | 63 | void pa_threaded_mainloop_free(pa_threaded_mainloop *loop) {} 64 | 65 | typedef struct VOLUME_CALLBACK { 66 | int index; 67 | int value; 68 | } volume_callback_t; 69 | 70 | #define PA_CONTEXT_GET_INFO(output)\ 71 | volume_callback_t *volume = m;\ 72 | output[0] = idx;\ 73 | output[1] = volume->index;\ 74 | output[2] = volume->value;\ 75 | 76 | void pa_context_get_sink_info_by_index(pa_context *context, uint32_t idx, void(*s)(pa_context *c, const pa_sink_info *info, int eol, void *a), void *m) { 77 | PA_CONTEXT_GET_INFO(output_sink_info); 78 | } 79 | 80 | void pa_context_get_sink_input_info(pa_context *context, uint32_t idx, void(*s)(pa_context *c, const pa_sink_input_info *info, int eol, void *a), void *m) { 81 | PA_CONTEXT_GET_INFO(output_sink_input_info); 82 | } 83 | 84 | void pa_context_get_source_info_by_index(pa_context *context, uint32_t idx, void(*s)(pa_context *c, const pa_source_info *info, int eol, void *a), void *m) { 85 | PA_CONTEXT_GET_INFO(output_source_info); 86 | } 87 | 88 | void pa_context_get_source_output_info(pa_context *context, uint32_t idx, void(*s)(pa_context *c, const pa_source_output_info *info, int eol, void *a), void *m) { 89 | PA_CONTEXT_GET_INFO(output_source_output_info); 90 | } 91 | 92 | #define PA_CONTEXT_SET_VOLUME(output)\ 93 | output[0] = idx;\ 94 | output[1] = v->values[0];\ 95 | output[2] = v->values[1];\ 96 | 97 | void pa_context_set_sink_volume_by_index(pa_context *context, uint32_t idx, pa_cvolume *v, void *s, void *m) { 98 | PA_CONTEXT_SET_VOLUME(output_sink_volume); 99 | } 100 | 101 | void pa_context_set_sink_input_volume(pa_context *context, uint32_t idx, pa_cvolume *v, void *s, void *m) { 102 | PA_CONTEXT_SET_VOLUME(output_sink_input_volume); 103 | } 104 | 105 | void pa_context_set_source_volume_by_index(pa_context *context, uint32_t idx, pa_cvolume *v, void *s, void *m) { 106 | PA_CONTEXT_SET_VOLUME(output_source_volume); 107 | } 108 | 109 | void pa_context_set_source_output_volume(pa_context *context, uint32_t idx, pa_cvolume *v, void *s, void *m) { 110 | PA_CONTEXT_SET_VOLUME(output_source_output_volume); 111 | } 112 | 113 | #define PA_CONTEXT_SET_MUTE(output)\ 114 | output[0] = idx;\ 115 | output[1] = v;\ 116 | 117 | void pa_context_set_sink_mute_by_index(pa_context* context, uint32_t idx, int v, void *s, void *m) { 118 | PA_CONTEXT_SET_MUTE(output_sink_mute); 119 | } 120 | 121 | void pa_context_set_sink_input_mute(pa_context *context, uint32_t idx, int v, void *s, void *m) { 122 | PA_CONTEXT_SET_MUTE(output_sink_input_mute); 123 | } 124 | 125 | void pa_context_set_source_mute_by_index(pa_context *context, uint32_t idx, int v, void *s, void *m) { 126 | PA_CONTEXT_SET_MUTE(output_source_mute); 127 | } 128 | 129 | void pa_context_set_source_output_mute(pa_context *context, uint32_t idx, int v, void *s, void *m) { 130 | PA_CONTEXT_SET_MUTE(output_source_output_mute); 131 | } 132 | 133 | void pa_context_set_card_profile_by_index(pa_context *context, uint32_t idx, const char *name, void *s, void *m) { 134 | output_card_profile.index = idx; 135 | strcpy(output_card_profile.active, name); 136 | } 137 | 138 | #define PA_CONTEXT_SET_OPTION(output)\ 139 | output.index = idx;\ 140 | strcpy(output.active, name);\ 141 | 142 | void pa_context_set_sink_port_by_index(pa_context *context, uint32_t idx, const char *name, void *s, void *m) { 143 | PA_CONTEXT_SET_OPTION(output_sink_port); 144 | } 145 | 146 | void pa_context_set_source_port_by_index(pa_context *context, uint32_t idx, const char *name, void *s, void *m) { 147 | PA_CONTEXT_SET_OPTION(output_source_port); 148 | } 149 | 150 | void pa_context_set_default_sink(pa_context *context, const char *name, void *s, void *m) { 151 | strcpy(default_sink, name); 152 | } 153 | 154 | void pa_context_set_default_source(pa_context *context, const char *name, void *s, void *m) { 155 | strcpy(default_source, name); 156 | } 157 | 158 | pa_context_state_t pa_context_get_state(pa_context *context) { 159 | return s_state; 160 | } 161 | 162 | const char *pa_proplist_gets(const pa_proplist proplist, int mask) { 163 | if(mask == PA_PROP_DEVICE_DESCRIPTION) { 164 | return proplist; 165 | } 166 | return ""; 167 | } 168 | 169 | void pa_context_get_card_info_by_index(pa_context *context, uint32_t idx, void(*s)(pa_context *c, const pa_card_info *info, int eol, void *a), void *m) { 170 | output_card_info = idx; 171 | } 172 | 173 | void pa_context_get_client_info(pa_context *context, uint32_t idx, void(*s)(pa_context*, const pa_client_info*, int, void*), void *m) { 174 | output_client_info = idx; 175 | pa_client_info info; 176 | info.index = PA_CLIENT_INDEX; 177 | strcpy(info.name, "client_name"); 178 | s(context, &info, 0, m); 179 | } 180 | 181 | void pa_context_get_server_info(pa_context *context, void(*s)(pa_context*, const pa_server_info*, void*), void *m) { 182 | } 183 | 184 | void pa_context_move_sink_input_by_name(pa_context *context, uint32_t idx, const char *name, void(*s)(pa_context*, int, void*), void *m) { 185 | PA_CONTEXT_SET_OPTION(output_sink_input_device); 186 | } 187 | 188 | void pa_context_move_source_output_by_name(pa_context *context, uint32_t idx, const char *name, void(*s)(pa_context*, int, void*), void *m) { 189 | PA_CONTEXT_SET_OPTION(output_source_output_device); 190 | } 191 | -------------------------------------------------------------------------------- /tests/mock_pulseaudio.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2013 - 2014 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include 21 | #include 22 | #include "mock_variables.h" 23 | 24 | 25 | typedef int pa_context_state_t; 26 | 27 | typedef enum { 28 | PA_CONTEXT_NOFAIL 29 | } pa_context_flags_t; 30 | 31 | typedef enum { 32 | PA_SUBSCRIPTION_MASK_ALL, 33 | PA_SUBSCRIPTION_EVENT_TYPE_MASK, 34 | PA_SUBSCRIPTION_EVENT_FACILITY_MASK, 35 | PA_SUBSCRIPTION_EVENT_CARD, 36 | PA_SUBSCRIPTION_EVENT_CHANGE, 37 | PA_SUBSCRIPTION_EVENT_REMOVE, 38 | PA_SUBSCRIPTION_EVENT_NEW, 39 | PA_SUBSCRIPTION_EVENT_SINK_INPUT, 40 | PA_SUBSCRIPTION_EVENT_SINK, 41 | PA_SUBSCRIPTION_EVENT_SOURCE, 42 | PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT, 43 | PA_SUBSCRIPTION_EVENT_SERVER 44 | } pa_subscription_event_type_t; 45 | 46 | enum { 47 | PA_PROP_DEVICE_DESCRIPTION 48 | }; 49 | 50 | #define PA_VOLUME_UI_MAX 150 51 | #define PA_VOLUME_NORM 100 52 | 53 | pa_threaded_mainloop *pa_threaded_mainloop_new(); 54 | pa_mainloop_api *pa_threaded_mainloop_get_api(pa_threaded_mainloop*); 55 | pa_context *pa_context_new(pa_mainloop_api*, const char*); 56 | int pa_context_connect(pa_context*, void*, int, void*); 57 | void pa_context_unref(pa_context*); 58 | void pa_context_set_state_callback(pa_context*, void(*)(pa_context*, void*), void*); 59 | void pa_threaded_mainloop_start(pa_threaded_mainloop*); 60 | void pa_context_set_subscribe_callback(pa_context*, void(*)(pa_context*, pa_subscription_event_type_t, uint32_t, void*), void*); 61 | void pa_context_subscribe(pa_context*, int, void*, void*); 62 | void pa_context_get_sink_input_info_list(pa_context*, void(*)(pa_context*, const pa_sink_input_info*, int, void*), void*); 63 | void pa_context_get_sink_info_list(pa_context*, void(*)(pa_context*, const pa_sink_info*, int, void*), void*); 64 | void pa_context_get_source_output_info_list(pa_context*, void(*)(pa_context*, const pa_source_output_info*, int, void*), void*); 65 | void pa_context_get_source_info_list(pa_context*, void(*)(pa_context*, const pa_source_info*, int, void*), void*); 66 | void pa_context_get_card_info_list(pa_context*, void(*)(pa_context*, const pa_card_info*, int, void*), void*); 67 | void pa_threaded_mainloop_stop(pa_threaded_mainloop*); 68 | void pa_context_disconnect(pa_context*); 69 | void pa_threaded_mainloop_free(pa_threaded_mainloop*); 70 | void pa_context_get_sink_info_by_index(pa_context*, uint32_t, void(*)(pa_context*, const pa_sink_info*, int, void*), void*); 71 | void pa_context_get_sink_input_info(pa_context*, uint32_t, void(*)(pa_context*, const pa_sink_input_info*, int, void*), void*); 72 | void pa_context_get_source_info_by_index(pa_context*, uint32_t, void(*)(pa_context*, const pa_source_info*, int, void*), void*); 73 | void pa_context_get_source_output_info(pa_context*, uint32_t, void(*)(pa_context*, const pa_source_output_info*, int, void*), void*); 74 | void pa_context_set_sink_volume_by_index(pa_context*, uint32_t, pa_cvolume*, void*, void*); 75 | void pa_context_set_sink_input_volume(pa_context*, uint32_t, pa_cvolume*, void*, void*); 76 | void pa_context_set_source_volume_by_index(pa_context*, uint32_t, pa_cvolume*, void*, void*); 77 | void pa_context_set_source_output_volume(pa_context*, uint32_t, pa_cvolume*, void*, void*); 78 | void pa_context_set_sink_mute_by_index(pa_context*, uint32_t, int, void*, void*); 79 | void pa_context_set_sink_input_mute(pa_context*, uint32_t, int, void*, void*); 80 | void pa_context_set_source_mute_by_index(pa_context*, uint32_t, int, void*, void*); 81 | void pa_context_set_source_output_mute(pa_context*, uint32_t, int, void*, void*); 82 | void pa_context_set_card_profile_by_index(pa_context*, uint32_t, const char*, void*, void*); 83 | void pa_context_set_sink_port_by_index(pa_context*, uint32_t, const char*, void*, void*); 84 | void pa_context_set_source_port_by_index(pa_context*, uint32_t, const char*, void*, void*); 85 | void pa_context_set_default_sink(pa_context*, const char*, void*, void*); 86 | void pa_context_set_default_source(pa_context*, const char*, void*, void*); 87 | pa_context_state_t pa_context_get_state(pa_context*); 88 | const char *pa_proplist_gets(const pa_proplist, int); 89 | void pa_context_get_card_info_by_index(pa_context*, uint32_t, void(*)(pa_context*, const pa_card_info*, int, void*), void*); 90 | void pa_context_get_client_info(pa_context*, uint32_t, void(*)(pa_context*, const pa_client_info*, int, void*), void*); 91 | void pa_context_get_server_info(pa_context*, void(*)(pa_context*, const pa_server_info*, void*), void*); 92 | void pa_context_move_sink_input_by_name(pa_context*, uint32_t, const char*, void(*)(pa_context*, int, void*), void*); 93 | void pa_context_move_source_output_by_name(pa_context*, uint32_t, const char*, void(*)(pa_context*, int, void*), void*); 94 | -------------------------------------------------------------------------------- /tests/mock_variables.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2013 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #include "mock_variables.h" 21 | 22 | 23 | pa_threaded_mainloop s_instance = -1; 24 | pa_mainloop_api s_api = 1; 25 | pa_context s_context = 1; 26 | pa_context_state_t s_state = PA_CONTEXT_UNCONNECTED; 27 | 28 | int output_sink_volume[3] = {PA_INVALID_INDEX, 0, 0}; 29 | int output_sink_input_volume[3] = {PA_INVALID_INDEX, 0, 0}; 30 | int output_source_volume[3] = {PA_INVALID_INDEX, 0, 0}; 31 | int output_source_output_volume[3] = {PA_INVALID_INDEX, 0, 0}; 32 | 33 | int output_sink_info[3] = {PA_INVALID_INDEX, 0, 0}; 34 | int output_sink_input_info[3] = {PA_INVALID_INDEX, 0, 0}; 35 | int output_source_info[3] = {PA_INVALID_INDEX, 0, 0}; 36 | int output_source_output_info[3] = {PA_INVALID_INDEX, 0, 0}; 37 | 38 | int output_sink_mute[2] = {PA_INVALID_INDEX, 0}; 39 | int output_sink_input_mute[2] = {PA_INVALID_INDEX, 0}; 40 | int output_source_mute[2] = {PA_INVALID_INDEX, 0}; 41 | int output_source_output_mute[2] = {PA_INVALID_INDEX, 0}; 42 | 43 | output_index_active_t output_card_profile = {.index = PA_INVALID_INDEX, .active = ""}; 44 | 45 | output_index_active_t output_sink_port = {.index = PA_INVALID_INDEX, .active = ""}; 46 | output_index_active_t output_source_port = {.index = PA_INVALID_INDEX, .active = ""}; 47 | 48 | output_index_active_t output_sink_input_device = {.index = PA_INVALID_INDEX, .active = ""}; 49 | output_index_active_t output_source_output_device = {.index = PA_INVALID_INDEX, .active = ""}; 50 | 51 | int output_card_info = PA_INVALID_INDEX; 52 | 53 | int output_client_info = PA_INVALID_INDEX; 54 | 55 | char default_sink[STRING_SIZE]; 56 | char default_source[STRING_SIZE]; 57 | 58 | void reset_mock_variables() { 59 | s_instance = -1; 60 | s_api = 1; 61 | s_context = 1; 62 | output_sink_volume[0] = PA_INVALID_INDEX; 63 | output_sink_volume[1] = 0; 64 | output_sink_volume[2] = 0; 65 | output_sink_input_volume[0] = PA_INVALID_INDEX; 66 | output_sink_input_volume[1] = 0; 67 | output_sink_input_volume[2] = 0; 68 | output_source_volume[0] = PA_INVALID_INDEX; 69 | output_source_volume[1] = 0; 70 | output_source_volume[2] = 0; 71 | output_source_output_volume[0] = PA_INVALID_INDEX; 72 | output_source_output_volume[1] = 0; 73 | output_source_output_volume[2] = 0; 74 | output_sink_info[0] = PA_INVALID_INDEX; 75 | output_sink_info[1] = 0; 76 | output_sink_info[2] = 0; 77 | output_sink_input_info[0] = PA_INVALID_INDEX; 78 | output_sink_input_info[1] = 0; 79 | output_sink_input_info[2] = 0; 80 | output_source_info[0] = PA_INVALID_INDEX; 81 | output_source_info[1] = 0; 82 | output_source_info[2] = 0; 83 | output_source_output_info[0] = PA_INVALID_INDEX; 84 | output_source_output_info[1] = 0; 85 | output_source_output_info[2] = 0; 86 | output_sink_mute[0] = PA_INVALID_INDEX; 87 | output_sink_mute[1] = 0; 88 | output_sink_input_mute[0] = PA_INVALID_INDEX; 89 | output_sink_input_mute[1] = 0; 90 | output_source_mute[0] = PA_INVALID_INDEX; 91 | output_source_mute[1] = 0; 92 | output_source_output_mute[0] = PA_INVALID_INDEX; 93 | output_source_output_mute[1] = 0; 94 | output_card_profile.index = 0; 95 | strcpy(output_card_profile.active, ""); 96 | output_sink_port.index = 0; 97 | strcpy(output_sink_port.active, ""); 98 | output_source_port.index = 0; 99 | strcpy(output_source_port.active, ""); 100 | output_sink_input_device.index = 0; 101 | strcpy(output_sink_input_device.active, ""); 102 | output_source_output_device.index = 0; 103 | strcpy(output_source_output_device.active, ""); 104 | output_card_info = PA_INVALID_INDEX; 105 | output_client_info = PA_INVALID_INDEX; 106 | strcpy(default_sink, ""); 107 | strcpy(default_source, ""); 108 | } 109 | -------------------------------------------------------------------------------- /tests/mock_variables.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 3 | Karol "Kenji Takahashi" Woźniak © 2013 - 2014 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | 20 | #ifndef MOCK_VARIABLES_H 21 | #define MOCK_VARIABLES_H 22 | 23 | 24 | #include 25 | #include 26 | 27 | 28 | #define STRING_SIZE 32 29 | #define PA_CHANNELS_MAX 32U // Taken from PA sources. 30 | 31 | typedef int pa_threaded_mainloop; 32 | typedef int pa_mainloop_api; 33 | typedef int pa_context; 34 | typedef int pa_context_state_t; 35 | typedef struct PA_CLIENT_INFO { 36 | uint32_t index; 37 | char name[STRING_SIZE]; 38 | } pa_client_info; 39 | typedef struct PA_CVOLUME { 40 | int channels; 41 | int values[PA_CHANNELS_MAX]; 42 | } pa_cvolume; 43 | struct PA_INFO { 44 | uint32_t index; 45 | pa_cvolume volume; 46 | int mute; 47 | const char *name; 48 | uint32_t client; 49 | const char *description; 50 | uint32_t sink; 51 | uint32_t source; 52 | }; 53 | typedef struct PA_INFO pa_sink_input_info; 54 | typedef struct PA_INFO pa_source_output_info; 55 | struct PA_PORT_INFO { 56 | char name[STRING_SIZE]; 57 | char description[STRING_SIZE]; 58 | }; 59 | typedef struct PA_PORT_INFO pa_sink_port_info; 60 | typedef struct PA_PORT_INFO pa_source_port_info; 61 | typedef struct PA_PORT_INFO pa_sink_port_info; 62 | typedef struct PA_PORT_INFO pa_source_port_info; 63 | struct PA_PORT_INFO_INFO { 64 | uint32_t index; 65 | pa_cvolume volume; 66 | int mute; 67 | char name[STRING_SIZE]; 68 | char description[STRING_SIZE]; 69 | int n_ports; 70 | struct PA_PORT_INFO **ports; 71 | struct PA_PORT_INFO *active_port; 72 | }; 73 | typedef struct PA_PORT_INFO_INFO pa_sink_info; 74 | typedef struct PA_PORT_INFO_INFO pa_source_info; 75 | typedef struct PA_CARD_PROFILE_INFO { 76 | char name[STRING_SIZE]; 77 | char description[STRING_SIZE]; 78 | } pa_card_profile_info; 79 | typedef char pa_proplist[STRING_SIZE]; 80 | typedef struct PA_CARD_INFO { 81 | uint32_t index; 82 | int n_profiles; 83 | pa_proplist proplist; 84 | pa_card_profile_info *profiles; 85 | pa_card_profile_info *active_profile; 86 | } pa_card_info; 87 | typedef struct PA_SERVER_INFO { 88 | const char *default_sink_name; 89 | const char *default_source_name; 90 | } pa_server_info; 91 | typedef struct OUTPUT_INDEX_ACTIVE { 92 | int index; 93 | char active[STRING_SIZE]; 94 | } output_index_active_t; 95 | 96 | extern pa_threaded_mainloop s_instance; 97 | extern pa_mainloop_api s_api; 98 | extern pa_context s_context; 99 | extern pa_context_state_t s_state; 100 | 101 | extern int output_sink_volume[3]; 102 | extern int output_sink_input_volume[3]; 103 | extern int output_source_volume[3]; 104 | extern int output_source_output_volume[3]; 105 | 106 | extern int output_sink_info[3]; 107 | extern int output_sink_input_info[3]; 108 | extern int output_source_info[3]; 109 | extern int output_source_output_info[3]; 110 | 111 | extern int output_sink_mute[2]; 112 | extern int output_sink_input_mute[2]; 113 | extern int output_source_mute[2]; 114 | extern int output_source_output_mute[2]; 115 | 116 | extern output_index_active_t output_card_profile; 117 | 118 | extern output_index_active_t output_sink_port; 119 | extern output_index_active_t output_source_port; 120 | 121 | extern output_index_active_t output_sink_input_device; 122 | extern output_index_active_t output_source_output_device; 123 | 124 | extern int output_card_info; 125 | 126 | extern int output_client_info; 127 | 128 | extern char default_sink[STRING_SIZE]; 129 | extern char default_source[STRING_SIZE]; 130 | 131 | enum { 132 | PA_CONTEXT_UNCONNECTED, 133 | PA_CONTEXT_READY, 134 | PA_CONTEXT_FAILED, 135 | PA_CONTEXT_TERMINATED 136 | }; 137 | 138 | enum { 139 | PA_INVALID_INDEX, 140 | PA_VALID_INDEX, 141 | PA_CLIENT_INDEX 142 | }; 143 | 144 | void reset_mock_variables(); 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /tests/test_main.mm: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #import "../vendor/catch.hpp" 4 | -------------------------------------------------------------------------------- /tests/test_types.mm: -------------------------------------------------------------------------------- 1 | // This is a part of pacmixer @ http://github.com/KenjiTakahashi/pacmixer 2 | // Karol "Kenji Takahashi" Woźniak © 2013 - 2014 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | 18 | #import "../vendor/catch.hpp" 19 | #import "../src/types.h" 20 | 21 | 22 | #define STRING_SIZE 32 23 | 24 | TEST_CASE("channel_t", "") { 25 | SECTION("mutable", "") { 26 | NSNumber *max = [NSNumber numberWithInt: 120]; 27 | NSNumber *norm = [NSNumber numberWithInt: 90]; 28 | 29 | channel_t *channel_i = [[channel_t alloc] initWithMaxLevel: 120 30 | andNormLevel: 90 31 | andMutable: 1]; 32 | 33 | REQUIRE([[channel_i maxLevel] isEqualToNumber: max]); 34 | REQUIRE([[channel_i normLevel] isEqualToNumber: norm]); 35 | REQUIRE([channel_i mutable] == YES); 36 | 37 | [channel_i release]; 38 | } 39 | 40 | SECTION("not mutable", "") { 41 | NSNumber *max = [NSNumber numberWithInt: 90]; 42 | NSNumber *norm = [NSNumber numberWithInt: 50]; 43 | 44 | channel_t *channel_i = [[channel_t alloc] initWithMaxLevel: 90 45 | andNormLevel: 50 46 | andMutable: 0]; 47 | 48 | REQUIRE([[channel_i maxLevel] isEqualToNumber: max]); 49 | REQUIRE([[channel_i normLevel] isEqualToNumber: norm]); 50 | REQUIRE([channel_i mutable] == NO); 51 | 52 | [channel_i release]; 53 | } 54 | } 55 | 56 | TEST_CASE("volume_t", "") { 57 | SECTION("mute", "") { 58 | NSNumber *lvl = [NSNumber numberWithInt: 90]; 59 | 60 | volume_t *volume_i = [[volume_t alloc] initWithLevel: 90 61 | andMute: 1]; 62 | 63 | REQUIRE([[volume_i level] isEqualToNumber: lvl]); 64 | REQUIRE([volume_i mute] == YES); 65 | 66 | [volume_i release]; 67 | } 68 | 69 | SECTION("do not mute", "") { 70 | NSNumber *lvl = [NSNumber numberWithInt: 50]; 71 | 72 | volume_t *volume_i = [[volume_t alloc] initWithLevel: 50 73 | andMute: 0]; 74 | 75 | REQUIRE([[volume_i level] isEqualToNumber: lvl]); 76 | REQUIRE([volume_i mute] == NO); 77 | 78 | [volume_i release]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /vendor/mkdirp.h: -------------------------------------------------------------------------------- 1 | // MIT/X Consortium License 2 | // 3 | // (C)opyright MMV-MMVI Anselm R. Garbe 4 | // (C)opyright MMV-MMVIII Nico Golde 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a 7 | // copy of this software and associated documentation files (the "Software"), 8 | // to deal in the Software without restriction, including without limitation 9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | // and/or sell copies of the Software, and to permit persons to whom the 11 | // Software is furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 22 | // DEALINGS IN THE SOFTWARE. 23 | 24 | 25 | #ifndef __VENDOR_MKDIRP_H__ 26 | #define __VENDOR_MKDIRP_H__ 27 | 28 | 29 | #include 30 | 31 | 32 | static inline void mkdirp(const char *dir) { 33 | char tmp[256]; 34 | char *p = NULL; 35 | size_t len; 36 | snprintf(tmp, sizeof(tmp),"%s",dir); 37 | len = strlen(tmp); 38 | if(tmp[len - 1] == '/') 39 | tmp[len - 1] = 0; 40 | for(p = tmp + 1; *p; p++) 41 | if(*p == '/') { 42 | *p = 0; 43 | mkdir(tmp, S_IRWXU); 44 | *p = '/'; 45 | } 46 | mkdir(tmp, S_IRWXU); 47 | } 48 | 49 | 50 | #endif // __VENDOR_MKDIRP_H__ 51 | --------------------------------------------------------------------------------