pax_global_header00006660000000000000000000000064130146473310014514gustar00rootroot0000000000000052 comment=cccd278e14989bddad80fb1581078bc094ad3baf PAmix-1.5/000077500000000000000000000000001301464733100123775ustar00rootroot00000000000000PAmix-1.5/.clang_complete000066400000000000000000000000631301464733100153530ustar00rootroot00000000000000-xc++ -Iinclude -std=c++11 -I/usr/include/ncursesw PAmix-1.5/.gitignore000066400000000000000000000001131301464733100143620ustar00rootroot00000000000000tools/ build/ Makefile.in aclocal.m4 autom4te.cache/ config.h.in configure PAmix-1.5/LICENSE000066400000000000000000000020621301464733100134040ustar00rootroot00000000000000The 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.5/Makefile.am000066400000000000000000000010061301464733100144300ustar00rootroot00000000000000bin_PROGRAMS = pamix pamix_CXXFLAGS = -I$(top_srcdir)/include -pthread $(NCURSES_CFLAGS) $(PULSEAUDIO_CFLAGS) pamix_LDADD = $(NCURSES_LIBS) $(PULSEAUDIO_LIBS) pamix_SOURCES = \ src/cardentry.cpp \ src/volumeutil.cpp\ src/sinkentry.cpp\ src/sourceentry.cpp\ src/pamix.cpp\ src/sourceoutputentry.cpp\ src/sinkinputentry.cpp\ src/entry.cpp\ src/configuration.cpp\ src/pamix_functions.cpp\ src/painterface.cpp man1_MANS = man/pamix.1 pkgsysconfdir = $(sysconfdir) dist_pkgsysconf_DATA = pamix.conf PAmix-1.5/README.md000066400000000000000000000034271301464733100136640ustar00rootroot00000000000000# PAMix - the pulseaudio terminal mixer # Table of Contents # 1. [**Installation**](#installation) 1. Gentoo 2. Arch 2. [**Building Manually**](#building-manually) 1. Dependencies 2. Configuration 3. Building 4. Installing 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` # Building Manually: # ## Dependencies: # ### Build ## * autoconf * autoconf-archive * pkg-config * make ### Runtime ## * PulseAudio * Ncurses ## Autoconf Configuration ## Generate configure script by running `autoreconf -i` and then run `./configure` with your preferred options ### Options ### `--disable-unicode` depends on ncurses instead of ncursesw and replaces unicode symbols with ascii ## Building ## Run `make` ## Installing ## Run `make install` --- # 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 | | 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.5/configure.ac000066400000000000000000000026551301464733100146750ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) AC_INIT([pamix], [1.2], [jenschjoshua@gmail.com]) AC_CONFIG_SRCDIR([include/entry.hpp]) AC_CONFIG_AUX_DIR([tools]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AC_LANG([C++]) AX_CXX_COMPILE_STDCXX_11() # Checks for programs. AC_PROG_CXX # Check options AC_MSG_CHECKING([--enable-unicode argument]) AC_ARG_ENABLE([unicode], AS_HELP_STRING([--enable-unicode], [Enable unicode support.]), [enable_unicode=$enableval], [enable_unicode="yes"]) AC_MSG_RESULT([$enable_unicode]) if test "$enable_unicode" = "yes"; then AC_DEFINE([FEAT_UNICODE], [1], [Enable unicode support]) fi # Checks for libraries. AS_IF([test "$enable_unicode" = "yes"], [PKG_CHECK_MODULES([NCURSES], [ncursesw])], [PKG_CHECK_MODULES([NCURSES], [ncurses])]) AC_SEARCH_LIBS([use_default_colors], [ncurses curses], AC_DEFINE(HAVE_USE_DEFAULT_COLORS, 1, [Define to 1 if the curses library has the function `use_default_colors`. ])) PKG_CHECK_MODULES([PULSEAUDIO], [libpulse]) # Checks for header files. AC_CHECK_HEADERS([locale.h]) # Checks for typedefs, structures, and compiler characteristics. AC_CHECK_HEADER_STDBOOL AC_C_INLINE AC_TYPE_SIZE_T AC_TYPE_UINT32_T AC_TYPE_UINT8_T # Checks for library functions. AC_CHECK_FUNCS([memset setlocale]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT PAmix-1.5/include/000077500000000000000000000000001301464733100140225ustar00rootroot00000000000000PAmix-1.5/include/configuration.hpp000066400000000000000000000020641301464733100174040ustar00rootroot00000000000000#pragma once #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.5/include/entry.hpp000066400000000000000000000104611301464733100156760ustar00rootroot00000000000000#pragma once #include #include #include #include #include class PAInterface; enum entry_type { ENTRY_SINK, ENTRY_SOURCE, ENTRY_SINKINPUT, ENTRY_SOURCEOUTPUT, ENTRY_CARDS, ENTRY_COUNT }; 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 { int m_Port; std::vector m_Ports; DeviceEntry(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 std::string getPort() { return m_Port > -1 ? m_Ports[m_Port] : ""; } 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.5/include/painterface.hpp000066400000000000000000000077201301464733100170220ustar00rootroot00000000000000#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 typedef uint32_t pai_subscription_type_t; typedef void (*pai_subscription_cb)(PAInterface *, const pai_subscription_type_t); typedef std::map>::iterator iter_entry_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); 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.5/include/pamix.hpp000066400000000000000000000024711301464733100156550ustar00rootroot00000000000000#pragma once #include #ifdef HAVE_CONFIG_H #include #endif struct UpdateData { bool redrawAll; UpdateData() = default; 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); void selectEntries(PAInterface *interfae, entry_type type); void set_volume(PAInterface *interface, double pct); void add_volume(PAInterface *interface, double pct); void cycle_switch(PAInterface *interface, bool inc); void set_mute(PAInterface *interface, bool mute); void toggle_mute(PAInterface *interface); void set_lock(PAInterface *interface, bool lock); void toggle_lock(PAInterface *interface); void select_next(PAInterface *interface, bool precise); void select_previous(PAInterface *interface, bool precise); PAmix-1.5/include/pamix_functions.hpp000066400000000000000000000011571301464733100177450ustar00rootroot00000000000000#pragma once #include #include union argument_t { double d; int i; bool b; }; void pamix_set_interface(PAInterface *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.5/include/volumeutil.hpp000066400000000000000000000002111301464733100167320ustar00rootroot00000000000000#pragma once #include #include pa_cvolume *volume_pct_delta(pa_cvolume *vol, int channel, double pctDelta); PAmix-1.5/man/000077500000000000000000000000001301464733100131525ustar00rootroot00000000000000PAmix-1.5/man/pamix.1000066400000000000000000000074511301464733100143610ustar00rootroot00000000000000./" this is the manpage of the pamix pulseaudio ncurses mixer .TH pamix 1 "05 Sep 2016" "V1.4" "pamix man page" .SH SYONPSIS pamix .SH DESCRIPTION This is a pavucontrol inspired ncurses based pulseaudio mixer for the commandline .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.5/pamix.conf000066400000000000000000000025771301464733100143770ustar00rootroot00000000000000; 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 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 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.5/src/000077500000000000000000000000001301464733100131665ustar00rootroot00000000000000PAmix-1.5/src/cardentry.cpp000066400000000000000000000017101301464733100156640ustar00rootroot00000000000000#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_Profiles.clear(); m_Profile = -1; for (unsigned i = 0; i < info->n_profiles; i++) { m_Profiles.push_back(info->profiles2[i]); if (info->profiles2[i] == info->active_profile2) m_Profile = i; } } void CardEntry::cycleSwitch(bool increment) { mainloop_lockguard lg(interface->getPAMainloop()); int delta = increment ? 1 : -1; if (!m_Profiles.size()) return; m_Profile = (m_Profile + delta) % 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.5/src/configuration.cpp000066400000000000000000000127461301464733100165530ustar00rootroot00000000000000#include #include #include #include #include std::vector> Configuration::m_Keynames; inline bool startswithsub(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.push_back(std::make_pair(i, r)); } } for (unsigned i = 0; i < m_Keynames.size(); i++) if (!name.compare(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"); 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"); kbind.m_Function = &pamix_set_mute; kbind.m_Argument.b = mute; } m_Bindings[code].push_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 (startswithsub(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 (startswithsub(line, "unbind ", sub)) { config->unbind(sub); } else if (startswithsub(line, "unbind-all", sub)) config->unbindall(); else if (startswithsub(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.5/src/entry.cpp000066400000000000000000000011611301464733100150320ustar00rootroot00000000000000#include Entry::Entry(PAInterface *iface) : interface(iface) { } Entry::~Entry() { if (m_Monitor) { 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.5/src/painterface.cpp000066400000000000000000000316651301464733100161660ustar00rootroot00000000000000#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(0), m_MainloopApi(0), m_Context() { } PAInterface::~PAInterface() { 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); } } } void PAInterface::signal_mainloop(void *interface) { pa_threaded_mainloop_signal(((PAInterface *)interface)->m_Mainloop, 0); } void PAInterface::cb_context_state(pa_context *context, void *interface) { if (PA_CONTEXT_IS_GOOD(pa_context_get_state(context))) PAInterface::signal_mainloop((PAInterface *)interface); else ((PAInterface *)interface)->m_Context = nullptr; } void PAInterface::cb_context_drain_complete(pa_context *context, void *null) { pa_context_disconnect(context); } void PAInterface::cb_success(pa_context *context, int success, 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 *context, pa_subscription_event_type_t type, uint32_t idx, void *interface) { pai_subscription_type_t paisubtype = 0x0U; 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 *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 *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 *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 *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) { PAInterface *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")) 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 *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) { std::pair *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 (iter_entry_t 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 (iter_entry_t it = map.begin(); it != map.end();) { if (!it->second) { it = map.erase(it); continue; } if (it->second->m_Kill) { for (std::vector>>::iterator pairit = interface->m_IEPairs.begin(); pairit != interface->m_IEPairs.end();) { if ((*pairit)->second == it->second.get()) { (*pairit)->second = nullptr; break; } pairit++; } 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::connect() { 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, NULL, 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)) return false; pa_context_flags flags = m_Autospawn ? PA_CONTEXT_NOFLAGS : PA_CONTEXT_NOAUTOSPAWN; if (pa_context_connect(m_Context, NULL, flags, NULL)) 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::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, NULL); if (!s) return nullptr; if (stream != (uint32_t)-1) pa_stream_set_monitor_stream(s, stream); std::pair *pair = new std::pair(); pair->first = interface; pair->second = entry; interface->m_IEPairs.push_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) ? 0 : 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, -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.5/src/pamix.cpp000066400000000000000000000341731301464733100150200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include // GLOBAL VARIABLES Configuration configuration; bool running = true; unsigned selectedEntry = 0; uint8_t selectedChannel = 0; std::map lastPeaks; std::mutex screenMutex; std::map> *entryMap = nullptr; entry_type entryType; // scrolling uint32_t skipEntries = 0; uint32_t numDisplayedEntries = 0; std::map mapMonitorLines; std::map mapIndexSize; // 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 selectEntries(PAInterface *interface, entry_type type) { switch (type) { case ENTRY_SINK: entryMap = &interface->getSinks(); break; case ENTRY_SOURCE: entryMap = &interface->getSources(); break; case ENTRY_SINKINPUT: entryMap = &interface->getSinkInputs(); break; case ENTRY_SOURCEOUTPUT: entryMap = &interface->getSourceOutputs(); break; case ENTRY_CARDS: entryMap = &interface->getCards(); break; default: return; } entryType = type; } void generateMeter(int y, int x, int width, double pct, const double maxvol) { int segments = width - 2; if (segments <= 0) return; if (pct < 0) pct = 0; else if (pct > maxvol) pct = maxvol; int filled = pct / maxvol * (double)segments; if (filled > segments) filled = segments; mvaddstr(y, x++, "["); mvaddstr(y, x + segments, "]"); int indexColorA = segments * ((double)1 / 3); int indexColorB = segments * ((double)2 / 3); FEAT_UNICODE_STRING meter; meter.append(filled, SYM_VOLBAR); meter.append(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 drawEntries(PAInterface *interface) { std::lock_guard modlg(interface->m_ModifyMutex); std::lock_guard lg(screenMutex); if (selectedEntry > entryMap->size() - 1) { selectedEntry = entryMap->size() - 1; selectedChannel = 0; } clear(); mvprintw(0, 1, "%d/%d", entryMap->empty() ? 0 : selectedEntry + 1, entryMap->size()); const char *entryname = entryType == ENTRY_SINK ? "Output Devices" : entryType == ENTRY_SOURCE ? "Input Devices" : entryType == ENTRY_SINKINPUT ? "Playback" : entryType == ENTRY_SOURCEOUTPUT ? "Recording" : "Cards"; mvprintw(0, 10, "%s", entryname); unsigned y = 2; unsigned index = 0; iter_entry_t it = entryMap->begin(); for (; it != entryMap->end(); it++, index++) mapIndexSize[index] = it->second->m_Lock ? 1 : it->second->m_PAVolume.channels; for (it = std::next(entryMap->begin(), skipEntries), index = skipEntries; it != entryMap->end(); it++, index++) { std::string appname = it->second ? it->second->m_Name : ""; pa_volume_t avgvol = pa_cvolume_avg(&it->second->m_PAVolume); double dB = pa_sw_volume_to_dB(avgvol); double vol = avgvol / (double)PA_VOLUME_NORM; bool isSelectedEntry = index == selectedEntry; uint8_t numChannels = it->second->m_Lock ? 1 : it->second->m_PAVolume.channels; if (y + numChannels + 2 > (unsigned)LINES) break; if (it->second->m_Meter && it->second->m_Lock) { generateMeter(y, 32, COLS - 33, vol, MAX_VOL); std::string descstring = "%.2fdB (%.2f)"; if (isSelectedEntry) descstring.insert(0, SYM_ARROW); mvprintw(y++, 1, descstring.c_str(), dB, vol); } else if (it->second->m_Meter) { for (uint32_t chan = 0; chan < numChannels; chan++) { bool isSelChannel = isSelectedEntry && chan == selectedChannel; std::string channame = pa_channel_position_to_pretty_string(it->second->m_PAChannelMap.map[chan]); double cdB = pa_sw_volume_to_dB(it->second->m_PAVolume.values[chan]); double cvol = it->second->m_PAVolume.values[chan] / (double)PA_VOLUME_NORM; generateMeter(y, 32, COLS - 33, cvol, MAX_VOL); std::string descstring = "%.*s %.2fdB (%.2f)"; if (isSelChannel) descstring.insert(0, SYM_ARROW); mvprintw(y++, 1, descstring.c_str(), isSelChannel ? 13 : 15, channame.c_str(), cdB, cvol); } } double peak = it->second->m_Peak; mapMonitorLines[it->first] = y; if (it->second->m_Meter) generateMeter(y++, 1, COLS - 2, peak, 1.0); if (isSelectedEntry) attron(A_STANDOUT); if (appname.length() > COLS * 0.4) { appname = appname.substr(0, COLS * 0.4 - 2); appname.append(".."); } mvprintw(y++, 1, appname.c_str()); if (isSelectedEntry) attroff(A_STANDOUT); bool muted = it->second->m_Mute || avgvol == PA_VOLUME_MUTED; printw(" %s %s", muted ? SYM_MUTE : "", it->second->m_Lock ? SYM_LOCK : ""); //append sinkname int px = 0, py = 0; unsigned space = 0; getyx(stdscr, py, px); space = COLS - px - 3; std::string sinkname = ""; switch (entryType) { case ENTRY_SINK: if (it->second) sinkname = ((SinkEntry *)it->second.get())->getPort(); break; case ENTRY_SOURCE: if (it->second) sinkname = ((SourceEntry *)it->second.get())->getPort(); break; case ENTRY_SINKINPUT: if (it->second && interface->getSinks()[((SinkInputEntry *)it->second.get())->m_Device]) sinkname = interface->getSinks()[((SinkInputEntry *)it->second.get())->m_Device]->m_Name; break; case ENTRY_SOURCEOUTPUT: if (it->second && interface->getSources()[((SourceOutputEntry *)it->second.get())->m_Device]) sinkname = interface->getSources()[((SourceOutputEntry *)it->second.get())->m_Device]->m_Name; break; case ENTRY_CARDS: if (it->second) sinkname = ((CardEntry *)it->second.get())->m_Profiles[((CardEntry *)it->second.get())->m_Profile].description; break; default: break; } if (space < sinkname.size()) { sinkname = sinkname.substr(0, space - 2); sinkname.append(".."); space = 0; } else { space -= sinkname.size(); } mvprintw(py, px + space + 1, sinkname.c_str()); y += 1; } numDisplayedEntries = index - skipEntries; refresh(); } void drawMonitors(PAInterface *interface) { std::lock_guard lg(screenMutex); std::lock_guard modlg(interface->m_ModifyMutex); iter_entry_t it = std::next(entryMap->begin(), skipEntries); uint32_t index = 0; for (; it != entryMap->end(); it++, index++) { if (index >= skipEntries + numDisplayedEntries) break; uint32_t y = mapMonitorLines[it->first]; if (it->second->m_Meter) generateMeter(y, 1, COLS - 2, it->second->m_Peak, 1.0); } refresh(); } inline iter_entry_t get_selected_entry_iter(PAInterface *interface) { if (selectedEntry < entryMap->size()) return std::next(entryMap->begin(), selectedEntry); else return entryMap->end(); } void set_volume(PAInterface *interface, double pct) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->setVolume(it->second->m_Lock ? -1 : selectedChannel, PA_VOLUME_NORM * pct); } void add_volume(PAInterface *interface, double pct) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->addVolume(it->second->m_Lock ? -1 : selectedChannel, pct); } void cycle_switch(PAInterface *interface, bool increment) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->cycleSwitch(increment); } void set_mute(PAInterface *interface, bool mute) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->setMute(mute); } void toggle_mute(PAInterface *interface) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->setMute(!it->second->m_Mute); } void adjustDisplay() { if (!entryMap->size()) return; if (selectedEntry >= skipEntries && selectedEntry < skipEntries + numDisplayedEntries) return; if (selectedEntry < skipEntries) { // scroll up until selected is at top skipEntries = selectedEntry; } else { // scroll down until selected is at bottom uint32_t linesToFree = 0; uint32_t idx = skipEntries + numDisplayedEntries; for (; idx <= selectedEntry; idx++) linesToFree += mapIndexSize[idx] + 2; uint32_t linesFreed = 0; idx = skipEntries; while (linesFreed < linesToFree) linesFreed += mapIndexSize[idx++] + 2; skipEntries = idx; } } void select_next(PAInterface *interface, bool channelLevel) { if (selectedEntry < entryMap->size()) { if (channelLevel) { iter_entry_t it = get_selected_entry_iter(interface); if (!it->second->m_Lock && selectedChannel < it->second->m_PAVolume.channels - 1) { selectedChannel++; return; } } selectedEntry++; if (selectedEntry >= entryMap->size()) selectedEntry = entryMap->size() - 1; else selectedChannel = 0; } adjustDisplay(); } void select_previous(PAInterface *interface, bool channelLevel) { if (selectedEntry < entryMap->size()) { if (channelLevel) { iter_entry_t it = get_selected_entry_iter(interface); if (!it->second->m_Lock && selectedChannel > 0) { selectedChannel--; return; } } if (selectedEntry > 0) selectedEntry--; else if (!get_selected_entry_iter(interface)->second->m_Lock) { selectedChannel = get_selected_entry_iter(interface)->second->m_PAVolume.channels - 1; } } adjustDisplay(); } void set_lock(PAInterface *interface, bool lock) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->m_Lock = lock; } void toggle_lock(PAInterface *interface) { iter_entry_t it = get_selected_entry_iter(interface); if (it != entryMap->end()) it->second->m_Lock = !it->second->m_Lock; } void inputThread(PAInterface *interface) { while (running) { int ch = getch(); configuration.pressKey(ch); continue; } } void pai_subscription(PAInterface *interface, pai_subscription_type_t type) { bool updAll = type & PAI_SUBSCRIPTION_MASK_INFO; signal_update(updAll); } void sig_handle_resize(int s) { endwin(); refresh(); signal_update(true); } void init_colors() { start_color(); #ifdef HAVE_USE_DEFAULT_COLORS use_default_colors(); init_pair(1, COLOR_GREEN, -1); init_pair(2, COLOR_YELLOW, -1); init_pair(3, COLOR_RED, -1); #else init_pair(1, COLOR_GREEN, 0); init_pair(2, COLOR_YELLOW, 0); init_pair(3, COLOR_RED, 0); #endif } void sig_handle(int sig) { 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"; 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"); } int main(int argc, char **argv) { loadConfiguration(); setlocale(LC_ALL, ""); initscr(); init_colors(); 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"); if (configuration.has(CONFIGURATION_AUTOSPAWN_PULSE)) pai.setAutospawn(configuration.getBool(CONFIGURATION_AUTOSPAWN_PULSE)); entry_type entry = ENTRY_SINKINPUT; if (configuration.has(CONFIGURATION_DEFAULT_TAB)) { int value = configuration.getInt(CONFIGURATION_DEFAULT_TAB); if (value >= 0 && value < ENTRY_COUNT) entry = (entry_type) value; } selectEntries(&pai, entry); pai.subscribe(pai_subscription); if (!pai.connect()) { endwin(); fprintf(stderr, "Failed to connect to PulseAudio.\n"); exit(1); } pamix_set_interface(&pai); drawEntries(&pai); std::thread inputT(inputThread, &pai); inputT.detach(); while (running) { std::unique_lock lk(updMutex); cv.wait(lk, [] { return !updateDataQ.empty(); }); if (updateDataQ.front().redrawAll) drawEntries(&pai); else drawMonitors(&pai); updateDataQ.pop(); } endwin(); return 0; } PAmix-1.5/src/pamix_functions.cpp000066400000000000000000000022161301464733100171010ustar00rootroot00000000000000#include #include static PAInterface *interface; void pamix_set_interface(PAInterface *i) { interface = i; } void pamix_quit(argument_t arg) { quit(); } void pamix_select_tab(argument_t arg) { selectEntries(interface, (entry_type)arg.i); signal_update(true); } void pamix_select_next(argument_t arg) { select_next(interface, arg.b); signal_update(true); } void pamix_select_prev(argument_t arg) { select_previous(interface, arg.b); signal_update(true); } void pamix_set_volume(argument_t arg) { set_volume(interface, arg.d); } void pamix_add_volume(argument_t arg) { add_volume(interface, arg.d); } void pamix_cycle_next(argument_t arg) { cycle_switch(interface, true); } void pamix_cycle_prev(argument_t arg) { cycle_switch(interface, false); } void pamix_toggle_lock(argument_t arg) { toggle_lock(interface); signal_update(true); } void pamix_set_lock(argument_t arg) { set_lock(interface, arg.b); signal_update(true); } void pamix_toggle_mute(argument_t arg) { toggle_mute(interface); signal_update(true); } void pamix_set_mute(argument_t arg) { set_mute(interface, arg.b); signal_update(true); } PAmix-1.5/src/sinkentry.cpp000066400000000000000000000043231301464733100157220ustar00rootroot00000000000000#include void SinkEntry::update(const pa_sink_info *info) { m_Name = info->description; m_Index = info->index; m_Mute = info->mute; 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++) { m_Ports.push_back(info->ports[i]->name); if (info->active_port == info->ports[i]) 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.size()) return; m_Port = (m_Port + delta) % m_Ports.size(); pa_operation *op = pa_context_set_sink_port_by_index(interface->getPAContext(), m_Index, m_Ports[m_Port].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.5/src/sinkinputentry.cpp000066400000000000000000000051751301464733100170100ustar00rootroot00000000000000#include void SinkInputEntry::update(const pa_sink_input_info *info) { // general vars m_Name = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_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) { iter_entry_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.5/src/sourceentry.cpp000066400000000000000000000043331301464733100162570ustar00rootroot00000000000000#include void SourceEntry::update(const pa_source_info *info) { m_Name = info->description; m_Index = info->index; m_Mute = info->mute; 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++) { m_Ports.push_back(info->ports[i]->name); if (info->active_port == info->ports[i]) 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.size()) return; m_Port = (m_Port + delta) % m_Ports.size(); pa_operation *op = pa_context_set_source_port_by_index(interface->getPAContext(), m_Index, m_Ports[m_Port].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.5/src/sourceoutputentry.cpp000066400000000000000000000053361301464733100175440ustar00rootroot00000000000000#include void SourceOutputEntry::update(const pa_source_output_info *info) { // general vars m_Name = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_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) { iter_entry_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.5/src/volumeutil.cpp000066400000000000000000000011431301464733100160760ustar00rootroot00000000000000#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; }