├── .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 | [](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 | 
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 |
--------------------------------------------------------------------------------