pax_global_header00006660000000000000000000000064132262046140014512gustar00rootroot0000000000000052 comment=ea4ab3b969b5ff7b6eaa51d1519c27c1c7acfbfd PAmix-1.6-10-gea4ab3b/000077500000000000000000000000001322620461400141625ustar00rootroot00000000000000PAmix-1.6-10-gea4ab3b/.clang_complete000066400000000000000000000000631322620461400171360ustar00rootroot00000000000000-xc++ -Iinclude -std=c++11 -I/usr/include/ncursesw PAmix-1.6-10-gea4ab3b/.gitignore000066400000000000000000000000711322620461400161500ustar00rootroot00000000000000build/ .vscode .idea cmake-build-* config.hpp man/pamix.1PAmix-1.6-10-gea4ab3b/CMakeGitDefines.cmake000066400000000000000000000014741322620461400201140ustar00rootroot00000000000000# Get the current working branch execute_process( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the latest abbreviated commit hash of the working branch execute_process( COMMAND git log -1 --format=%h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) execute_process( COMMAND git describe --tags WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GIT_TAG RESULT_VARIABLE GIT_TAG_ERR OUTPUT_STRIP_TRAILING_WHITESPACE ) if (${GIT_TAG_ERR} EQUAL 0) set(GIT_VERSION "${GIT_TAG}") else() set(GIT_VERSION "${GIT_BRANCH} ${GIT_COMMIT_HASH}") endif ()PAmix-1.6-10-gea4ab3b/CMakeLists.txt000066400000000000000000000017721322620461400167310ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) project(pamix) include(CMakeGitDefines.cmake) option(WITH_UNICODE "Compile with unicode support and symbols" ON) set(NCURSESW_H_INCLUDE "ncursesw/ncurses.h" CACHE STRING "ncursesw include directive content") configure_file(config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/config.hpp ) configure_file(man/pamix.1.in ${CMAKE_CURRENT_SOURCE_DIR}/man/pamix.1) set(CMAKE_CXX_STANDARD 11) file(GLOB_RECURSE pamix_SRC "include/*.h" "src/*.cpp") include_directories("include") link_libraries("pulse" "pthread") find_package(PkgConfig REQUIRED QUIET) IF (WITH_UNICODE) pkg_search_module(NCURSESW REQUIRED ncursesw) link_libraries(${NCURSESW_LDFLAGS}) add_definitions(-DFEAT_UNICODE) ELSE () pkg_search_module(NCURSES REQUIRED ncurses) link_libraries(${NCURSES_LDFLAGS}) ENDIF () add_executable(pamix ${pamix_SRC}) install(FILES pamix.conf DESTINATION /etc/xdg) install(TARGETS pamix DESTINATION bin) install(FILES man/pamix.1 DESTINATION share/man/man1) PAmix-1.6-10-gea4ab3b/LICENSE000066400000000000000000000020621322620461400151670ustar00rootroot00000000000000The MIT License Copyright (c) 2016 Joshua Jensch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PAmix-1.6-10-gea4ab3b/README.md000066400000000000000000000043601322620461400154440ustar00rootroot00000000000000# PAMix - the pulseaudio terminal mixer # Table of Contents # 1. [**Installation**](#installation) 1. Gentoo 2. Arch 3. openSUSE 2. [**Building Manually**](#building-manually) 1. Dependencies 2. Building and Installing 4. Build Options 3. [**Configuration**](#configuration) 4. [**Default Keybindings**](#default-keybindings) ![alt tag](http://i.imgur.com/NuzrAXZ.gif) # Installation: # ### Gentoo ### `emerge media-sound/pamix` ### Arch ### `yaourt -S pamix-git` ### openSUSE ### `zypper in pamix` # Building Manually: # ## Dependencies: # In order for CMake to properly inject the current version into the headers and man files it has to be inside the git repository. ### Build ## * cmake * pkg-config ### Runtime ## * PulseAudio * Ncurses ## Building and Installing ```bash mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=RELEASE -DWITH_UNICODE=1 make sudo make install ``` ## Build Options ### Unicode Support To compile PAmix with ncurses and without unicode support pass `-DWITH_UNICODE=0` as an argument to cmake. This will use ncurses instead of ncursesw. ### ncursesw header location You can pass in the include location of your ncursesw header file as an argument, if you are having problems with the default setup. So if your ncursesw header file is at /usr/include/ncw/ncurses.h you would pass `-DNCURSESW_H_INCLUDE="ncw/ncurses.h"` # Configuration # Configure pamix and set keybindings using pamix.conf (see [**Configuration**](https://github.com/patroclos/PAmix/wiki/Configuration) for detailed instructions) # Default Keybindings # (arrow keys are also supported instead of hjkl) | Action | Key | |----------------------------|-----| | Playback tab | F1 | | Recording Tab | F2 | | Output Devices | F3 | | Input Devices | F4 | | Cards | F5 | | Set volume to percentage | 0-9 | | Decrease Volume | h | | Increase Volume | l | | Select Next | j | | Jump to next Entry | J | | Select Previous | k | | Jump to previous Entry | K | | (Un)Mute | m | | Next/Previous device/port | s/S | | (Un)Lock Channels together | c | | Quit | q | PAmix-1.6-10-gea4ab3b/config.hpp.in000066400000000000000000000002121322620461400165400ustar00rootroot00000000000000#pragma once #ifdef FEAT_UNICODE #include <${NCURSESW_H_INCLUDE}> #else #include #endif #define GIT_VERSION "${GIT_VERSION}" PAmix-1.6-10-gea4ab3b/include/000077500000000000000000000000001322620461400156055ustar00rootroot00000000000000PAmix-1.6-10-gea4ab3b/include/configuration.hpp000066400000000000000000000021221322620461400211620ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #define CONFIGURATION_AUTOSPAWN_PULSE "pulseaudio_autospawn" #define CONFIGURATION_DEFAULT_TAB "default_tab" struct keybind_t { union argument_t m_Argument; void (*m_Function)(argument_t arg); }; class Configuration { private: std::map m_Variables; std::map> m_Bindings; static std::vector> m_Keynames; static int getKeycode(const std::string &name); public: bool has(const std::string &key); std::string getString(const std::string &key); int getInt(const std::string &key); bool getBool(const std::string &key); bool pressKey(int code); void set(const std::string &key, const std::string &value); void unset(const std::string &key); void bind(const std::string &key, const std::string &cmd, const std::string &arg = ""); void unbind(const std::string &key); void unbindall(); static bool loadFile(Configuration *config, const std::string &path); }; PAmix-1.6-10-gea4ab3b/include/entry.hpp000066400000000000000000000114261322620461400174630ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include struct Entry; typedef std::map>::iterator pamix_entry_iter_t; class PAInterface; enum entry_type { ENTRY_SINK, ENTRY_SOURCE, ENTRY_SINKINPUT, ENTRY_SOURCEOUTPUT, ENTRY_CARDS, ENTRY_COUNT }; static std::map entryTypeNames = { {ENTRY_SINK, "Output Devices"}, {ENTRY_SINKINPUT, "Playback"}, {ENTRY_SOURCE, "Input Devices"}, {ENTRY_SOURCEOUTPUT, "Recording"}, {ENTRY_CARDS, "Cards"} }; struct Entry { PAInterface *interface; std::string m_Name; uint32_t m_Index; double m_Peak = 0; pa_stream *m_Monitor = nullptr; uint32_t m_MonitorIndex; bool m_Mute; pa_cvolume m_PAVolume; pa_channel_map m_PAChannelMap; bool m_Lock = true; bool m_Kill; bool m_Meter = true; Entry(PAInterface *iface); ~Entry(); // general methods virtual void setVolume(const int channel, const pa_volume_t volume) = 0; virtual void setMute(bool mute) = 0; virtual void addVolume(const int channel, const double deltaPct); virtual void cycleSwitch(bool increment) = 0; virtual void update(const pa_sink_info *info) {} virtual void update(const pa_source_info *info) {} virtual void update(const pa_sink_input_info *info) {} virtual void update(const pa_source_output_info *info) {} virtual void update(const pa_card_info *info) {} // device methods virtual void suspend() {} virtual void setPort(const char *port) {} // stream methods virtual void move(uint32_t idx) {} virtual void kill() {} }; struct DeviceEntry : public Entry { struct DeviceProfile { std::string name; std::string description; DeviceProfile(std::string name, std::string description) : name(name), description(description) {} }; int m_Port; std::vector m_Ports; DeviceEntry(PAInterface *paInterface) : Entry(paInterface) {}; // general methods virtual void setVolume(const int channel, const pa_volume_t volume) = 0; virtual void setMute(bool mute) = 0; virtual void cycleSwitch(bool increment) = 0; virtual const DeviceProfile *getPortProfile() const { return m_Port > -1 ? &m_Ports[m_Port] : nullptr; } virtual void setPort(const char *port) = 0; }; struct StreamEntry : public Entry { uint32_t m_Device; StreamEntry(PAInterface *iface) : Entry(iface) {}; // general methods virtual void setVolume(const int channel, const pa_volume_t volume) = 0; virtual void setMute(bool mute) = 0; virtual void cycleSwitch(bool increment) = 0; virtual void move(uint32_t idx) = 0; virtual void kill() = 0; }; struct SinkEntry : public DeviceEntry { pa_sink_state_t m_State; SinkEntry(PAInterface *iface) : DeviceEntry(iface) {} void update(const pa_sink_info *info); // general methods virtual void setVolume(const int channel, const pa_volume_t volume); virtual void setMute(bool mute); virtual void cycleSwitch(bool increment); virtual void setPort(const char *port); }; struct SourceEntry : public DeviceEntry { pa_source_state_t m_State; SourceEntry(PAInterface *iface) : DeviceEntry(iface) {} void update(const pa_source_info *info); // general methods virtual void setVolume(const int channel, const pa_volume_t volume); virtual void setMute(bool mute); virtual void cycleSwitch(bool increment); virtual void setPort(const char *port); }; struct SinkInputEntry : public StreamEntry { void update(const pa_sink_input_info *info); SinkInputEntry(PAInterface *iface) : StreamEntry(iface) {} // general methods virtual void setVolume(const int channel, const pa_volume_t volume); virtual void setMute(bool mute); virtual void cycleSwitch(bool increment); virtual void move(uint32_t idx); virtual void kill(); }; struct SourceOutputEntry : public StreamEntry { void update(const pa_source_output_info *info); SourceOutputEntry(PAInterface *iface) : StreamEntry(iface) {} // general methods virtual void setVolume(const int channel, const pa_volume_t volume); virtual void setMute(bool mute); virtual void cycleSwitch(bool increment); virtual void move(uint32_t idx); virtual void kill(); }; struct CardEntry : public Entry { struct CardProfile { std::string name; std::string description; CardProfile(pa_card_profile_info2 *profile) : name(profile->name), description(profile->description) {}; }; int m_Profile; std::vector m_Profiles; void update(const pa_card_info *info); CardEntry(PAInterface *iface) : Entry(iface) {} virtual void cycleSwitch(bool increment); virtual void setVolume(const int channel, const pa_volume_t volume) {} virtual void setMute(bool mute) {} virtual void move(uint32_t idx) {} virtual void kill() {} }; PAmix-1.6-10-gea4ab3b/include/painterface.hpp000066400000000000000000000077371322620461400206150ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include struct mainloop_lockguard { pa_threaded_mainloop *m; mainloop_lockguard(pa_threaded_mainloop *m); ~mainloop_lockguard(); }; class PAInterface; struct Entry; //define subscription masks #define PAI_SUBSCRIPTION_MASK_PEAK 0x1U #define PAI_SUBSCRIPTION_MASK_INFO 0x2U #define PAI_SUBSCRIPTION_MASK_OTHER 0x4U #define PAI_SUBSCRIPTION_MASK_CONNECTION_STATUS 0x128 typedef uint32_t pai_subscription_type_t; typedef void (*pai_subscription_cb)(PAInterface *, const pai_subscription_type_t); class PAInterface { private: bool m_Autospawn; const char *m_ContextName; pa_threaded_mainloop *m_Mainloop; pa_mainloop_api *m_MainloopApi; pa_context *m_Context; std::map> m_Sinks; std::map> m_Sources; std::map> m_SinkInputs; std::map> m_SourceOutputs; std::map> m_Cards; pai_subscription_cb m_Subscription_callback; private: static void signal_mainloop(void *interface); static void _updateSinks(PAInterface *interface); static void _updateSources(PAInterface *interface); static void _updateInputs(PAInterface *interface); static void _updateOutputs(PAInterface *interface); static void _updateCards(PAInterface *interface); static void _updatethread(pai_subscription_type_t paisubtype, pa_subscription_event_type_t type, PAInterface *interface); //member methods void updateSinks() { _updateSinks(this); } void updateSources() { _updateSources(this); } void updateInputs() { _updateInputs(this); } void updateOutputs() { _updateOutputs(this); } void updateCards() { _updateCards(this); } void notifySubscription(const pai_subscription_type_t); bool tryCreateConnection(); void cleanupPulseObjects(); public: PAInterface(const char *context_name); ~PAInterface(); inline pa_threaded_mainloop *getPAMainloop() { return m_Mainloop; } inline pa_context *getPAContext() { return m_Context; } bool connect(); bool isConnected(); void setAutospawn(bool as) { m_Autospawn = as; } bool getAutospawn() { return m_Autospawn; } void subscribe(pai_subscription_cb callback); std::mutex m_ModifyMutex; std::map> &getSinks() { return m_Sinks; } std::map> &getSources() { return m_Sources; } std::map> &getSinkInputs() { return m_SinkInputs; } std::map> &getSourceOutputs() { return m_SourceOutputs; } std::map> &getCards() { return m_Cards; } std::vector>> m_IEPairs; void createMonitorStreamForEntry(Entry *entry, int type); //PulseAudio API Callbacks //userptr points to current PAInterface instance static void cb_context_state(pa_context *context, void *interface); static void cb_context_drain_complete(pa_context *context, void *null); static void cb_success(pa_context *context, int success, void *interface); static void cb_subscription_event(pa_context *context, pa_subscription_event_type_t type, uint32_t idx, void *interface); static void cb_sink_info(pa_context *context, const pa_sink_info *info, int eol, void *interface); static void cb_source_info(pa_context *context, const pa_source_info *info, int eol, void *interface); static void cb_sink_input_info(pa_context *context, const pa_sink_input_info *info, int eol, void *interface); static void cb_source_output_info(pa_context *context, const pa_source_output_info *info, int eol, void *interface); static void cb_card_info(pa_context *context, const pa_card_info *info, int eol, void *interface); static void cb_read(pa_stream *stream, size_t nbytes, void *iepair); static void cb_stream_state(pa_stream *stream, void *entry); }; PAmix-1.6-10-gea4ab3b/include/pamix.hpp000066400000000000000000000023351322620461400174370ustar00rootroot00000000000000#pragma once #include <../config.hpp> #include #include "pamix_ui.hpp" struct UpdateData { bool redrawAll; UpdateData() = default; explicit UpdateData(bool redrawAll) { this->redrawAll = redrawAll; } }; #define DECAY_STEP 0.04 #define MAX_VOL 1.5 #ifdef FEAT_UNICODE #define SYM_VOLBAR L'\u25ae' //▮ #define SYM_ARROW "\u25b6 " //▶ #define SYM_MUTE "🔇" #define SYM_LOCK "🔒" #define SYM_SPACE L' ' #define FEAT_UNICODE_STRING std::wstring #define FEAT_UNICODE_MVADDNSTR(y, x, str, n) mvaddnwstr(y, x, str, n); #else #define SYM_VOLBAR '|' #define SYM_ARROW "> " #define SYM_MUTE "M" #define SYM_LOCK "L" #define SYM_SPACE ' ' #define FEAT_UNICODE_STRING std::string #define FEAT_UNICODE_MVADDNSTR(y, x, str, n) mvaddnstr(y, x, str, n); #endif void quit(); void signal_update(bool all, bool threaded = false); void set_volume(pamix_ui *ui, double pct); void add_volume(pamix_ui *ui, double pct); void cycle_switch(pamix_ui *ui, bool inc); void set_mute(pamix_ui *ui, bool mute); void toggle_mute(pamix_ui *ui); void set_lock(pamix_ui *ui, bool lock); void toggle_lock(pamix_ui *ui); void select_next(PAInterface *interface, bool precise); void select_previous(PAInterface *interface, bool precise); PAmix-1.6-10-gea4ab3b/include/pamix_functions.hpp000066400000000000000000000012071322620461400215240ustar00rootroot00000000000000#pragma once #include #include #include "pamix_ui.hpp" union argument_t { double d; int i; bool b; }; void pamix_setup(pamix_ui *interface); void pamix_quit(argument_t arg); void pamix_select_tab(argument_t arg); void pamix_select_next(argument_t arg); void pamix_select_prev(argument_t arg); void pamix_set_volume(argument_t arg); void pamix_add_volume(argument_t arg); void pamix_cycle_next(argument_t arg); void pamix_cycle_prev(argument_t arg); void pamix_toggle_lock(argument_t arg); void pamix_set_lock(argument_t arg); void pamix_toggle_mute(argument_t arg); void pamix_set_mute(argument_t arg); PAmix-1.6-10-gea4ab3b/include/pamix_ui.hpp000066400000000000000000000022111322620461400201250ustar00rootroot00000000000000#pragma once #include #include #include "entry.hpp" #include "painterface.hpp" class pamix_ui { private: std::map m_VolumeBarLineNums; std::map m_EntrySizes; unsigned m_NumDrawnEntries; std::mutex m_DrawMutex; public: unsigned m_SelectedEntry; unsigned m_SelectedChannel; unsigned m_NumSkippedEntries; std::map> *m_Entries; entry_type m_EntriesType; PAInterface *m_paInterface; public: explicit pamix_ui(PAInterface *paInterface); void reset(); void selectEntries(entry_type type); void redrawAll(); void redrawVolumeBars(); std::string getEntryDisplayName(Entry *entry); pamix_entry_iter_t getSelectedEntryIterator(); void selectNext(bool includeChannels); void selectPrevious(bool includeChannels); int getKeyInput(); private: void drawHeader() const; void drawVolumeBar(int y, int x, int width, double fill, double maxFill) const; unsigned int drawEntryControlMeters(const Entry *entry, unsigned int entryIndex, unsigned int lineNumber) const; void adjustDisplayedEntries(); void moveSelection(int delta, bool includeChannels); }; PAmix-1.6-10-gea4ab3b/include/volumeutil.hpp000066400000000000000000000002111322620461400205150ustar00rootroot00000000000000#pragma once #include #include pa_cvolume *volume_pct_delta(pa_cvolume *vol, int channel, double pctDelta); PAmix-1.6-10-gea4ab3b/man/000077500000000000000000000000001322620461400147355ustar00rootroot00000000000000PAmix-1.6-10-gea4ab3b/man/pamix.1.in000066400000000000000000000076561322620461400165600ustar00rootroot00000000000000.\" this is the manpage of the pamix pulseaudio ncurses mixer .TH pamix 1 "05 Sep 2016" "V${GIT_VERSION}" "pamix man page" .SH SYNOPSIS .\" Commandline arguments .B pamix .RB [\| \--version \|] .SH DESCRIPTION This is a pavucontrol inspired ncurses based pulseaudio mixer for the commandline .SH OPTIONS .SS "General options" .BR \--version print build version .SH CONFIGURATION pamix is configured using a file called pamix.conf inside the $XDG_CONFIG_HOME or $XDG_CONFIG_DIRS directories or their default values, should they not be set. .br $XDG_CONFIG_HOME will be preferred over $XDG_CONFIG_DIRS. .br .start .SH COMMANDS .PP PAmix conf files support the following commands: .br * set * bind * unbind * unbindall .PP characters after a ';' will be interpreted as comments and ignored .SH set .PP \fBSYNOPSIS:\fP set KEY=VALUE .PP set is used to set a variable. This is currently only relevant for the 'pulseaudio\_autospawn' and 'default\_tab' options. .SH bind .PP \fBSYNOPSIS:\fP bind KEYNAME MIXER\-COMMAND [ARGUMENT] .PP bind is used to bind a keyname to a mixer\-command. .br Some mixer\-commands require an argument. .br You can bind a keyname to multiple mixer\-commands. .SH unbind .PP \fBSYNOPSIS:\fP unbind KEYNAME .PP unbind will remove all bindings for the given keyname .SH unbindall .PP \fBSYNOPSIS:\fP unbindall .PP unbindall will remove all key bindings .SH PAMIX\-COMMANDS .PP Pamix\-Commands can be bound to keys using the bind command and are used to interact with pamix. .br The following pamix\-commands are currently supported: * quit * select\-tab * select\-next * select\-prev * set\-volume * add\-volume * cycle\-next * cycle\-prev * toggle\-lock * set\-lock * toggle\-mute * set\-mute .SH quit .PP quit will cause PAmix to exit and takes no arguments. .SH select\-tab .PP select\-tab will select one of the following tabs: Output Devices, Input Devices, Playback, Recording, Cards .br select\-tab takes the number of the tab to switch to starting at 0 in the order mentioned. .SH select\-next and select\-prev .PP these commands are given the optional argument 'channel' they will select the next and previous channels. if no argument is given they will select the next and previous entry in the displayed tab. .SH set\-volume .PP this command takes the targetvalue in form of a double as an argument. .br depending on weather channels are locked, this command will set the volume of the selected entry/channel to the targetvalue given in the argument. .br \fIExample:\fP bind 0 set\-volume 1.0 \fI; this will set the volume to 100%\fP .SH add\-volume .PP this command takes a deltavalue in form of a double as an argument. .br the deltavalue can be negative \fIExample:\fP bind h add\-volume \-0.05 \fI; this will reduce the volume by 5%\fP .SH cycle\-next and cycle\-prev .PP these commands will change the device or port of the currently selected entry. .br they dont take any arguments. .SH toggle\-lock .PP this command toggles weather channels should be locked together for the currently selected entry .br and takes no arguments. .SH set\-lock .PP this command takes either '0' or '1' as an argument and sets the channel\-lock like the toggle\-lock mixer\-command. .SH toggle\-mute .PP toggles weather the currently selected entry is muted .br and takes no arguments. .SH set\-mute .PP works like the set\-lock mixer\-command, but sets weather the currently selected entry is muted or not .stop .SH DEFAULT CONFIGURATION pamix does not autospawn pulseaudio by default Keybindings: .br F1 show Playback tab .br F2 show Recording tab .br F3 show Output devices tab .br F4 show Input devices tab .br F4 show Cards tab .br 0-9 set volume to percentage (10%-100%) .br j/down select next channel .br J select next entry .br k/up select previous channel .br K select previous entry .br h/left decrease volume .br h/right increase volume .br c un/lock channels .br s/S select next/previous device/port PAmix-1.6-10-gea4ab3b/pamix.conf000066400000000000000000000026441322620461400161550ustar00rootroot00000000000000; This is a sample configuration file for pamix (https://github.com/patroclos/PAmix) implementing the default configuration ; SETTING OPTIONS ; set this to true to spawn a new pulseaudio daemon if it isn't already running set pulseaudio_autospawn=false ; set which tab should be selected by default (tab indexes are the same as for select-tab) set default_tab=2 ; BINDING KEYS ; see `man keyname` for reference for special keynames/combinations bind q quit ; Navigation ; select-tab ids: ; 0 Output Devices ; 1 Input Devices ; 2 Playback ; 3 Recording ; 4 Cards bind KEY_F(1) select-tab 2 bind KEY_F(2) select-tab 3 bind KEY_F(3) select-tab 0 bind KEY_F(4) select-tab 1 bind KEY_F(5) select-tab 4 bind j select-next channel bind KEY_DOWN select-next channel bind J select-next bind k select-prev channel bind KEY_UP select-prev channel bind K select-prev ; Volume Control bind h add-volume -0.05 bind KEY_LEFT add-volume -0.05 bind l add-volume 0.05 bind KEY_RIGHT add-volume 0.05 bind 1 set-volume 0.1 bind 2 set-volume 0.2 bind 3 set-volume 0.3 bind 4 set-volume 0.4 bind 5 set-volume 0.5 bind 6 set-volume 0.6 bind 7 set-volume 0.7 bind 8 set-volume 0.8 bind 9 set-volume 0.9 bind 0 set-volume 1.0 ; cycle-next/prev will select the next/previous device or port ; for the currently selected entry bind s cycle-next bind S cycle-prev ; toggle-lock toggles weather channels are locked together bind c toggle-lock bind m toggle-mute PAmix-1.6-10-gea4ab3b/src/000077500000000000000000000000001322620461400147515ustar00rootroot00000000000000PAmix-1.6-10-gea4ab3b/src/cardentry.cpp000066400000000000000000000021751322620461400174550ustar00rootroot00000000000000#include void CardEntry::update(const pa_card_info *info) { m_Name = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_DESCRIPTION); m_Index = info->index; m_Lock = false; m_Kill = false; m_Meter = false; m_PAVolume.channels = 0; m_Profiles.clear(); m_Profile = -1; for (unsigned i = 0; i < info->n_profiles; i++) { m_Profiles.emplace_back(info->profiles2[i]); if (info->profiles2[i] == info->active_profile2) m_Profile = i; } } void CardEntry::cycleSwitch(bool increment) { mainloop_lockguard lg(interface->getPAMainloop()); if (m_Profiles.empty()) return; m_Profile = (m_Profile + (increment ? 1 : -1)) % (int) m_Profiles.size(); if (m_Profile < 0) m_Profile += m_Profiles.size(); pa_operation *op = pa_context_set_card_profile_by_index(interface->getPAContext(), m_Index, m_Profiles[m_Profile].name.c_str(), &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } PAmix-1.6-10-gea4ab3b/src/configuration.cpp000066400000000000000000000125471322620461400203350ustar00rootroot00000000000000#include #include std::vector> Configuration::m_Keynames; inline bool startsWithSubstring(const std::string &a, const std::string &b, std::string &sub) { bool rv = a.substr(0, b.length()) == b; if (rv) sub = a.substr(b.length(), a.length() - b.length()); return rv; } int Configuration::getKeycode(const std::string &name) { if (m_Keynames.empty()) { for (int i = -1; i < KEY_MAX; i++) { const char *r = keyname(i); if (r) m_Keynames.emplace_back(i, r); } } for (unsigned i = 0; i < m_Keynames.size(); i++) if (name == m_Keynames[i].second) return m_Keynames[i].first; return -1; } bool Configuration::pressKey(int code) { if (!m_Bindings.count(code)) return false; for (unsigned i = 0; i < m_Bindings[code].size(); i++) { keybind_t kb = m_Bindings[code][i]; kb.m_Function(kb.m_Argument); } return true; } void Configuration::set(const std::string &key, const std::string &value) { m_Variables[key] = value; } void Configuration::unset(const std::string &key) { m_Variables.erase(key); } void Configuration::bind(const std::string &key, const std::string &cmd, const std::string &args) { int code = getKeycode(key); if (code == -1) { fprintf(stderr, "Invalid keyname: %s\n", key.c_str()); return; } keybind_t kbind; if (!cmd.compare("quit")) kbind.m_Function = &pamix_quit; else if (!cmd.compare("select-tab")) { int tab = 0; sscanf(args.c_str(), "%d", &tab); kbind.m_Function = &pamix_select_tab; kbind.m_Argument.i = tab; } else if (!cmd.compare("select-next")) { bool precise = !args.compare("channel"); kbind.m_Function = &pamix_select_next; kbind.m_Argument.b = precise; } else if (!cmd.compare("select-prev")) { bool precise = !args.compare("channel"); kbind.m_Function = &pamix_select_prev; kbind.m_Argument.b = precise; } else if (!cmd.compare("set-volume")) { double value = 0.0; sscanf(args.c_str(), "%lf", &value); kbind.m_Function = &pamix_set_volume; kbind.m_Argument.d = value; } else if (!cmd.compare("add-volume")) { double delta = 0.0; sscanf(args.c_str(), "%lf", &delta); kbind.m_Function = &pamix_add_volume; kbind.m_Argument.d = delta; } else if (!cmd.compare("cycle-next")) kbind.m_Function = &pamix_cycle_next; else if (!cmd.compare("cycle-prev")) kbind.m_Function = &pamix_cycle_prev; else if (!cmd.compare("toggle-lock")) kbind.m_Function = &pamix_toggle_lock; else if (!cmd.compare("set-lock")) { bool lock = args.compare("0") != 0; kbind.m_Function = &pamix_set_lock; kbind.m_Argument.b = lock; } else if (!cmd.compare("toggle-mute")) kbind.m_Function = &pamix_toggle_mute; else if (!cmd.compare("set-mute")) { bool mute = args.compare("0") != 0; kbind.m_Function = &pamix_set_mute; kbind.m_Argument.b = mute; } m_Bindings[code].emplace_back(kbind); } void Configuration::unbind(const std::string &key) { int code = getKeycode(key); if (code == -1) { fprintf(stderr, "Invalid keyname: %s\n", key.c_str()); return; } m_Bindings.erase(code); } void Configuration::unbindall() { m_Bindings.clear(); } bool Configuration::loadFile(Configuration *config, const std::string &path) { FILE *file = fopen(path.c_str(), "r"); if (!file) return false; fseek(file, 0, SEEK_END); unsigned long length = ftell(file); if (length == LLONG_MAX) return false; char *data = new char[length + 1]; std::memset(data, 0, length + 1); fseek(file, 0, SEEK_SET); unsigned read = fread(data, 1, length, file); if (read != length) return false; fclose(file); std::istringstream stream(data); std::string line; /* * parse text line by line * syntax: * ; comments out last line * floats look like 0.0 * * couple of example lines: * bind ^L set_volume(1.5, other args) * set autospawn_pulse=1 * */ while (std::getline(stream, line)) { // clip comments size_t cpos = line.find(';'); if (cpos != std::string::npos) line = line.substr(0, cpos); // set x=y std::string sub; if (startsWithSubstring(line, "set ", sub)) { size_t pos = sub.find('='); // TODO error message if pos==npos if (pos == std::string::npos) continue; config->set(sub.substr(0, pos), sub.substr(pos + 1, sub.length() - pos - 1)); } else if (startsWithSubstring(line, "unbind ", sub)) { config->unbind(sub); } else if (startsWithSubstring(line, "unbind-all", sub)) config->unbindall(); else if (startsWithSubstring(line, "bind ", sub)) { size_t pos = sub.find(' '); if (pos == std::string::npos) continue; std::string key = sub.substr(0, pos); std::string cmd = sub.substr(pos + 1, sub.length() - pos - 1); // seperate command from arguments std::string args; size_t spos = cmd.find(' '); if (spos != std::string::npos) { args = cmd.substr(spos + 1, cmd.length() - spos - 1); cmd = cmd.substr(0, spos); } config->bind(key, cmd, args); continue; } } delete[] data; return true; } bool Configuration::has(const std::string &key) { return m_Variables.count(key); } std::string Configuration::getString(const std::string &key) { if (has(key)) return m_Variables[key]; return ""; } int Configuration::getInt(const std::string &key) { if (!has(key)) return 0; int rv = 0; sscanf(m_Variables[key].c_str(), "%d", &rv); return rv; } bool Configuration::getBool(const std::string &key) { if (!has(key)) return false; return m_Variables[key].compare("false"); } PAmix-1.6-10-gea4ab3b/src/entry.cpp000066400000000000000000000012101322620461400166100ustar00rootroot00000000000000#include Entry::Entry(PAInterface *iface) : interface(iface) { } Entry::~Entry() { if (m_Monitor && interface->isConnected()) { mainloop_lockguard mlg(interface->getPAMainloop()); pa_stream_set_state_callback(m_Monitor, &PAInterface::cb_stream_state, nullptr); if (PA_STREAM_IS_GOOD(pa_stream_get_state(m_Monitor))) { pa_stream_disconnect(m_Monitor); } pa_stream_unref(m_Monitor); m_Monitor = nullptr; } } void Entry::addVolume(const int channel, const double deltaPct) { volume_pct_delta(&m_PAVolume, channel, deltaPct); setVolume(channel, m_Lock ? pa_cvolume_avg(&m_PAVolume) : m_PAVolume.values[channel]); } PAmix-1.6-10-gea4ab3b/src/painterface.cpp000066400000000000000000000332131322620461400177400ustar00rootroot00000000000000#include mainloop_lockguard::mainloop_lockguard(pa_threaded_mainloop *m) : m(m) { pa_threaded_mainloop_lock(m); } mainloop_lockguard::~mainloop_lockguard() { pa_threaded_mainloop_unlock(m); } PAInterface::PAInterface(const char *context_name) : m_Autospawn(false), m_ContextName(context_name), m_Mainloop(nullptr), m_MainloopApi(nullptr), m_Context() { } PAInterface::~PAInterface() { cleanupPulseObjects(); } void PAInterface::signal_mainloop(void *interface) { pa_threaded_mainloop_signal(((PAInterface *) interface)->m_Mainloop, 0); } void PAInterface::cb_context_state(pa_context *context, void *userdata) { auto interface = static_cast(userdata); if (PA_CONTEXT_IS_GOOD(pa_context_get_state(context))) PAInterface::signal_mainloop(interface); else { interface->m_Sinks.clear(); interface->m_SinkInputs.clear(); interface->m_Sources.clear(); interface->m_SourceOutputs.clear(); interface->m_Cards.clear(); } interface->notifySubscription(PAI_SUBSCRIPTION_MASK_CONNECTION_STATUS); } void PAInterface::cb_context_drain_complete(pa_context *context, void *) { pa_context_disconnect(context); } void PAInterface::cb_success(pa_context *, int, void *interface) { PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::_updatethread(pai_subscription_type_t paisubtype, pa_subscription_event_type_t type, PAInterface *interface) { if (paisubtype == PAI_SUBSCRIPTION_MASK_INFO) { if (pa_subscription_match_flags(PA_SUBSCRIPTION_MASK_SINK, type)) PAInterface::_updateSinks(interface); else if (pa_subscription_match_flags(PA_SUBSCRIPTION_MASK_SOURCE, type)) PAInterface::_updateSources(interface); else if (pa_subscription_match_flags(PA_SUBSCRIPTION_MASK_SINK_INPUT, type)) PAInterface::_updateInputs(interface); else if (pa_subscription_match_flags(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, type)) PAInterface::_updateOutputs(interface); else if (pa_subscription_match_flags(PA_SUBSCRIPTION_MASK_CARD, type)) PAInterface::_updateCards(interface); } interface->notifySubscription(paisubtype); } void PAInterface::cb_subscription_event(pa_context *, pa_subscription_event_type_t type, uint32_t, void *interface) { pai_subscription_type_t paisubtype; if (pa_subscription_match_flags( PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT | PA_SUBSCRIPTION_MASK_CARD, type)) { paisubtype = PAI_SUBSCRIPTION_MASK_INFO; } else { paisubtype = PAI_SUBSCRIPTION_MASK_OTHER; } std::thread updthread(_updatethread, paisubtype, type, (PAInterface *) interface); updthread.detach(); } void PAInterface::cb_sink_info(pa_context *, const pa_sink_info *info, int eol, void *interface) { if (!eol) { std::map> &map = ((PAInterface *) interface)->m_Sinks; std::lock_guard lg(((PAInterface *) interface)->m_ModifyMutex); if (!map.count(info->index) || !map[info->index]) map[info->index] = std::unique_ptr(new SinkEntry((PAInterface *) interface)); map[info->index]->update(info); } else PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::cb_source_info(pa_context *, const pa_source_info *info, int eol, void *interface) { if (!eol) { std::map> &map = ((PAInterface *) interface)->m_Sources; std::lock_guard lg(((PAInterface *) interface)->m_ModifyMutex); if (!map.count(info->index) || !map[info->index]) map[info->index] = std::unique_ptr(new SourceEntry((PAInterface *) interface)); map[info->index]->update(info); } else PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::cb_sink_input_info(pa_context *, const pa_sink_input_info *info, int eol, void *interface) { if (!eol) { std::map> &map = ((PAInterface *) interface)->m_SinkInputs; std::lock_guard lg(((PAInterface *) interface)->m_ModifyMutex); if (!map.count(info->index) || !map[info->index]) map[info->index] = std::unique_ptr(new SinkInputEntry((PAInterface *) interface)); map[info->index]->update(info); } else PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::cb_source_output_info(pa_context *, const pa_source_output_info *info, int eol, void *interface) { if (!eol) { const char *appid = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID); if (appid) { if (strcmp(appid, "pamix") == 0) { const pa_cvolume *cv = &info->volume; if (cv->channels == 1 && cv->values[0] != PA_VOLUME_NORM) { auto *iface = (PAInterface *) interface; pa_cvolume cvol{}; pa_cvolume_set(&cvol, 1, PA_VOLUME_NORM); pa_context_set_source_output_volume(iface->getPAContext(), info->index, &cvol, nullptr, nullptr); } return; } if (strcmp(appid, "org.PulseAudio.pavucontrol") != 0) return; } std::map> &map = ((PAInterface *) interface)->m_SourceOutputs; std::lock_guard lg(((PAInterface *) interface)->m_ModifyMutex); if (!map.count(info->index) || !map[info->index]) map[info->index] = std::unique_ptr(new SourceOutputEntry((PAInterface *) interface)); map[info->index]->update(info); } else PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::cb_card_info(pa_context *, const pa_card_info *info, int eol, void *interface) { if (!eol) { std::map> &map = ((PAInterface *) interface)->m_Cards; std::lock_guard lg(((PAInterface *) interface)->m_ModifyMutex); if (!map.count(info->index) || !map[info->index]) map[info->index] = std::unique_ptr(new CardEntry((PAInterface *) interface)); map[info->index]->update(info); } else PAInterface::signal_mainloop((PAInterface *) interface); } void PAInterface::cb_read(pa_stream *stream, size_t nbytes, void *iepair) { auto *pair = (std::pair *) (iepair); if (!pair->second || !pair->first->m_Context) return; const void *data; float v; if (pa_stream_peek(stream, &data, &nbytes) < 0) return; if (!data) { if (nbytes) pa_stream_drop(stream); return; } assert(nbytes > 0); assert(nbytes % sizeof(float) == 0); v = ((const float *) data)[nbytes / sizeof(float) - 1]; pa_stream_drop(stream); if (v < 0) v = 0; else if (v > 1) v = 1; pair->second->m_Peak = v; pair->first->notifySubscription(PAI_SUBSCRIPTION_MASK_PEAK); } void PAInterface::cb_stream_state(pa_stream *stream, void *entry) { if (!entry) return; pa_stream_state_t state = pa_stream_get_state(stream); if (state == PA_STREAM_TERMINATED || state == PA_STREAM_FAILED) { ((Entry *) entry)->m_Monitor = nullptr; } } void __updateEntries(PAInterface *interface, std::map> &map, entry_type entrytype) { mainloop_lockguard lg(interface->getPAMainloop()); { std::lock_guard mlg(interface->m_ModifyMutex); for (auto it = map.begin(); it != map.end();) if (it->second) it++->second->m_Kill = true; else it = map.erase(it); } pa_operation *infooper = nullptr; switch (entrytype) { case ENTRY_SINK: infooper = pa_context_get_sink_info_list(interface->getPAContext(), &PAInterface::cb_sink_info, interface); break; case ENTRY_SOURCE: infooper = pa_context_get_source_info_list(interface->getPAContext(), &PAInterface::cb_source_info, interface); break; case ENTRY_SINKINPUT: infooper = pa_context_get_sink_input_info_list(interface->getPAContext(), &PAInterface::cb_sink_input_info, interface); break; case ENTRY_SOURCEOUTPUT: infooper = pa_context_get_source_output_info_list(interface->getPAContext(), &PAInterface::cb_source_output_info, interface); break; case ENTRY_CARDS: infooper = pa_context_get_card_info_list(interface->getPAContext(), &PAInterface::cb_card_info, interface); break; default: return; } assert(infooper); while (pa_operation_get_state(infooper) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(infooper); std::lock_guard modlg(interface->m_ModifyMutex); for (auto it = map.begin(); it != map.end();) { if (!it->second) { it = map.erase(it); continue; } if (it->second->m_Kill) { for (auto pairIterator = interface->m_IEPairs.begin(); pairIterator != interface->m_IEPairs.end();) { if ((*pairIterator)->second == it->second.get()) { (*pairIterator)->second = nullptr; break; } pairIterator++; } it = map.erase(it); } else { if (!it->second->m_Monitor) interface->createMonitorStreamForEntry(it->second.get(), entrytype); it++; } } } void PAInterface::_updateSinks(PAInterface *interface) { __updateEntries(interface, interface->getSinks(), ENTRY_SINK); } void PAInterface::_updateSources(PAInterface *interface) { __updateEntries(interface, interface->getSources(), ENTRY_SOURCE); } void PAInterface::_updateInputs(PAInterface *interface) { __updateEntries(interface, interface->getSinkInputs(), ENTRY_SINKINPUT); } void PAInterface::_updateOutputs(PAInterface *interface) { __updateEntries(interface, interface->getSourceOutputs(), ENTRY_SOURCEOUTPUT); } void PAInterface::_updateCards(PAInterface *interface) { __updateEntries(interface, interface->getCards(), ENTRY_CARDS); } bool PAInterface::tryCreateConnection() { m_Mainloop = pa_threaded_mainloop_new(); assert(m_Mainloop); m_MainloopApi = pa_threaded_mainloop_get_api(m_Mainloop); pa_proplist *plist = pa_proplist_new(); pa_proplist_sets(plist, PA_PROP_APPLICATION_ID, m_ContextName); pa_proplist_sets(plist, PA_PROP_APPLICATION_NAME, m_ContextName); m_Context = pa_context_new_with_proplist(m_MainloopApi, nullptr, plist); pa_proplist_free(plist); assert(m_Context); pa_context_set_state_callback(m_Context, &PAInterface::cb_context_state, this); pa_threaded_mainloop_lock(m_Mainloop); if (pa_threaded_mainloop_start(m_Mainloop)) { pa_threaded_mainloop_unlock(m_Mainloop); return false; } pa_context_flags flags = m_Autospawn ? PA_CONTEXT_NOFLAGS : PA_CONTEXT_NOAUTOSPAWN; if (pa_context_connect(m_Context, nullptr, flags, nullptr)) { pa_threaded_mainloop_unlock(m_Mainloop); return false; } for (;;) { pa_context_state_t state = pa_context_get_state(m_Context); assert(PA_CONTEXT_IS_GOOD(state)); if (state == PA_CONTEXT_READY) break; pa_threaded_mainloop_wait(m_Mainloop); } pa_context_set_subscribe_callback(m_Context, &PAInterface::cb_subscription_event, this); pa_operation *subscrop = pa_context_subscribe(m_Context, PA_SUBSCRIPTION_MASK_ALL, &PAInterface::cb_success, this); while (pa_operation_get_state(subscrop) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(m_Mainloop); pa_operation_unref(subscrop); pa_threaded_mainloop_unlock(m_Mainloop); updateSinks(); updateSources(); updateInputs(); updateOutputs(); updateCards(); return true; } bool PAInterface::connect() { bool success = tryCreateConnection(); if (!success) cleanupPulseObjects(); return success; } void PAInterface::cleanupPulseObjects() { m_Sinks.clear(); m_Sources.clear(); m_SinkInputs.clear(); m_SourceOutputs.clear(); m_Cards.clear(); if (m_Context) { pa_operation *o; if (!(o = pa_context_drain(m_Context, &PAInterface::cb_context_drain_complete, nullptr))) pa_context_disconnect(m_Context); else { pa_operation_unref(o); } m_Context = nullptr; } if (m_Mainloop) { //pa_threaded_mainloop_unlock(m_Mainloop); //pa_threaded_mainloop_stop(m_Mainloop); pa_threaded_mainloop_free(m_Mainloop); m_Mainloop = nullptr; } m_MainloopApi = nullptr; } bool PAInterface::isConnected() { return m_Context ? pa_context_get_state(m_Context) == PA_CONTEXT_READY : false; } pa_stream *_createMonitor(PAInterface *interface, uint32_t source, Entry *entry, uint32_t stream) { pa_stream *s; char t[16]; pa_buffer_attr attr{}; pa_sample_spec ss{}; pa_stream_flags_t flags; ss.channels = 1; ss.format = PA_SAMPLE_FLOAT32; ss.rate = 25; memset(&attr, 0, sizeof(attr)); attr.fragsize = sizeof(float); attr.maxlength = (uint32_t) -1; snprintf(t, sizeof(t), "%u", source); s = pa_stream_new(interface->getPAContext(), "PeakDetect", &ss, nullptr); if (!s) return nullptr; if (stream != (uint32_t) -1) pa_stream_set_monitor_stream(s, stream); auto *pair = new std::pair(); pair->first = interface; pair->second = entry; interface->m_IEPairs.emplace_back(std::unique_ptr>(pair)); pa_stream_set_read_callback(s, &PAInterface::cb_read, pair); pa_stream_set_state_callback(s, &PAInterface::cb_stream_state, entry); flags = (pa_stream_flags_t) (PA_STREAM_DONT_MOVE | PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY); if (pa_stream_connect_record(s, (stream != (uint32_t) -1) ? nullptr : t, &attr, flags) < 0) { pa_stream_unref(s); return nullptr; } return s; } void PAInterface::createMonitorStreamForEntry(Entry *entry, int type) { if (entry->m_Monitor) { pa_stream_disconnect(entry->m_Monitor); entry->m_Monitor = nullptr; } if (type == ENTRY_SINKINPUT) { uint32_t dev = ((SinkInputEntry *) entry)->m_Device; if (m_Sinks.count(dev) && m_Sinks[dev]) entry->m_Monitor = _createMonitor(this, m_Sinks[dev]->m_Index, entry, entry->m_Index); } else if (type != ENTRY_CARDS) { entry->m_Monitor = _createMonitor(this, entry->m_MonitorIndex, entry, (uint32_t) -1); } } void PAInterface::subscribe(pai_subscription_cb callback) { m_Subscription_callback = callback; } void PAInterface::notifySubscription(const pai_subscription_type_t type) { if (m_Subscription_callback) { m_Subscription_callback(this, type); } } PAmix-1.6-10-gea4ab3b/src/pamix.cpp000066400000000000000000000145521322620461400166020ustar00rootroot00000000000000#include #include #include #include #include #include // GLOBAL VARIABLES Configuration configuration; bool running = true; // sync main and callback threads std::mutex updMutex; std::condition_variable cv; std::queue updateDataQ; void quit() { running = false; signal_update(false); } void __signal_update(bool all) { { std::lock_guard lk(updMutex); updateDataQ.push(UpdateData(all)); } cv.notify_one(); } void signal_update(bool all, bool threaded) { if (threaded) std::thread(__signal_update, all).detach(); else __signal_update(all); } void set_volume(pamix_ui *ui, double pct) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->setVolume(it->second->m_Lock ? -1 : (int) ui->m_SelectedChannel, (pa_volume_t) (PA_VOLUME_NORM * pct)); } void add_volume(pamix_ui *ui, double pct) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->addVolume(it->second->m_Lock ? -1 : (int) ui->m_SelectedChannel, pct); } void cycle_switch(pamix_ui *ui, bool increment) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->cycleSwitch(increment); } void set_mute(pamix_ui *ui, bool mute) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->setMute(mute); } void toggle_mute(pamix_ui *ui) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->setMute(!it->second->m_Mute); } void set_lock(pamix_ui *ui, bool lock) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->m_Lock = lock; } void toggle_lock(pamix_ui *ui) { auto it = ui->getSelectedEntryIterator(); if (it != ui->m_Entries->end()) it->second->m_Lock = !it->second->m_Lock; } void inputThread(pamix_ui *ui) { while (running) { int ch = ui->getKeyInput(); bool isValidKey = ch != ERR && ch != KEY_RESIZE && ch != KEY_MOUSE; //if (isValidKey && ui->m_paInterface->isConnected()) { if (isValidKey) { configuration.pressKey(ch); } usleep(2000); } } void pai_subscription(PAInterface *, pai_subscription_type_t type) { bool updAll = (type & PAI_SUBSCRIPTION_MASK_INFO) != 0 || (type & PAI_SUBSCRIPTION_MASK_CONNECTION_STATUS) != 0; signal_update(updAll); } void sig_handle_resize(int) { endwin(); refresh(); signal_update(true, true); } void init_colors() { start_color(); init_pair(1, COLOR_GREEN, 0); init_pair(2, COLOR_YELLOW, 0); init_pair(3, COLOR_RED, 0); } void sig_handle(int) { endwin(); } void loadConfiguration() { char *home = getenv("HOME"); char *xdg_config_home = getenv("XDG_CONFIG_HOME"); std::string path; path = xdg_config_home ? xdg_config_home : std::string(home) += "/.config"; path += "/pamix.conf"; if (Configuration::loadFile(&configuration, path)) return; char *xdg_config_dirs = getenv("XDG_CONFIG_DIRS"); path = xdg_config_dirs ? xdg_config_dirs : "/etc/xdg"; path += "/pamix.conf"; size_t cpos = path.find(':'); while (cpos != std::string::npos) { if (Configuration::loadFile(&configuration, path.substr(0, cpos))) return; path = path.substr(cpos + 1, path.length() - cpos - 1); cpos = path.find(':'); } if (Configuration::loadFile(&configuration, path)) return; // if config file cant be loaded, use default bindings configuration.bind("q", "quit"); configuration.bind("KEY_F(1)", "select-tab", "2"); configuration.bind("KEY_F(2)", "select-tab", "3"); configuration.bind("KEY_F(3)", "select-tab", "0"); configuration.bind("KEY_F(4)", "select-tab", "1"); configuration.bind("j", "select-next", "channel"); configuration.bind("KEY_DOWN", "select-next", "channel"); configuration.bind("J", "select-next"); configuration.bind("k", "select-prev", "channel"); configuration.bind("KEY_UP", "select-prev", "channel"); configuration.bind("K", "select-prev"); configuration.bind("h", "add-volume", "-0.05"); configuration.bind("KEY_LEFT", "add-volume", "-0.05"); configuration.bind("l", "add-volume", "0.05"); configuration.bind("KEY_RIGHT", "add-volume", "0.05"); configuration.bind("s", "cycle-next"); configuration.bind("S", "cycle-prev"); configuration.bind("c", "toggle-lock"); configuration.bind("m", "toggle-mute"); configuration.bind("1", "set-volume", "0.1"); configuration.bind("2", "set-volume", "0.2"); configuration.bind("3", "set-volume", "0.3"); configuration.bind("4", "set-volume", "0.4"); configuration.bind("5", "set-volume", "0.5"); configuration.bind("6", "set-volume", "0.6"); configuration.bind("7", "set-volume", "0.7"); configuration.bind("8", "set-volume", "0.8"); configuration.bind("9", "set-volume", "0.9"); configuration.bind("0", "set-volume", "1.0"); } void interfaceReconnectThread(PAInterface *interface) { while (running) { if (!interface->isConnected()) { interface->connect(); signal_update(true); } sleep(5); } } void handleArguments(int argc, char **argv) { for (int i = 1; i < argc; i++) { std::string arg = argv[i]; if (arg == "--version") { printf("PAmix v%s\n", GIT_VERSION); exit(0); } } } int main(int argc, char **argv) { handleArguments(argc, argv); loadConfiguration(); setlocale(LC_ALL, ""); initscr(); init_colors(); nodelay(stdscr, true); curs_set(0); keypad(stdscr, true); meta(stdscr, true); noecho(); signal(SIGABRT, sig_handle); signal(SIGSEGV, sig_handle); signal(SIGWINCH, sig_handle_resize); PAInterface pai("pamix"); pamix_ui pamixUi(&pai); if (configuration.has(CONFIGURATION_AUTOSPAWN_PULSE)) pai.setAutospawn(configuration.getBool(CONFIGURATION_AUTOSPAWN_PULSE)); entry_type initialEntryType = ENTRY_SINKINPUT; if (configuration.has(CONFIGURATION_DEFAULT_TAB)) { int value = configuration.getInt(CONFIGURATION_DEFAULT_TAB); if (value >= 0 && value < ENTRY_COUNT) initialEntryType = (entry_type) value; } pamixUi.selectEntries(initialEntryType); pai.subscribe(&pai_subscription); pai.connect(); pamix_setup(&pamixUi); pamixUi.redrawAll(); std::thread(&interfaceReconnectThread, &pai).detach(); std::thread inputT(inputThread, &pamixUi); inputT.detach(); while (running) { std::unique_lock lk(updMutex); cv.wait(lk, [] { return !updateDataQ.empty(); }); if (updateDataQ.front().redrawAll) pamixUi.redrawAll(); else pamixUi.redrawVolumeBars(); updateDataQ.pop(); } endwin(); return 0; } PAmix-1.6-10-gea4ab3b/src/pamix_functions.cpp000066400000000000000000000022561322620461400206700ustar00rootroot00000000000000#include static pamix_ui *pamixUi; inline bool isConnected() { return pamixUi->m_paInterface->isConnected(); } void pamix_setup(pamix_ui *ui) { pamixUi = ui; } void pamix_quit(argument_t arg) { quit(); } void pamix_select_tab(argument_t arg) { pamixUi->selectEntries(static_cast(arg.i)); signal_update(true); } void pamix_select_next(argument_t arg) { pamixUi->selectNext(arg.b); signal_update(true); } void pamix_select_prev(argument_t arg) { pamixUi->selectPrevious(arg.b); signal_update(true); } void pamix_set_volume(argument_t arg) { set_volume(pamixUi, arg.d); } void pamix_add_volume(argument_t arg) { add_volume(pamixUi, arg.d); } void pamix_cycle_next(argument_t arg) { cycle_switch(pamixUi, true); } void pamix_cycle_prev(argument_t arg) { cycle_switch(pamixUi, false); } void pamix_toggle_lock(argument_t arg) { toggle_lock(pamixUi); signal_update(true); } void pamix_set_lock(argument_t arg) { set_lock(pamixUi, arg.b); signal_update(true); } void pamix_toggle_mute(argument_t arg) { toggle_mute(pamixUi); signal_update(true); } void pamix_set_mute(argument_t arg) { set_mute(pamixUi, arg.b); signal_update(true); } PAmix-1.6-10-gea4ab3b/src/pamix_ui.cpp000066400000000000000000000235471322620461400173030ustar00rootroot00000000000000#include #include void pamix_ui::reset() { m_Entries = nullptr; m_VolumeBarLineNums.clear(); m_EntrySizes.clear(); m_NumDrawnEntries = 0; m_NumSkippedEntries = 0; m_SelectedEntry = m_SelectedChannel = 0; } void pamix_ui::drawVolumeBar(int y, int x, int width, double fill, double maxFill) const { int segments = width - 2; if (segments <= 0) return; if (fill < 0) fill = 0; else if (fill > maxFill) fill = maxFill; auto filled = (unsigned) (fill / maxFill * (double) segments); if (filled > segments) filled = (unsigned) segments; mvaddstr(y, x++, "["); mvaddstr(y, x + segments, "]"); auto indexColorA = (int) (segments * ((double) 1 / 3)); auto indexColorB = (int) (segments * ((double) 2 / 3)); FEAT_UNICODE_STRING meter; meter.append(filled, SYM_VOLBAR); meter.append((unsigned) segments - filled, SYM_SPACE); attron(COLOR_PAIR(1)); FEAT_UNICODE_MVADDNSTR(y, x, meter.c_str(), indexColorA); attroff(COLOR_PAIR(1)); attron(COLOR_PAIR(2)); FEAT_UNICODE_MVADDNSTR(y, x + indexColorA, meter.c_str() + indexColorA, indexColorB - indexColorA); attroff(COLOR_PAIR(2)); attron(COLOR_PAIR(3)); FEAT_UNICODE_MVADDNSTR(y, x + indexColorB, meter.c_str() + indexColorB, segments - indexColorB); attroff(COLOR_PAIR(3)); } void string_maxlen_abs(std::string &str, unsigned max) { if (str.length() > max) { str = str.substr(0, max - 2); str.append(".."); } } void string_maxlen_pct(std::string &str, double maxPct) { string_maxlen_abs(str, (unsigned) (COLS * maxPct)); } void pamix_ui::redrawAll() { std::lock_guard lockGuard(m_DrawMutex); if (!m_paInterface->isConnected()) { clear(); mvprintw(1, 1, "No connection to pulseaudio yet"); refresh(); return; } clear(); drawHeader(); unsigned lineNumber = 2; unsigned entryIndex = 0; auto entryIter = m_Entries->begin(); for (auto end = m_Entries->end(); entryIter != end; entryIter++, entryIndex++) m_EntrySizes[entryIndex] = entryIter->second->m_Lock ? (char) 1 : entryIter->second->m_PAVolume.channels; entryIndex = m_NumSkippedEntries; entryIter = std::next(m_Entries->begin(), entryIndex); for (auto end = m_Entries->end(); entryIter != end; entryIter++, entryIndex++) { Entry *entry = entryIter->second.get(); std::string applicationName = entryIter->second ? entry->m_Name : ""; pa_volume_t averageVolume = pa_cvolume_avg(&entry->m_PAVolume); char numChannels = entry->m_Lock ? (char) 1 : entry->m_PAVolume.channels; bool isSelectedEntry = entryIndex == m_SelectedEntry; if (lineNumber + numChannels + 2 > (unsigned) LINES) break; lineNumber = drawEntryControlMeters(entry, entryIndex, lineNumber); double volumePeak = entry->m_Peak; m_VolumeBarLineNums[entryIter->first] = lineNumber; if (entry->m_Meter) drawVolumeBar(lineNumber++, 1, COLS - 2, volumePeak, 1.0); string_maxlen_pct(applicationName, 0.4); if (isSelectedEntry) attron(A_STANDOUT); mvprintw(lineNumber++, 1, applicationName.c_str()); attroff(A_STANDOUT); bool isMuted = entry->m_Mute || averageVolume == PA_VOLUME_MUTED; printw(" %s %s", isMuted ? SYM_MUTE : "", entry->m_Lock ? SYM_LOCK : ""); int curX = 0, curY = 0; getyx(stdscr, curY, curX); unsigned remainingChars = (unsigned) COLS - curX - 3; std::string displayName = getEntryDisplayName(entry); if (remainingChars < displayName.length()) { string_maxlen_abs(displayName, remainingChars); remainingChars = 0; } else { remainingChars -= displayName.length(); } mvprintw(curY, curX + remainingChars + 1, displayName.c_str()); lineNumber++; } m_NumDrawnEntries = entryIndex - m_NumSkippedEntries; refresh(); } unsigned int pamix_ui::drawEntryControlMeters(const Entry *entry, unsigned entryIndex, unsigned int lineNumber) const { pa_volume_t averageVolume = pa_cvolume_avg(&entry->m_PAVolume); double dB = pa_sw_volume_to_dB(averageVolume); double vol = averageVolume / (double) PA_VOLUME_NORM; char numChannels = entry->m_Lock ? (char) 1 : entry->m_PAVolume.channels; bool isSelectedEntry = entryIndex == m_SelectedEntry; if (entry->m_Meter) { if (entry->m_Lock) { drawVolumeBar(lineNumber, 32, COLS - 33, vol, MAX_VOL); std::string descriptionTemplate = "%.2fdB (%.2f)"; if (isSelectedEntry) descriptionTemplate.insert(0, SYM_ARROW); mvprintw(lineNumber++, 1, descriptionTemplate.c_str(), dB, vol); } else { for (char channel = 0; channel < numChannels; channel++) { std::string descriptionTemplate = "%.*s %.2fdB (%.2f)"; uint32_t volume = entry->m_PAVolume.values[channel]; bool isSelectedChannel = isSelectedEntry && channel == m_SelectedChannel; double channel_dB = pa_sw_volume_to_dB(volume); double channel_pct = volume / (double) PA_VOLUME_NORM; pa_channel_position_t channelPosition = entry->m_PAChannelMap.map[channel]; std::string channelPrettyName = pa_channel_position_to_pretty_string(channelPosition); drawVolumeBar(lineNumber, 32, COLS - 33, channel_pct, MAX_VOL); if (isSelectedChannel) descriptionTemplate.insert(0, SYM_ARROW); unsigned indent = isSelectedChannel ? 13 : 15; mvprintw(lineNumber++, 1, descriptionTemplate.c_str(), indent, channelPrettyName.c_str(), channel_dB, channel_pct); } } } return lineNumber; } void pamix_ui::redrawVolumeBars() { std::lock_guard lockGuard(m_DrawMutex); auto it = std::next(m_Entries->begin(), m_NumSkippedEntries); uint32_t index = 0; for (auto end = m_Entries->end(); it != end; it++, index++) { if (index >= m_NumSkippedEntries + m_NumDrawnEntries) break; uint32_t y = m_VolumeBarLineNums[it->first]; if (it->second->m_Meter) drawVolumeBar(y, 1, COLS - 2, it->second->m_Peak, 1.0); } refresh(); } void pamix_ui::drawHeader() const { mvprintw(0, 1, "%d/%d", m_Entries->empty() ? 0 : m_SelectedEntry + 1, m_Entries->size()); mvprintw(0, 10, "%s", entryTypeNames[m_EntriesType]); } std::string pamix_ui::getEntryDisplayName(Entry *entry) { switch (m_EntriesType) { case ENTRY_SINK: case ENTRY_SOURCE: { auto deviceEntry = ((DeviceEntry *) entry); if (deviceEntry != nullptr) { const DeviceEntry::DeviceProfile *deviceProfile = deviceEntry->getPortProfile(); if (deviceProfile != nullptr) return deviceProfile->description.empty() ? deviceProfile->name : deviceProfile->description; else return deviceEntry->m_Name; } return ""; } case ENTRY_SINKINPUT: { auto sinkInput = (SinkInputEntry *) entry; return m_paInterface->getSinks()[sinkInput->m_Device]->m_Name; } case ENTRY_SOURCEOUTPUT: { auto sourceOutput = (SourceOutputEntry *) entry; return m_paInterface->getSources()[sourceOutput->m_Device]->m_Name; } case ENTRY_CARDS: { return ((CardEntry *) entry)->m_Profiles[((CardEntry *) entry)->m_Profile].description; } default: return "UNKNOWN ENTRY TYPE"; } } pamix_ui::pamix_ui(PAInterface *paInterface) : m_paInterface(paInterface) { reset(); } void pamix_ui::selectEntries(entry_type type) { switch (type) { case ENTRY_SINK: m_Entries = &m_paInterface->getSinks(); break; case ENTRY_SOURCE: m_Entries = &m_paInterface->getSources(); break; case ENTRY_SINKINPUT: m_Entries = &m_paInterface->getSinkInputs(); break; case ENTRY_SOURCEOUTPUT: m_Entries = &m_paInterface->getSourceOutputs(); break; case ENTRY_CARDS: m_Entries = &m_paInterface->getCards(); break; default: return; } m_EntriesType = type; if (m_SelectedEntry > 0 && m_SelectedEntry >= m_Entries->size()) m_SelectedEntry = (unsigned) m_Entries->size() - 1; auto currentEntry = getSelectedEntryIterator(); if (currentEntry != m_Entries->end() && m_SelectedChannel > 0 && m_SelectedChannel >= currentEntry->second->m_PAVolume.channels) m_SelectedChannel = (unsigned) currentEntry->second->m_PAVolume.channels - 1; } int pamix_ui::getKeyInput() { std::lock_guard guard(m_DrawMutex); return getch(); } pamix_entry_iter_t pamix_ui::getSelectedEntryIterator() { if (m_SelectedEntry < m_Entries->size()) return std::next(m_Entries->begin(), m_SelectedEntry); else return m_Entries->end(); } void pamix_ui::adjustDisplayedEntries() { if (!m_Entries->empty()) return; if (m_SelectedEntry >= m_NumSkippedEntries && m_SelectedEntry < m_NumSkippedEntries + m_NumDrawnEntries) return; if (m_SelectedEntry < m_NumSkippedEntries) { // scroll up until selected is at top m_NumSkippedEntries = m_SelectedEntry; } else { // scroll down until selected is at bottom uint32_t linesToFree = 0; uint32_t idx = m_NumSkippedEntries + m_NumDrawnEntries; for (; idx <= m_SelectedEntry; idx++) linesToFree += m_EntrySizes[idx] + 2; uint32_t linesFreed = 0; idx = m_NumSkippedEntries; while (linesFreed < linesToFree) linesFreed += m_EntrySizes[idx++] + 2; m_NumSkippedEntries = idx; } } void pamix_ui::selectNext(bool includeChannels) { moveSelection(1, includeChannels); } void pamix_ui::selectPrevious(bool includeChannels) { moveSelection(-1, includeChannels); } void pamix_ui::moveSelection(int delta, bool includeChannels) { if (m_SelectedEntry < m_Entries->size()) { auto it = getSelectedEntryIterator(); int step = delta > 0 ? 1 : -1; for (int i = 0, numSteps = delta < 0 ? -delta : delta; i < numSteps; i++) { auto entryThresh = static_cast(delta < 0 ? 0 : m_Entries->size() - 1); if (includeChannels && m_EntriesType != ENTRY_CARDS) { bool isLocked = it->second->m_Lock; int channelThresh = it->second->m_PAVolume.channels - 1; if (delta < 0) channelThresh = 0; if (!isLocked && m_SelectedChannel != channelThresh) { m_SelectedChannel += step; continue; } } if ((step == -1 && it == m_Entries->begin()) || (step == 1 && it == m_Entries->end())) break; if (m_SelectedEntry != entryThresh) { it = std::next(it, step); m_SelectedEntry += step; m_SelectedChannel = static_cast(delta < 0 ? it->second->m_PAVolume.channels - (char) 1 : 0); } } } adjustDisplayedEntries(); } PAmix-1.6-10-gea4ab3b/src/sinkentry.cpp000066400000000000000000000046761322620461400175200ustar00rootroot00000000000000#include void SinkEntry::update(const pa_sink_info *info) { m_Name = info->description; m_Index = info->index; m_Mute = info->mute != 0; m_MonitorIndex = info->monitor_source; m_PAVolume = info->volume; m_PAChannelMap = info->channel_map; m_Kill = false; m_Ports.clear(); m_Port = -1; for (unsigned i = 0; i < info->n_ports; i++) { auto port = info->ports[i]; m_Ports.emplace_back(port->name, port->description); if (info->active_port == port) m_Port = i; } m_State = info->state; } void SinkEntry::setVolume(const int channel, const pa_volume_t volume) { mainloop_lockguard lg(interface->getPAMainloop()); pa_cvolume *cvol = &m_PAVolume; if (m_Lock) pa_cvolume_set(cvol, cvol->channels, volume); else cvol->values[channel] = volume; pa_operation *op = pa_context_set_sink_volume_by_index(interface->getPAContext(), m_Index, cvol, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkEntry::setMute(bool mute) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_sink_mute_by_index(interface->getPAContext(), m_Index, mute, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkEntry::cycleSwitch(bool increment) { int delta = increment ? 1 : -1; if (m_Ports.empty()) return; m_Port = (m_Port + delta) % (unsigned) m_Ports.size(); pa_operation *op = pa_context_set_sink_port_by_index(interface->getPAContext(), m_Index, m_Ports[m_Port].name.c_str(), &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkEntry::setPort(const char *port) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_sink_port_by_index(interface->getPAContext(), m_Index, port, PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } PAmix-1.6-10-gea4ab3b/src/sinkinputentry.cpp000066400000000000000000000056631322620461400205750ustar00rootroot00000000000000#include void SinkInputEntry::update(const pa_sink_input_info *info) { // general vars const char *name = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME); m_Name = name != nullptr ? name : info->name; m_Index = info->index; m_Mute = info->mute; m_PAVolume = info->volume; m_PAChannelMap = info->channel_map; m_Kill = false; // stream vars m_Device = info->sink; } void SinkInputEntry::setVolume(const int channel, const pa_volume_t volume) { mainloop_lockguard lg(interface->getPAMainloop()); pa_cvolume *cvol = &m_PAVolume; if (m_Lock) pa_cvolume_set(cvol, cvol->channels, volume); else cvol->values[channel] = volume; pa_operation *op = pa_context_set_sink_input_volume(interface->getPAContext(), m_Index, cvol, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkInputEntry::setMute(bool mute) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_sink_input_mute(interface->getPAContext(), m_Index, mute, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkInputEntry::cycleSwitch(bool increment) { pamix_entry_iter_t sink = interface->getSinks().find(m_Device); if (increment) sink++; else { if (sink == interface->getSinks().begin()) sink = std::next(sink, interface->getSinks().size() - 1); else sink--; } if (sink == interface->getSinks().end()) sink = interface->getSinks().begin(); pa_operation *op = pa_context_move_sink_input_by_index(interface->getPAContext(), m_Index, sink->second->m_Index, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkInputEntry::move(uint32_t idx) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_move_sink_input_by_index(interface->getPAContext(), m_Index, idx, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SinkInputEntry::kill() { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_kill_sink_input(interface->getPAContext(), m_Index, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } PAmix-1.6-10-gea4ab3b/src/sourceentry.cpp000066400000000000000000000050061322620461400200400ustar00rootroot00000000000000#include void SourceEntry::update(const pa_source_info *info) { m_Name = info->description; m_Index = info->index; m_Mute = info->mute != 0; m_MonitorIndex = m_Index; m_PAVolume = info->volume; m_PAChannelMap = info->channel_map; m_Kill = false; m_Ports.clear(); m_Port = -1; for (unsigned i = 0; i < info->n_ports; i++) { auto port = info->ports[i]; m_Ports.emplace_back(port->name, port->description); if (info->active_port == port) m_Port = i; } m_State = info->state; } void SourceEntry::setVolume(const int channel, const pa_volume_t volume) { mainloop_lockguard lg(interface->getPAMainloop()); pa_cvolume *cvol = &m_PAVolume; if (m_Lock) pa_cvolume_set(cvol, cvol->channels, volume); else cvol->values[channel] = volume; pa_operation *op = pa_context_set_source_volume_by_index(interface->getPAContext(), m_Index, cvol, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceEntry::setMute(bool mute) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_source_mute_by_index(interface->getPAContext(), m_Index, mute, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceEntry::cycleSwitch(bool increment) { int delta = increment ? 1 : -1; if (m_Ports.empty()) return; m_Port = (m_Port + delta) % (unsigned) m_Ports.size(); pa_operation *op = pa_context_set_source_port_by_index(interface->getPAContext(), m_Index, m_Ports[m_Port].name.c_str(), &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceEntry::setPort(const char *port) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_source_port_by_index(interface->getPAContext(), m_Index, port, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } PAmix-1.6-10-gea4ab3b/src/sourceoutputentry.cpp000066400000000000000000000060561322620461400213270ustar00rootroot00000000000000#include void SourceOutputEntry::update(const pa_source_output_info *info) { // general vars const char *name = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_NAME); m_Name = name != nullptr ? name : info->name; m_Index = info->index; m_Mute = info->mute; m_MonitorIndex = info->source; m_PAVolume = info->volume; m_PAChannelMap = info->channel_map; m_Kill = false; // stream vars m_Device = info->source; } void SourceOutputEntry::setVolume(const int channel, const pa_volume_t volume) { mainloop_lockguard lg(interface->getPAMainloop()); pa_cvolume *cvol = &m_PAVolume; if (m_Lock) pa_cvolume_set(cvol, cvol->channels, volume); else cvol->values[channel] = volume; pa_operation *op = pa_context_set_source_output_volume(interface->getPAContext(), m_Index, cvol, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceOutputEntry::setMute(bool mute) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_set_source_output_mute(interface->getPAContext(), m_Index, mute, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceOutputEntry::cycleSwitch(bool increment) { pamix_entry_iter_t source = interface->getSources().find(m_Device); if (increment) source++; else { if (source == interface->getSources().begin()) source = std::next(source, interface->getSources().size() - 1); else source--; } if (source == interface->getSources().end()) source = interface->getSources().begin(); pa_operation *op = pa_context_move_source_output_by_index(interface->getPAContext(), m_Index, source->second->m_Index, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceOutputEntry::move(uint32_t idx) { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_move_source_output_by_index(interface->getPAContext(), m_Index, idx, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } void SourceOutputEntry::kill() { mainloop_lockguard lg(interface->getPAMainloop()); pa_operation *op = pa_context_kill_source_output(interface->getPAContext(), m_Index, &PAInterface::cb_success, interface); while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(interface->getPAMainloop()); pa_operation_unref(op); } PAmix-1.6-10-gea4ab3b/src/volumeutil.cpp000066400000000000000000000011361322620461400176630ustar00rootroot00000000000000#include pa_cvolume *volume_pct_delta(pa_cvolume *volume, int channel, double pctDelta) { int delta = round(pctDelta * PA_VOLUME_NORM); pa_volume_t vol = channel == -1 ? pa_cvolume_avg(volume) : volume->values[channel]; if (delta > 0) { if (vol + delta > vol) { if (vol + delta > PA_VOLUME_NORM * 1.5) vol = PA_VOLUME_NORM * 1.5; else vol += delta; } } else { if (vol + delta < vol) vol += delta; else vol = PA_VOLUME_MUTED; } if (channel == -1) pa_cvolume_set(volume, volume->channels, vol); else volume->values[channel] = vol; return volume; }