pax_global_header00006660000000000000000000000064143233437730014523gustar00rootroot0000000000000052 comment=c893bc11fcb3787a6b067ba9d27205af9b4390d1 studio-controls-2.3.9/000077500000000000000000000000001432334377300147065ustar00rootroot00000000000000studio-controls-2.3.9/.bzrignore000066400000000000000000000000131432334377300167020ustar00rootroot00000000000000./MANIFEST studio-controls-2.3.9/.gitignore000066400000000000000000000037031432334377300167010ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### VirtualEnv template # Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python #[Bb]in [Ii]nclude #[Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json # backup files *~ ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ #lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .idea/ studio-controls-2.3.9/AUTHORS000066400000000000000000000004371432334377300157620ustar00rootroot00000000000000Authors: Kaj Ailomaa Len Ovens Erich Eickmeyer Copyright: Andrew Hunter, Luis de Bethencourt Guimera 2007 Kaj Ailomaa 2012 Len Ovens 2012-2020 Erich Eickmeyer 2020 studio-controls-2.3.9/CHANGELOG000066400000000000000000000322241432334377300161230ustar00rootroot00000000000000studio-controls version 2.3.9 [ Len Ovens ] - Fixed makefile to reflect new desktop file name (issue #84) studio-controls version 2.3.8 [ Len Ovens ] - Do all governor setting from autojack not systemd - Do all Boost setting from autojack not systemd studio-controls version 2.3.7 [ Len Ovens ] - Ensure value exists before using in auto_jack.py - Ensure value exists before using in autojack - Don't read empty lock file studio-controls version 2.3.6 [ Len Ovens ] - Extra delay when starting to avoid race condition - Set all DBUS call backs to only set a variable - Check_jack_status now runs actual dbus calls to prevent dbus loop studio-controls version 2.3.5 [ Len Ovens ] - Properly prevent duplicate device records - Skip device numbers that don't exist but look for higher numbers - Fix type, compare string to string studio-controls version 2.3.4 [ Len Ovens ] - When adding extra devices allow zita to complete before next device (issue #80) - Don't try to detect valid SR, use device info files instead (issue #81) - Reinitialize alsa devices before jack startup studio-controls version 2.3.3 [ Len Ovens ] - Use dbus calls to detect jack state - Make sure device pid is removed when zita-ajbridge is killed - Make sure there is only one pulseaudio running studio-controls version 2.3.2 [ Len Ovens ] - Move studio-controls-convert to a module - Change makefile for file differences - Device scanning in convert uses installed devices - Studio-controls uses module device scan (issue #75) - Autojack uses module for device scan - Fix pke spelling (lp #1969827) - Fix missing globals - Make python scripts coding style work with pep8 - Default zita-ajbridge name should not be none - Fix headphone switching for non-default setup - Change startup to xdg autostart (lp #1972896) - Don't switch to jack port unless it is there - Don't error out if connection already exists - Make sure jack latency dropdown has valid selection - Add commandline utility for some operations - Use direct query to jackdbus to check for Running - Don't allow running as system or root - Use file locking to make sure a read get the whole database - Headphones now include port number and can use JACK master - Update man pages - Fixed many bugs studio-controls version 2.3.1 [ Len Ovens ] -Monitor should test for system: (issue #77) -Prevent hang if alsaaudio can't access mixer (lp #1964862) studio-controls version 2.3.0 [ Len Ovens ] -Test path correctly (issue #76) studio-controls version 2.2.9 [ Len Ovens ] - Remove reference to zdev - make sure empty lock file works - Don't kill Pulse alsa if no bridges - Fix spelling mistake (issue #72) - Change logging name to AutoJack instead of root - Allow setting -u for a2jmidid (issue #63) - use path for spawning subprocesses - Use either pavucontrol or pavucontrol-qt - Use python dbus instead of dbus-send - Change autojack status to use dbus parameter - Don't accept USB plug change if device list has not changed [ Erich Eickmeyer ] - Change icon and .desktop filenames to avoid confusion with Android Studio - Also complies with latest XDG standards - Update to use new icon filename in application titlebar studio-controls version 2.2.8 [ Len Ovens ] - USB devices may be less than 3 char - Convert old style int to name (issue #70) studio-controls version 2.2.7 [ Len Ovens ] - Device rates not getting set in studio-controls (lp #1946572) studio-controls version 2.2.6 [ Len Ovens ] - Initialize variable (lp #1946268) studio-controls version 2.2.5 [ Len Ovens ] - fix no rates found for card (lp #1938075) studio-controls version 2.2.4 [ David Marzal ] - autojack-start should be executable studio-controls version 2.2.3 [ Len Ovens ] - Run convert at session start to ensure config exists (lp #1944607) - Convert should not add a none device to device db - Fix default device selection (lp #1944607) studio-controls version 2.2.2 [ Erich Eickmeyer ] - Wrap autojack startup - Update studio.service - Change scalable icon to plain svg (was inkscape) - Add additional icon sizes [ Len Ovens ] - add debug messages studio-controls version 2.2.1 [ Len Ovens ] - force the firewire key to exist in all devices studio-controls version 2.2.0 [ Len Ovens ] -Release 2.2.0 studio-controls version 2.1.72 [ Len Ovens ] - Use dbus to query jack for xruns (issue #62) - Use dbus to query jack for DSP value studio-controls version 2.1.71 [ Len Ovens ] - If device name is already converted use the converted name. studio-controls version 2.1.70 [ Len Ovens ] - Fix issue #56 init variable - Fix false not applied trigger - Xrun count done in autojack so count remains through controls restart studio-controls version 2.1.69 [ Len Ovens ] - First run default device if no internal device fix - Fix bug if no rates in list (issue #59) - Code cleanup - Default device should bridge by default - Unused Jack Master should be a usable extra device studio-controls version 2.1.68 [ Len Ovens ] - Fix USB plug in/out detection (dbus changed) - Change convert to match - Make studio-controls use convert from correct path - fix window size change from jack status - Use correct version when checking if our version config exists - Fix JACK status to show more info - Fix USB devices not connected at jack startup - Shutdown a2jmidid properly for less error messages studio-controls version 2.1.67 [ Len Ovens ] - Fix spelling and punctuation - Add if statment to only see real devices studio-controls version 2.1.66 [ Len Ovens ] - Fix extra mixers opening - Make sure all devices are fully populated studio-controls version 2.1.65 [ Len Ovens ] - Change qasmixer button to drop down fixes no device - Convert checks each value for sanity - Convert checks for duplicate devices studio-controls version 2.1.64 [ Len Ovens ] - fix spelling mistake that prevents a2jmidid start studio-controls version 2.1.63 [ Len Ovens ] - Fix autojack hang - Fix various usbdev bugs - Fix zita-njbridge install warning studio-controls version 2.1.62 [ Len Ovens ] - Bugfix, convert usbdev correctly studio-controls version 2.1.61 [ Len Ovens ] - Bugfix, don't close jack client if it fails to open studio-controls version 2.1.60 [ Len Ovens ] - Add not applied dialog on exit - Add user script for pre/post start/stop - Add custom extra device naming - Re-imagine the GUI - Add qmidinet starting for networked MIDI - Add zita-njbridge for networked audio - Add JACK and bridge extra latency - Fixed bridge find rate - Fixed device number setting in device DB - Add post bridge script - fix JACK status not restarting on Apply - fix audio net bridge not accepting changes - IP/port should be for the receiver - Make fields not applicable for direction insensitive studio-controls version 2.1.4 [ Len Ovens ] - Apparently regex doesn't see : filter out bad positives studio-controls version 2.1.3 [ Len Ovens ] - Catch jack connect exceptions - Search for clients with : included to prevent false positive studio-controls version 2.1.2 [ Len Ovens ] - fix missing parameter default set - fix check jack client exists studio-controls version 2.1.1 [ Len Ovens ] - Fix won't run from /usr/local - Fix convert-studio-controls logic studio-controls version 2.1.0 [ Len Ovens ] - Set version to 2.1.0 release studio-controls version 2.0.26-pre [ Len Ovens ] - Add docs directory and start documentation - Move docs to docs branch - Limit sample rates to those device supports - Add webpage to README studio-controls version 2.0.25-pre [ Len Ovens ] - GUI was setting a config param with string instead of bool - Convert utility will update config file version studio-controls version 2.0.24-pre [ Len Ovens ] - Fix pulse widgets at startup - Add more tooltips - Remove duplicate device in USB JACK master - Don't remove device type once set - Reset device number before device scan - Make sure jack client is cleaned up with USB master unplug - Don't restart jack unless we need to. - Fix version file name in convert - Add JACK log file rotate studio-controls version 2.0.23-pre [ Len Ovens ] - Bug fixes - Add version file - Arrange widgets in jack master and extra devices - Skip USB MIDI devices studio-controls version 2.0.21 [ Len Ovens ] - Change GUI to add channel count to Pulse-jack bridges - Add pulse bridge channel count to autojack as well - Clean up code in studio-controls file - Add Session Manager tab - Add jack connection mode setting - Move systemd to usr/lib - Add a makefile - Make pulse connections use count as well - JACK should be all caps in the GUI - Add session manager launch buttons - Moved first jack startup out of main for crash proofing - Moved jack callbacks out of main for crash proofing - Use direct dbus calls instead of executing jack_control - Don't list loopback devices or put them last - set default icon - change config file to json to allow easier grouping of device data - Allow setting each device's parameters - Change GUI to show one extra device at a time - Allow setting non-USB device backup when USB device in use - Update ping version to make sure autojack will use new config file - Fix extra devices latency limits - Bugfixes studio-controls version 2.0.9 [ Len Ovens ] - Wrong variable used causing incorrect port to be chosen (lp: #1898369) studio-controls version 2.0.8 [ Len Ovens ] - Catch value 0 from old config file (bug fix) studio-controls version 2.0.7 [ Len Ovens ] - "MONITOR" should never be 'none' studio-controls version 2.0.6 [ Len Ovens ] - bugfix for variable used before initialized studio-controls version 2.0.5 [ Len Ovens ] - Add lockfile to autojack to prevent two instances running - Add lockfile to studio-controls to prevent two instances running - Depend on zita-ajbridge version 0.8.4 fixes (lp: #1889146) - Fix phones detection for USB phones when not plugged in - Catch jack error messages so they don't alarm user - studio-controls: Catch system signals to exit - up autojack signal version studio-controls version 2.0.4 [ Len Ovens ] - Fix spelling mistake - Fix wrong indent - Add "NVidia" to HDMI names - Add a readout of the configuration to logging studio-controls version 2.0.3 [ Len Ovens ] - Make sure to use default-device and not PCH everywhere - Catch pulse connection port is integer - no more support for older config file studio-controls version 2.0.2 [ Len Ovens ] - logging should have log level set at read config file - autojack needs to import glob before using it - bug fix studio-controls version 2.0.1 [ Len Ovens ] - Use saved value for device instead index - bug fix - Log file too long re-enable log rotate - bugfix - dynamicly set default audio device - bugfix - Make sure bridges are created before connecting - Check if headphone device exists before checking (lp: #1888588) studio-controls version 2.0.0 [ Erich Eickmeyer ] - Add white glow to icon to increase visibilty [ Len Ovens ] - Head phone detection for PCH devices added - Force PCH devices to be at least 128 buffer in extra devices - Remove old jackdbus settings file before starting jack - Pulse connect port extended to all Physical ports - Fixed extra devices that are not sub device 0 don't work - Fixed Apply for input only - USB units with sub devices other than 0 will now auto connect - Added extra logging level for lots of output - Added better method of finding device sample rate - Added new depend: python3-alsaaudio - Detect headphone plug state on startup - Use direct alsa mixer manipulation - Add manual headphone switching - Add ability use outputs other than system:playback_1/2 - general code clean up - Readded Firewire backend - Added fixes for alsa firewire devices - make sure extra devices work correctly with all backends studio-controls version 1.99.1 - add Blacklist to keep things like webcams adding a mic - clean up white space in studio_system - Add CHANGELOG file and clean up notes.txt - Extra HDI devices have forced buffer size of 4096 studio-controls version 1.99.0 - Update to new icon - remove tablet code as direction has changed - finish rebrand studio-controls version 1.12.5 - Check if device has a description before trying to use it studio-controls version 1.12.4 - Start rebranding to plain Studio Controls - Add systemd service for non-Ubuntu systems - Fix USB bridge checkbox so it actually changes states (LP #1872250) - Finish rebranding (icons are placeholders) studio-controls version 1.12.3 - Change adduser to usermod -aG - Update copyright & AUTHORS studio-controls version 1.12.0 - Add logging level control to GUI - Change from autostart to systemd unit - Make sure session ends so audio device is not locked studio-controls version 1.11.3 - Bug fixes: check if device removed signal is true Save last valid internal device so removing USB device allows controls to switch to something sane. - moved config file to it's own directory so that we can add more files like post start scripts. - Converted config file to use configparser for less accident prone maintenance. - Declutter audio setup GUI by splitting it into three tabs - Pulse bridges can have custom names - pulse bridges can have custom auto connects studio-controls-2.3.9/COPYING000066400000000000000000000354221432334377300157470ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS studio-controls-2.3.9/INSTALL000066400000000000000000000021041432334377300157340ustar00rootroot00000000000000Installing Studio Controls ========================== in a terminal enter: sudo make install The standard install is into /usr/local/* Packagers can make use of the ‘PREFIX’ and ‘DESTDIR’ variable during install, like this: $ make install PREFIX=/usr DESTDIR=./test-dir To remove: sudo make uninstall Note: if PREFIX= or DESTDIR= were used for install then they must be used for uninstall as well Please Note: Studio Controls is not meant to run on the same machine as other session based jack controllers like Cadence. In fact, if Cadence has ever been run on the same partition it is possible that studio-controls will not work properly even if Cadence has been removed later because Cadence leaves extra config files around that interfere with the operation of studio-controls. Packagers should mark Cadence as something that conflicts with studio-controls. This software should not be packaged as a flatpack, snap or other sandbox style package. These packaging schemes do not work well with jack aware applications or audio applications that use audio plugins. studio-controls-2.3.9/Makefile000066400000000000000000000114171432334377300163520ustar00rootroot00000000000000#!/usr/bin/make -f CWD=source # --------------------------------------------------------------------------------------------------------------------- PREFIX := /usr/local BINDIR := $(PREFIX)/bin SBINDIR := $(PREFIX)/sbin LIBDIR := $(PREFIX)/lib DATADIR := $(PREFIX)/share DESTDIR := all: echo "build finished" install: # this one is special, it has to go in /etc install -d /etc/acpi/events install -m 644 etc/acpi/events/* /etc/acpi/events install -m 644 etc/acpi/studio.sh /etc/acpi # Everything else is $(DESTDIR) install -d $(DESTDIR)$(DATADIR)/applications install -d $(DESTDIR)$(DATADIR)/icons/hicolor/16x16/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/22x22/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/24x24/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/48x48/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/64x64/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/128x128/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/256x256/apps install -d $(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps install -d $(DESTDIR)$(DATADIR)/man/man1 install -d $(DESTDIR)$(DATADIR)/man/man2 install -d $(DESTDIR)$(DATADIR)/polkit-1/actions install -d $(DESTDIR)$(DATADIR)/studio-controls install -d $(DESTDIR)$(BINDIR) install -d $(DESTDIR)$(SBINDIR) install -d $(DESTDIR)$(LIBDIR)/systemd/user/default.target.wants install -d $(DESTDIR)$(LIBDIR)/systemd/user/indicator-messages.service.wants install -d $(DESTDIR)$(LIBDIR)/python3/dist-packages # now the files install -m 644 usr/share/applications/com.github.ovenwerks.studio-controls.desktop \ $(DESTDIR)$(DATADIR)/applications install -m 644 usr/share/icons/hicolor/16x16/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/16x16/apps install -m 644 usr/share/icons/hicolor/22x22/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/22x22/apps install -m 644 usr/share/icons/hicolor/24x24/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/24x24/apps install -m 644 usr/share/icons/hicolor/48x48/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/48x48/apps install -m 644 usr/share/icons/hicolor/64x64/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/64x64/apps install -m 644 usr/share/icons/hicolor/128x128/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/128x128/apps install -m 644 usr/share/icons/hicolor/256x256/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/256x256/apps install -m 644 usr/share/icons/hicolor/scalable/apps/* \ $(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps install -m 644 usr/share/man/man1/* \ $(DESTDIR)$(DATADIR)/man/man1 install -m 644 usr/share/man/man2/* \ $(DESTDIR)$(DATADIR)/man/man2 install -m 644 usr/share/polkit-1/actions/* \ $(DESTDIR)$(DATADIR)/polkit-1/actions install -m 644 usr/share/studio-controls/* \ $(DESTDIR)$(DATADIR)/studio-controls install -m 655 usr/bin/* \ $(DESTDIR)$(BINDIR) install -m 655 usr/sbin/* \ $(DESTDIR)$(SBINDIR) install -m 644 usr/lib/systemd/user/session-monitor.service \ $(DESTDIR)$(LIBDIR)/systemd/user install -m 655 usr/lib/python3/dist-packages/* \ $(DESTDIR)$(LIBDIR)/python3/dist-packages # make links ln -s $(DESTDIR)$(LIBDIR)/systemd/user/session-monitor.service \ $(DESTDIR)$(LIBDIR)/systemd/user/indicator-messages.service.wants/session-monitor.service # --------------------------------------------------------------------------------------------------------------------- uninstall: rm -f /etc/acpi/studio-* rm -f /etc/acpi/studio.sh rm -f $(DESTDIR)$(DATADIR)/applications/com.github.ovenwerks.studio-controls.desktop rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/16x16/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/22x22/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/24x24/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/48x48/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/64x64/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/128x128/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/256x256/apps/studio-controls.png rm -f $(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/studio-controls.svg rm -f $(DESTDIR)$(DATADIR)/man/man1/studio-controls.* rm -f $(DESTDIR)$(DATADIR)/man/man1/studio-cmd.* rm -f $(DESTDIR)$(DATADIR)/man/man2/autojack.* rm -f $(DESTDIR)$(DATADIR)/man/man2/studio-system.* rm -f $(DESTDIR)$(DATADIR)/polkit-1/actions/com.studiocontrols.pkexec.studio-controls.policy rm -rf $(DESTDIR)$(DATADIR)/studio-controls rm -f $(DESTDIR)$(BINDIR)/autojack rm -f $(DESTDIR)$(BINDIR)/studio-cmd rm -f $(DESTDIR)$(BINDIR)/studio-controls rm -f $(DESTDIR)$(SBINDIR)/studio-system rm -f $(DESTDIR)$(LIBDIR)/python3/dist-packages/auto_jack.py rm -f $(DESTDIR)$(LIBDIR)/systemd/user/session-monitor.service rm -f $(DESTDIR)$(LIBDIR)/systemd/user/indicator-messages.service.wants/session-monitor.service studio-controls-2.3.9/README000066400000000000000000000020651432334377300155710ustar00rootroot00000000000000Studio Controls This is a small application to allow setting up audio for (semi)pro audio work. The application first checks to see if application will have access to memory locking and real time priorities. If not, a warning is displayed and a checkbox allows repairing this. After repairing this the user needs to log out and back in for the new setting to have effect, there is a warning to this effect. It is assumed that jack will run at session start and not be stopped till session end. However, it is possible to override this. USB mics have started to make their way into bedroom studios and have been a problem with jack or ALSA oriented audio applications which expect to work with only one audio device. The user expects to use their USB mic for input and internal audio for monitoring. These two devices have no way of being in sync with each other and in most cases cannot be used together. This application allows setting one jack device and adding a second device as a jack client via zita-ajbridge. Home Page: https://ovenwerks.github.io/studio-controls/ studio-controls-2.3.9/ROADMAP000066400000000000000000000015761432334377300157250ustar00rootroot00000000000000 This is Notes about developing this application, known bugs and features to add. It is generally in order of importance... or at least order to be worked on. (some later things rely on sooner things) Fixes: These things need to be looked for in 2.3.3 TODO: - detect if pulse is pw - Future Features: - make help work with current tab - remove warning dialogs and create on the fly as needed - Add packagers file describing depends/conflicts - Add multiple devices for firewire - bluetooth via pulse. use: pacmd load-module module-loopback latency_msec=5 and: pacmd unload-module module-loopback need to figue out how to auto route jack->pulse bridge to loop and loop to bt device. Note: not worth doing. Pipewire will do this for us. - adding in stuff from https://github.com/jhernberg/udev-rtirq so that hot plugged USB devices get higher priority. studio-controls-2.3.9/etc/000077500000000000000000000000001432334377300154615ustar00rootroot00000000000000studio-controls-2.3.9/etc/acpi/000077500000000000000000000000001432334377300163755ustar00rootroot00000000000000studio-controls-2.3.9/etc/acpi/events/000077500000000000000000000000001432334377300177015ustar00rootroot00000000000000studio-controls-2.3.9/etc/acpi/events/studio-plug000066400000000000000000000001041432334377300220730ustar00rootroot00000000000000event=jack/headphone HEADPHONE plug action=/etc/acpi/studio.sh plug studio-controls-2.3.9/etc/acpi/events/studio-unplug000066400000000000000000000001101432334377300224330ustar00rootroot00000000000000event=jack/headphone HEADPHONE unplug action=/etc/acpi/studio.sh unplug studio-controls-2.3.9/etc/acpi/studio.sh000077500000000000000000000005231432334377300202430ustar00rootroot00000000000000#!/bin/sh # send signal to autojack /usr/bin/dbus-send --system --type=signal / org.studio.control.event.${1}_signal # Note: in most cases -controls can automatoically switch from speakers # to phones and back via alsa controling. In some cases pin # function needs to be changed as root. This would be the place to # add that below. studio-controls-2.3.9/etc/xdg/000077500000000000000000000000001432334377300162435ustar00rootroot00000000000000studio-controls-2.3.9/etc/xdg/autostart/000077500000000000000000000000001432334377300202715ustar00rootroot00000000000000studio-controls-2.3.9/etc/xdg/autostart/autojack.desktop000066400000000000000000000003561432334377300234710ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Name=AutoJACK Comment=Start the AutoJACK Sound System Exec=autojack Terminal=false Type=Application X-GNOME-Autostart-Phase=Initialization X-GNOME-HiddenUnderSystemd=true X-KDE-autostart-phase=1 NoDisplay=true studio-controls-2.3.9/testing000066400000000000000000000012531432334377300163070ustar00rootroot00000000000000TESTING Controls: ================= At present, controls should be tested for: - defaults are correct (no config file) both in running instance (autojack autostarts) - check pavucontrol shows devices and sends sound through and in GUI - not in performance - boost (if available) is on Audio should show correct defaults - config file in ~/.config/autojackrc is read and used. in running instance In GUI when gui saves this file should vanish - config file in ~/.config/autojack/autojackrc in running instance start jack change everything make sure whats actually runnig echoes config in GUI Gui should show last config change studio-controls-2.3.9/usr/000077500000000000000000000000001432334377300155175ustar00rootroot00000000000000studio-controls-2.3.9/usr/bin/000077500000000000000000000000001432334377300162675ustar00rootroot00000000000000studio-controls-2.3.9/usr/bin/autojack000077500000000000000000002267541432334377300200360ustar00rootroot00000000000000#!/usr/bin/python3 -u # autojack - Monitors dbus for added audio devices # (hot plugged USB audio intefaces) # on detect it does one of three things: # makes it the jack device # makes it a jack client (via zita-ajbridge) # nothing # # Monitors acpi for headphone un/plug and reroutes audio if needed # # autojack also monitors dbus for messages from studio-controls to: # - stop jack # - re/start jack # - remove a USB device as jack master to allow safe device removal # - reread ~/.config/autojackrc and apply any changes import alsaaudio import copy import dbus import dbus.service import dbus.exceptions import dbus.mainloop.glib import glob import jack import json import logging import os import re import shlex import shutil import signal import subprocess import sys import time from gi.repository import GLib from os.path import expanduser global name_base global control_interface_name global configure_interface_name global service_name global install_path install_path = os.path.abspath(f"{sys.path[0]}/..") sys.path.insert(1, f"{install_path}/lib/python3/dist-packages") import auto_jack name_base = 'org.jackaudio' control_interface_name = name_base + '.JackControl' configure_interface_name = name_base + '.Configure' service_name = name_base + '.service' class sendbus(dbus.service.Object): def __init__(self): dbus.service.Object.__init__(self, dbus.SessionBus(), "/") @dbus.service.signal(dbus_interface="org.studio.control.command", signature="s") def signal(self, sg): logging.log(7, f"sent signal {sg}") print(f"sent it {sg}") pass @dbus.service.signal(dbus_interface="org.studio.control.state", signature="ss") def state(self, vr, st): logging.log(7, f"sent state ver: {vr} status: {st}") pass def get_raw(device): ''' given a USB*,*,* device name return a true (raw) dev,*,* string''' global conf_db if device == 'none': return 'none' un, ud, us = device.split(',') if un in conf_db['devices']: rn = conf_db['devices'][un]['raw'] return f"{rn},{ud},{us}" else: return 'none' def get_db(device): ''' give a device string (raw or db nameed) return the data base for that device''' global conf_db name = device.split(',')[0] if name in conf_db['devices']: return conf_db['devices'][name] else: for dev in conf_db['devices']: if conf_db['devices'][dev]['raw'] == name: return conf_db['devices'][dev] def extra_devices(): ''' set up all extra device as per configuration ''' global conf_db global last_master global phones global jack_alive logging.debug("updating extra devices") if not jack_alive: logging.debug("Jack is not running, why are we adding extra devices?") return # no use checking anything else import_config("extra_devices") # a few loops for dev in conf_db['devices']: time.sleep(.5) rawdev = conf_db['devices'][dev]['raw'] numst = conf_db['devices'][dev]['number'] usb = bool(conf_db['devices'][dev]['usb'] and conf_db['extra']['usbauto']) for sub in conf_db['devices'][dev]['sub']: fullname = f"{dev},{sub},0" sub_db = conf_db['devices'][dev]['sub'][sub] logging.debug( f"checking: {fullname} channel counts: " f"{sub_db['play-chan']} " f"{sub_db['cap-chan']} card: {numst}") if sub_db['hide'] or int(conf_db['devices'][dev]['number']) == -1: logging.debug(f"{fullname} is hidden or unplugged, no bridge") if sub_db['cap-pid'] or sub_db['play-pid']: kill_slave(fullname, conf_db['devices']) break if get_raw(fullname) == last_master: logging.debug(f"{fullname} is master, no bridge") break if fullname == conf_db['extra']['phone-device']: if phones: if not sub_db['play-pid']: start_slave(fullname) break if sub_db['play-chan'] or sub_db['cap-chan']: if not sub_db['cap-pid'] or not sub_db['play-pid']: start_slave(fullname) break else: if sub_db['cap-pid'] or sub_db['play-pid']: kill_slave(fullname, conf_db['devices']) logging.debug("updating extra devices complete") def net_bridge(): ''' set up all audio network bridges as per configuration ''' global conf_db logging.debug("updating Audio network bridges") if not conf_db['jack']['on']: return # no use checking anything else kla = shutil.which("killall") if kla is None: logging.warning("unable to kill old bridges missing killall") else: cp = subprocess.run([kla, "zita-n2j"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) cp1 = subprocess.run([kla, "zita-j2n"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug( f"kill old zita-njbridge: {cp.stdout.strip()}{cp1.stdout.strip()}") for bridge in conf_db['znet']: br_db = conf_db['znet'][bridge] cmd = "" cnt_str = "--chan " late_bits = "" if br_db['direction'] == "out": cmd = "zita-j2n " cnt_str = f"{cnt_str}{str(br_db['count'])}" late_bits = f"--{br_db['bits']}" elif br_db['direction'] == "in": cmd = "zita-n2j" late_bits = f"--buff {str(br_db['latency'])}" for i in range(1, (br_db['count'] + 1)): if i == br_db['count']: cnt_str = f"{cnt_str}{str(i)}" else: cnt_str = f"{cnt_str}{str(i)}," else: logging.warning(f"audio network bridge {bridge} invalid") continue fcmd = shutil.which(cmd) if fcmd is None: logging.warning( f"command {cmd} not found, bridge: " f"{bridge} can not be started") continue cmd = f"{fcmd} --jname {bridge} {cnt_str} " f"{late_bits} {br_db['ip']} {str(br_db['port'])}" subprocess.Popen(shlex.split(cmd), shell=False).pid logging.debug("updating network audio bridges complete") def import_config(caller): ''' sets default parmeters, then reads values from configuration file''' global config_path global config_file global conf_db global fw_exists global install_path global phone_port global startup phone_port = 'none' logging.log(7, f"import_config called by: {caller}") if startup: conf_db = auto_jack.convert(False) startup = False else: conf_db = auto_jack.convert() # logging.log(7, json.dumps(conf_db, indent = 4)) # print(json.dumps(conf_db, indent = 4)) devices = conf_db['devices'] if os.path.exists("/etc/modprobe.d/blacklist-studio.conf"): fw_exists = True else: for this_dev in devices: if devices[this_dev]['firewire']: fw_exists = True for pout in conf_db['pulse']['outputs']: if conf_db['pulse']['outputs'][pout]['connection'] == "monitor": conf_db['pulse']['outputs'][ pout]['connection'] = conf_db['extra']['monitor'] if conf_db['jack']['driver'] == 'firewire': # we don't save this, but treat usbdev as none conf_db['jack']['usbdev'] = 'none' if 'phone-device' in conf_db['extra']: if 'phone-left' not in conf_db['extra']: conf_db['extra']['phone-left'] = "1" hp_lft = conf_db['extra']['phone-left'] hp_d_l = conf_db['extra']['phone-device'].split(',', 2) if len(hp_d_l) == 3: dnm, dnum, sub = hp_d_l if dnm in conf_db['devices']: hp_db = conf_db['devices'][dnm] if dnum in hp_db['sub']: hp_sub_db = hp_db['sub'][dnum] phone_port = f"{hp_sub_db['name']}-out:" phone_port = f"{phone_port}playback_{hp_lft}" elif conf_db['extra']['phone-device'] == 'system': phone_port = f"system:playback_{hp_lft}" logger = logging.getLogger() logger.setLevel(int(conf_db['log-level'])) if caller == "config_start" or caller == "reconfig": logging.log(7, f"log level: {str(conf_db['log-level'])}") logging.log(7, json.dumps(conf_db, indent=4)) logging.debug(f"phone port set to: {phone_port}") def reconfig(): '''reads values from configuration file and changes run to match. This tries to do this without stopping jack if not needed''' global phones global conf_db global last_master global midiproc logging.debug("reconfigure autojack") old_conf_db = copy.deepcopy(conf_db) import_config("reconfig") ol_jkdb = old_conf_db['jack'] jackdb = conf_db['jack'] old_jack = [ol_jkdb['on'], ol_jkdb['driver'], ol_jkdb['dev'], ol_jkdb['rate'], ol_jkdb['frame'], ol_jkdb['period'], ol_jkdb['connect-mode'], ol_jkdb['chan-in'], ol_jkdb['chan-out'], ol_jkdb['usbdev'], ol_jkdb['cap-latency'], ol_jkdb['play-latency']] new_jack = [jackdb['on'], jackdb['driver'], jackdb['dev'], jackdb['rate'], jackdb['frame'], jackdb['period'], jackdb['connect-mode'], jackdb['chan-in'], jackdb['chan-out'], jackdb['usbdev'], jackdb['cap-latency'], jackdb['play-latency']] if old_jack != new_jack: logging.debug("change requires restart") config_start() return if not conf_db['jack']['on']: return # no use checking anything else pulse_dirty = False if [old_conf_db['pulse']['inputs'], old_conf_db[ 'pulse']['outputs']] != [ conf_db['pulse']['inputs'], conf_db['pulse']['outputs']]: pc = shutil.which("pactl") if pc is None: logging.warning( "pactl is missing... unable to control" "pulseaudio\n reconfigure failed") return pulse_dirty = True logging.debug("reconfiguring pulse bridges") if len(conf_db[ 'pulse']['inputs']) or len(conf_db['pulse']['outputs']): cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-udev-detect"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove module-udev-detect: {cp.stdout.strip()}") cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-alsa-card"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove module-alsa-card: {cp.stdout.strip()}") else: cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-udev-detect"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug("load module-udev-detect: " f"{cp.stdout.strip()}") if old_conf_db['pulse']['inputs'] != conf_db['pulse']['inputs']: cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jack-source"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove jackd_source: {cp.stdout.strip()}") for bridge in conf_db['pulse']['inputs']: this_count = str( conf_db['pulse']['inputs'][bridge]['count']) cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-source", f"client_name={bridge}", f"channels={this_count}", "connect=no"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Load jackd_source: {cp.stdout.strip()}") if old_conf_db['pulse']['outputs'] != conf_db['pulse']['outputs']: logging.debug("reconfiguring pulse bridges") cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jack-sink"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"PA unload jack-sink: {cp.stdout.strip()}") for bridge in conf_db['pulse']['outputs']: this_count = str( conf_db['pulse']['outputs'][bridge]['count']) cp = subprocess.run(["/usr/bin/pactl", "load-module", "module-jack-sink", f"client_name={bridge}", f"channels={this_count}", "connect=no"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"PA load jack-sink: {cp.stdout.strip()}") if old_conf_db['extra']['a2j'] != conf_db['extra']['a2j']: logging.debug("reconfiguring MIDI bridge") if 'midiproc' in globals(): midiproc.send_signal(signal.SIGINT) try: rt = midiproc.wait(timeout=15) except subprocess.TimeoutExpired: logging.debug(f"kill a2jmidid failed") try: os.kill(int(sub_db['cap-pid']), 9) except Exception: print("") if conf_db['extra']['a2j']: # Can't add logging for background processes. # Will show up in syslog ajc = shutil.which("a2jmidid") if ajc is None: logging.Warning( "a2jmidid not found, is it installed? " "Starting MIDI-JACK bridge failed.") else: up = "" if 'a2j_u' in conf_db['extra']: if conf_db['extra']['a2j_u']: up = "-u" cmd = f"{ajc} -e {up}" midiproc = subprocess.Popen( shlex.split(cmd), shell=False) if [old_conf_db['mnet']['count'], old_conf_db['mnet']['type']] != [conf_db['mnet']['count'], conf_db['mnet']['type']]: logging.debug("reconfiguring MIDI network") cp = subprocess.run(["/usr/bin/killall", "qmidinet"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"kill old qmidinet: {cp.stdout.strip()}") if conf_db['mnet']['count']: mint = shutil.which("qmidinet") if mint is None: logging.Warning( "qmidinet not found, is it installed?" "Starting networked midi failed.") else: mtype = "" if conf_db['mnet']['type'] == 'jack': mtype = "--alsa-midi=no --jack-midi=yes" cmd = f"{mint} --num-ports={str(conf_db['mnet']['count'])}" f"--no-gui {mtype}" subprocess.Popen(shlex.split(cmd), shell=False).pid # extra devices compares what is to config and fixes things logging.debug("Check extra devices") extra_devices() net_bridge() if conf_db['extra'][ 'phone-device'] != old_conf_db[ 'extra']['phone-device']: logging.debug("reconfigure Headphones setup") phones = False phones_check() if conf_db['extra']['monitor'][:7] != "system:" and not phones: logging.debug("Check main outputs") mon_dev, ldev, temp = conf_db['extra']['monitor'].split('-') if not conf_db['devices'][ mon_dev]['sub'][ldev] and conf_db[ 'jack']['driver'] != "alsa" or mon_dev != last_master: start_slave(mon_dev) con_dirty = True if pulse_dirty: # do this last after all bridges are created connect_pa() def config_start(): ''' Pulls configuration and force restarts the world ''' global conf_db global configure_interface_name global control_interface_name global fw_exists global jack_alive global jack_died global last_master global midiproc global name_base global phones global service_name if jack_alive: kill_jack_client() logging.info("Running: config_start()") import_config("config_start") # if at session start we should wait a few seconds for pulse # to be fully running time.sleep(2) logging.debug("Running: config_start jack stat") jack_stat("Stopping...") logging.info("Running: config_start after jack_stat") fpath = expanduser('~/.config/autojack/prestop') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): logging.debug("Found prestop script") cp = subprocess.run([fpath], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath}") if 'midiproc' in globals(): midiproc.send_signal(signal.SIGINT) try: rt = midiproc.wait(timeout=15) except subprocess.TimeoutExpired: logging.debug(f"kill a2jmidid failed") try: os.kill(int(sub_db['cap-pid']), 9) except Exception: print("") pc = shutil.which("pactl") if pc is None: logging.warning( "pactl is missing... unable to control" "pulseaudio\n configure failed") else: cp = subprocess.run([pc, "unload-module", "module-jack-source"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Pulseaudio: {cp.stdout.strip()}") time.sleep(1) cp = subprocess.run([pc, "unload-module", "module-jack-sink"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Pulseaudio: {cp.stdout.strip()}") time.sleep(1) bus = dbus.SessionBus() controller = bus.get_object(service_name, "/org/jackaudio/Controller") control_iface = dbus.Interface(controller, control_interface_name) # configure_iface = dbus.Interface(controller, configure_interface_name) try: control_iface.StopServer() except Exception: print("stop failed") time.sleep(2) cp = subprocess.run(["alsactl", "init"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) # Stop jack if running kla = shutil.which("killall") if kla is None: logging.warning("killall not found, audio controls may fail to work") else: cp = subprocess.run([kla, "-q", "-9", "jackdbus", "jackd", "a2jmidid", "pulseaudio"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Kill old Procs: {cp.stdout.strip()}") cp = subprocess.run(["alsactl", "init"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) jack_stat("Stopped") last_master = 'none' fpath = expanduser('~/.config/autojack/poststop') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): logging.debug("Found poststop script") cp = subprocess.run([fpath], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath}") if (conf_db['jack']['driver'] == "firewire") or fw_exists: time.sleep(3) # this is a firewire device, A busreset makes for stability ftst = shutil.which("ffado-test") if ftst is None: logging.warning( "ffado-test not found, fire wire device may" "lock up. ffado-tools not installed?") else: cp = subprocess.run([ftst, "BusReset"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"reset firewire bus: {cp.stdout.strip()}") time.sleep(3) if kla is None: logging.warning( "killall not found, audio controls may fail to work") else: cp = subprocess.run([kla, "-q", "ffado-dbus-server", "ffado-mixer"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug( "Kill ffado mixer because busreset needed:" f"{cp.stdout.strip()}") # restart Pulse to reload default modules which we may have removed # bus = dbus.SessionBus() # bus already set above systemd1 = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager') job = manager.RestartUnit('pulseaudio.service', 'fail') logging.debug(f"JACK is {str(conf_db['jack']['on'])} restart pulse: {job.strip()}") if not conf_db['jack']['on']: return logging.debug(f"JACK is {str(conf_db['jack']['on'])} start up jack") jack_stat("Config Pulse") # Assume start of session where pulse may be fully loaded # get rid of anything that can automatically interfere cp = subprocess.run(["/usr/bin/pactl", "unload-module", "module-jackdbus-detect"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove jackdbus_detect: {cp.stdout.strip()}") fpath = expanduser('~/.config/autojack/prestart') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): logging.debug("Found prestart script") jack_stat("Prestart running") cp = subprocess.run([fpath], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath}") jack_stat("Configuring...") rn = 'none' if conf_db['jack']['usbdev'] != "none": rn = get_db(conf_db['jack']['usbdev'])['raw'] if os.path.exists(f"/proc/asound/{rn}"): mdev = get_raw(conf_db['jack']['usbdev']) else: mdev = conf_db['jack']['dev'] if mdev.split(',')[0] == "HDMI" or mdev.split(',')[0] == "NVidia": logging.info("HDMI device, setting buffer to 4096") conf_db['jack']['frame'] = "4096" mdev_db = get_db(mdev) if os.path.isfile(expanduser('~/.config/jack/conf.xml')): logging.debug("Found previous jack config removing") os.remove(expanduser('~/.config/jack/conf.xml')) # Now start jackdbus with the configured device if conf_db['jack']['connect-mode'] != 'n': bus = dbus.SessionBus() controller = bus.get_object(service_name, "/org/jackaudio/Controller") # control_iface = dbus.Interface(controller, control_interface_name) configure_iface = dbus.Interface(controller, configure_interface_name) configure_iface.SetParameterValue( ['engine', 'self-connect-mode'], dbus.Byte(ord(conf_db['jack']['connect-mode']))) if conf_db['jack']['driver'] == "firewire": cp = subprocess.run(["/usr/bin/ffado-test", "BusReset"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"reset firewire bus: {cp.stdout.strip()}") cp = subprocess.run(["/usr/bin/killall", "-q", "ffado-dbus-server", "ffado-mixer"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug( f"Kill ffado mixer because busreset needed: {cp.stdout.strip()}") time.sleep(3) bus = dbus.SessionBus() controller = bus.get_object(service_name, "/org/jackaudio/Controller") control_iface = dbus.Interface(controller, control_interface_name) configure_iface = dbus.Interface(controller, configure_interface_name) configure_iface.SetParameterValue( ['engine', 'driver'], str(conf_db['jack']['driver'])) logging.debug(f"JACK driver set to: {conf_db['jack']['driver']}") time.sleep(.4) if conf_db['jack']['driver'] == "alsa": # we use default device for FW right now configure_iface.SetParameterValue(['driver', 'device'], f"hw:{mdev}") logging.debug(f"JACK device set to: {mdev}") if mdev_db['firewire'] and conf_db['jack']['frame'] < 256: conf_db['jack']['frame'] = 256 logging.info( f"Firewire device using ALSA drivers, JACK buffer set to: 256") time.sleep(.2) configure_iface.SetParameterValue( ['driver', 'rate'], dbus.UInt32(conf_db['jack']['rate'])) time.sleep(.2) configure_iface.SetParameterValue( ['driver', 'period'], dbus.UInt32(conf_db['jack']['frame'])) time.sleep(.2) if conf_db['jack']['driver'] != "dummy": configure_iface.SetParameterValue(['driver', 'nperiods'], dbus.UInt32(conf_db[ 'jack']['period'])) time.sleep(.2) configure_iface.SetParameterValue( ['driver', 'input-latency'], dbus.UInt32(conf_db['jack']['cap-latency'])) time.sleep(.2) configure_iface.SetParameterValue(['driver', 'output-latency'], dbus.UInt32(conf_db['jack'][ 'play-latency'])) else: configure_iface.SetParameterValue(['driver', 'capture'], dbus.UInt32(conf_db[ 'jack']['chan-in'])) time.sleep(.2) configure_iface.SetParameterValue([ 'driver', 'playback'], dbus.UInt32(conf_db['jack']['chan_out'])) time.sleep(.2) configure_iface.SetParameterValue(['driver', 'monitor'], True) logging.debug( "JACK rate/period/nperiods set to: " f"{conf_db['jack']['rate']}/" f"{conf_db['jack']['frame']}/{conf_db['jack']['period']}") time.sleep(3) jack_stat("Starting...") try: control_iface.StartServer() except Exception: logging.debug("JACK start failed") jack_stat("Start Failed") return logging.debug("JACK started") if conf_db['jack']['driver'] == "alsa": last_master = mdev # maybe check for jack up (need function?) time.sleep(3) jack_stat("Poststart") fpath = expanduser('~/.config/autojack/poststart') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): logging.debug("Found poststart script") cp = subprocess.run([fpath], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath}") start_jack_client() time.sleep(1) jack_stat("Adding Bridges") pc = shutil.which("pactl") if pc is None: logging.warning( "pactl is missing... unable to control" "pulseaudio\n configure failed") else: for bridge in conf_db['pulse']['inputs']: this_count = str(conf_db['pulse']['inputs'][bridge]['count']) cp = subprocess.run([pc, "load-module", "module-jack-source", f"client_name={bridge}", f"channels={this_count}", "connect=no"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Pulseaudio: {cp.stdout.strip()}") time.sleep(1) for bridge in conf_db['pulse']['outputs']: this_count = str(conf_db['pulse']['outputs'][bridge]['count']) cp = subprocess.run([pc, "load-module", "module-jack-sink", f"client_name={bridge}", f"channels={this_count}", "connect=no"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"Pulseaudio: {cp.stdout.strip()}") time.sleep(1) if len(conf_db['pulse']['inputs']) or len(conf_db['pulse']['outputs']): cp = subprocess.run([pc, "unload-module", "module-udev-detect"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove module-udev-detect: {cp.stdout.strip()}") time.sleep(1) cp = subprocess.run([pc, "unload-module", "module-alsa-card"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) logging.debug(f"remove module-alsa-card: {cp.stdout.strip()}") time.sleep(1) extra_devices() net_bridge() # check if phones device exists ph_dev = conf_db['extra']['phone-device'].split(',')[0] if ph_dev not in conf_db['devices']: phones = False if phones: if conf_db['extra']['phone-device'] != last_master: start_slave(conf_db['extra']['phone-device']) con_dirty = True elif conf_db['extra']['monitor'][:7] != "system:": mon_dev = conf_db['extra']['monitor'].split('-')[0] start_slave(mon_dev) con_dirty = True # not sure all these delays need to be here. Was checking with old pulse. # this has to be last after all audio bridges are created time.sleep(2) connect_pa() if conf_db['extra']['a2j']: ajc = shutil.which("a2jmidid") if ajc is None: logging.Warning( "a2jmidid not found, is it installed?" " Starting MIDI-JACK bridge failed.") else: up = "" if 'a2j_u' in conf_db['extra']: if conf_db['extra']['a2j_u']: up = "-u" cmd = f"{ajc} -e {up}" midiproc = subprocess.Popen(shlex.split(cmd), shell=False) # logging not possible without extra thread if conf_db['mnet']['count']: mint = shutil.which("qmidinet") if mint is None: logging.Warning( "qmidinet not found, is it installed?" " Starting networked midi failed.") else: mtype = "" if conf_db['mnet']['type'] == 'jack': mtype = "--alsa-midi=no --jack-midi=yes" cmd = f"{mint} --num-ports={str(conf_db['mnet']['count'])} " f"--no-gui {mtype}" subprocess.Popen(shlex.split(cmd), shell=False).pid jack_stat("Postbridge") fpath = expanduser('~/.config/autojack/postbridge') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): logging.debug("Found poststart script") cp = subprocess.run([fpath], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath}") jack_stat("Running") def start_jack_client(): ''' Create a jack client for monitoring and changing port connections''' global jack_client global jack_died global jack_alive global con_dirty logging.debug("create jack client") jack.set_error_function(callback=jack_error) jack.set_info_function(callback=jack_info) # use dbus call to find if jack is running bus = dbus.SessionBus() controller = bus.get_object(service_name, "/org/jackaudio/Controller") control_iface = dbus.Interface(controller, control_interface_name) if int(control_iface.IsStarted()): try: jack_client = jack.Client( 'AutoJack', use_exact_name=False, no_start_server=True) except jack.JackError: logging.warning("Unable to create jack client") jack_died = True return jack_client.set_shutdown_callback(jackdied) jack_client.set_port_connect_callback(jack_con_det) jack_client.activate() jack_died = False jack_alive = True con_dirty = True logging.debug("jack client created") def kill_jack_client(): global jack_client global jack_alive global jack_died if jack_client in globals(): jack_client.deactivate() jack_client.close() jack_died = False jack_alive = False jack_stat("Stopped") logging.debug("jack client closed and cleaned up") def jackdied(state, why): '''gets called by jack if it is exiting, we can't clean up here... so tell the world jack died with a flag instead''' global jack_died jack_died = True jack_stat("Stopping") logging.debug("jack died callback") def jack_stat(status): global last_status last_status = status sendbs.state(version, status) def jack_con_det(a, b, connect): ''' a port has had a connection check if one of the ports is monitor L/R if so and phones == True move to phones port''' global con_dirty global conf_db if connect and (phones or (conf_db['extra']['monitor'] != "system:playback_1")): con_dirty = True def check_jack_status(user_data): ''' Check if jack has died and the client needs to be closed. At least that is what it started as. Now it is a loop that is run on a timer that checks for various things to do. ''' global con_dirty global conf_db global control_signal global device_signal global jack_alive global jack_count global jack_client global jack_died global last_status global phone_port global phone_signal if control_signal != []: command_run() control_signal = [] if device_signal < 0: device_removed() device_signal = 0 if device_signal > 0: device_new() device_signal = 0 if phone_signal < 0: phones_switch(False) phone_signal = 0 if phone_signal > 0: phones_switch(True) phone_signal = 0 # use dbus call to find if jack is running bus = dbus.SessionBus() controller = bus.get_object(service_name, "/org/jackaudio/Controller") control_iface = dbus.Interface(controller, control_interface_name) # configure_iface = dbus.Interface(controller, configure_interface_name) if jack_alive and not int(control_iface.IsStarted()): jack_died = True # sent by jackdied() callback if jack_died: kill_jack_client() jack_count = jack_count + 1 if jack_count == 200: # maintain logfiles and other things that don't need to # be looked at so often logging.log(7, f"Jack_status: {str(jack_alive)}") jack_count = 0 logfile = expanduser("~/.log/autojack.log") if os.path.isfile(logfile) and os.path.getsize(logfile) > 200000: logging.debug("Log File getting large: rotate") for old_idx in range(5, -1, -1): if old_idx: if os.path.isfile(f"{logfile}.{str(old_idx)}"): os.replace(f"{logfile}.{str(old_idx)}", f"{logfile}.{str(old_idx + 1)}") else: logging.shutdown() os.replace(logfile, f"{logfile}.1") logging.basicConfig(filename=logfile, format='%(asctime)s - %(name)s' ' - %(levelname)s - %(message)s', level=conf_db['log-level']) logging.debug("Log File rotated") logfile = expanduser("~/.log/jack/jackdbus.log") if os.path.isfile(logfile) and os.path.getsize(logfile) > 200000: logging.debug("JACK log File getting large: rotate") for old_idx in range(5, -1, -1): if old_idx: if os.path.isfile(f"{logfile}.{str(old_idx)}"): os.replace(f"{logfile}.{str(old_idx)}", f"{logfile}.{str(old_idx + 1)}") else: os.replace(logfile, f"{logfile}.1") logging.debug("JACK Log File rotated") rgov, rboost = auto_jack.get_gvnr_hw() pk = shutil.which("pkexec") if pk is None: logging.warning( "pexec is missing... unable to change" " cpu-governor or boost") else: if conf_db['cpu-governor'] != rgov: if conf_db['cpu-governor']: gov = "performance" else: if os.path.exists("/sys/devices/system/cpu/intel_pstate"): gov = "powersave" else: gov = "ondemand" logging.info(f"Setting CPU Governor to {gov}") subprocess.run( [pk, f"{install_path}/sbin/studio-system", gov], shell=False) if conf_db['boost'] != rboost: if conf_db['boost']: boost = "noboost" logging.info("Setting Intel Boost to disable") else: boost = 'boost' logging.info("Setting Intel Boost to enable") subprocess.run( [pk, f"{install_path}/sbin/studio-system", boost], shell=False) if con_dirty and jack_alive: logging.debug("Connection changed") if phones and 'phone_port' in globals() and phone_port != 'none': new_l = phone_port logging.debug(f"change it to phones port: {new_l}") else: new_l = conf_db['extra']['monitor'] switch_outputs("system:playback_1", new_l) con_dirty = False return True def switch_outputs(left_old, left_new): '''finds any outputs connected to a_dev and moves them to b_dev. Devices are jack client names including the first port of the stereo pair. example: system:playback_1''' global jack_client global jack_alive logging.debug( f"moving connections from {str(left_old)} to {str(left_new)}") if not jack_alive: logging.debug("Jack not running") return if left_old == left_new: logging.debug("both ports are the same, no move done") return try: jack_client.get_port_by_name(str(left_old)) except jack.JackError: logging.debug(f"Jack port: {left_old} not found") return try: jack_client.get_port_by_name(str(left_new)) except jack.JackError: logging.debug(f"Jack port: {left_new} not found") return right_old = get_next_port(left_old) right_new = get_next_port(left_new) l_old_con = jack_client.get_all_connections(left_old) logging.log(8, f"old left connections {str(l_old_con)}") # first connect all output to new device for raw_p in l_old_con: s_port = str(raw_p).split("'")[1] try: jack_client.connect(s_port, left_new) except Exception: logging.debug(f"Jack port {s_port} could not be connected to {left_new}") r_old_con = jack_client.get_all_connections(right_old) logging.log(8, f"old right connections {str(r_old_con)}") for raw_p in r_old_con: s_port = str(raw_p).split("'")[1] try: jack_client.connect(s_port, right_new) except Exception: logging.debug(f"Jack port {s_port} could not be connected to {right_new}") # then disconnect old ones for raw_p in l_old_con: s_port = str(raw_p).split("'")[1] jack_client.disconnect(s_port, left_old) for raw_p in r_old_con: s_port = str(raw_p).split("'")[1] jack_client.disconnect(s_port, right_old) def get_next_port(left_port_ask): ''' given a port, this returns the jack port of the next port or for a mono device the same port so that it is connected to both left and write''' global jack_client global jack_alive if not jack_alive: return "" right = False # For firewire backend, the ports are labeled # firewire_pcm:something_in or _out # but have an alias of system:_. # We have to find the real port name for left_port left_port = jack_client.get_port_by_name(left_port_ask) logging.debug(f"Real left port name: {str(left_port.name)}") right_port = left_port.name if left_port.is_input: ports = jack_client.get_ports( f"{left_port.name.split(':')[0]}*", is_audio=True, is_input=True) else: ports = jack_client.get_ports( f"{left_port.name.split(':')[0]}*", is_audio=True, is_output=True) for next_port in ports: if next_port.name == left_port.name: logging.log(8, f"{str(next_port.name)} == {str(left_port.name)}") right = True elif right: right_port = next_port.name logging.log(8, f"Right port: {str(right_port)}") break return right_port def connect_pa(): '''connects pulse ports to the correct device ports. May have to use zita-ajbridge to first make the correct device available.''' global conf_db global jack_client global jack_alive if not jack_alive: logging.debug("Can't connect pulse, jack not running") return logging.debug("connect pulse bridges") for bridge in conf_db['pulse']['inputs']: connection = conf_db['pulse']['inputs'][bridge]['connection'] if connection != 'none': prevport = "" nextport = connection pulselist = jack_client.get_ports( f"{bridge}:*", is_audio=True, is_input=True) for pport in pulselist: # apparently reg ex with : doesn't find : so sort it here if pport.name.split(':')[0] != bridge: continue if nextport != prevport: logging.debug(f"jack connect {nextport} to {pport.name}") try: jack_client.connect(nextport, pport) except Exception: logging.log(8, "already connected skipping") prevport = nextport nextport = get_next_port(prevport) for bridge in conf_db['pulse']['outputs']: connection = conf_db['pulse']['outputs'][bridge]['connection'] if connection != 'none': prevport = "" nextport = connection if connection == "monitor": nextport = conf_db['extra']['MONITOR'] pulselist = jack_client.get_ports( f"{bridge}:*", is_audio=True, is_output=True) for pport in pulselist: if pport.name.split(':')[0] != bridge: continue if nextport != prevport: logging.debug(f"jack connect {pport.name} to {nextport}") try: jack_client.connect(pport, nextport) except Exception: logging.log(8, "already connected skipping") prevport = nextport nextport = get_next_port(prevport) def disconnect_pa(our_config): '''disconnect Pulse ports we know we have connected. The pa-jack bridge is left running. Leave other connections alone.''' global jack_client global jack_alive if not jack_alive: logging.debug("Jack not running") return logging.debug("disconnect pulse bridges") p_in_db = our_config['pulse']['inputs'] p_out_db = our_config['pulse']['outputs'] for bridge in p_in_db: if p_in_db[bridge]['connection'] != 'none': prevport = "" nextport = p_in_db[bridge]['connection'] pulselist = jack_client.get_ports( f"{bridge}:*", is_audio=True, is_input=True) for pport in pulselist: if nextport != prevport: logging.debug( f"jack disconnect {nextport} from {pport.name}") try: jack_client.disconnect(nextport, pport) except Exception: logging.log(8, "already connected skipping") prevport = nextport nextport = get_next_port(prevport) for bridge in p_out_db: if p_out_db[bridge]['connection'] != 'none': prevport = "" if p_out_db[bridge]['connection'] == "monitor": nextport = our_config['extra']['monitor'] else: nextport = p_out_db[bridge]['connection'] pulselist = jack_client.get_ports( f"{bridge}:*", is_audio=True, is_output=True) for pport in pulselist: if nextport != prevport: logging.debug( f"jack disconnect {pport.name} from {nextport}") try: jack_client.disconnect(pport, nextport) except Exception: logging.log(8, "already connected skipping") prevport = nextport nextport = get_next_port(prevport) def msg_cb_new(*args, **kwargs): '''call back for udev sensing new device. checks if device is audio. If it is, set a global variable to run in timer loop. ''' global last_master global conf_db global device_signal global version global cards_f if not conf_db['jack']['on']: # then we don't care return # let things settle time.sleep(3) if args[0].find("sound-card") >= 0: device_signal = 1 return def device_new(): ''' call back for udev sensing new device has set variable so this checks if device is USB. If both are true and configuration is to use this device, the device is either connected with zita-ajbridge or becomes jack's master device ''' global last_master global conf_db global version global cards_f # check to see if any new cards with open('/proc/asound/cards', "r") as card_file: my_cards_f = card_file.read() if my_cards_f == cards_f: logging.debug("card file not changed, skip") return else: cards_f = my_cards_f # remake database old_devs_db = copy.deepcopy(conf_db['devices']) import_config("msg_cb_new") dev_db = {} for dev_name in conf_db['devices']: dev_db = conf_db['devices'][dev_name] # we only care about usb devices that are plugged in and have audio if dev_db['usb'] and dev_db['number'] != -1 and len(dev_db['sub']): # check if it was not in db before or "card number" has changed if dev_name not in old_devs_db or dev_db[ 'number'] != old_devs_db[dev_name]['number']: # tell gui devicelist has changed so it can up date # device dropdowns sendbs.state(version, 'usb') logging.debug(f"USB device {dev_name} has been plugged in") for subn in dev_db['sub']: subname = f"{dev_name},{str(subn)},0" if subname == last_master: # this device is current master # maybe jack did a reset? continue sub_db = dev_db['sub'][subn] if (conf_db['jack']['driver'] == "alsa") and ( conf_db['jack']['usbdev'] == subname): logging.debug( f"Changing jack master to: {subname}") config_start() return if sub_db['play-pid'] or sub_db['cap-pid']: # device in use already continue if conf_db['extra']['usbauto']: # extra_devices() start_slave(subname) if subname == conf_db['extra']['phone-device']: phones_switch(True) def msg_cb_removed(*args, **kwargs): ''' dbus call back when a USB device removal has been detected by udev ''' global conf_db global cards_f global device_signal global jack_alive global last_master if not jack_alive: # then we don't care return # let things settle time.sleep(.5) if args[0].find("sound-card") >= 0: device_signal = -1 def device_removed(): ''' dbus call back when a USB device removal has been detected by udev ''' global conf_db global cards_f global jack_alive global last_master if not jack_alive: # then we don't care return # check to see if any cards have vanished with open('/proc/asound/cards', "r") as card_file: my_cards_f = card_file.read() # logging.debug(f"card file: {str(my_cards_f)}") if my_cards_f == cards_f: logging.debug("card file not changed, skip") return else: cards_f = my_cards_f # remake database old_devs_db = copy.deepcopy(conf_db['devices']) import_config("msg_cb_removed") dev_db = {} for dev_name in conf_db['devices']: dev_db = conf_db['devices'][dev_name] logging.debug(f"check device {dev_name} for unplug") # we only care about usb devices that are unplugged and have audio logging.debug(f"USB {str(dev_db['usb'])} Num: {str(dev_db['number'])}") if dev_db['usb'] and (dev_db['number'] == -1) and len(dev_db['sub']): # check if it was not in db before or "card number" has changed if dev_name in old_devs_db or dev_db[ 'number'] != old_devs_db[dev_name]['number']: # tell gui devicelist has changed # so it can up date device dropdowns sendbs.state(version, 'usb') logging.debug(f"USB device {dev_name} has been unplugged") for subn in dev_db['sub']: subname = f"{dev_name},{str(subn)},0" if conf_db['jack']['usbdev'] == subname: if last_master == get_raw( conf_db['jack']['usbdev']): kill_slave(conf_db['jack']['dev'], old_devs_db) time.sleep(1) config_start() elif conf_db['extra']['usbauto']: if subname == conf_db['extra']['phone-device']: phones_switch(False) kill_slave(subname, old_devs_db) def start_slave(ldev): ''' takes the audio device as a parameter and starts a bridge from that device to jack ''' global conf_db global procs logging.debug(f"Start slave: {ldev}") if len(ldev.split(",", 2)) != 3: logging.debug( f"Device {ldev} missing device number or sub device") return import_config("start_slave") dname, l_dev, sub = ldev.split(",", 2) dev_db = conf_db['devices'][dname] sub_db = dev_db['sub'][l_dev] if sub_db['hide']: # don't start a slave blacklisted device logging.debug(f"Blacklisted: {ldev} don't start") return raw_xp = f"{dev_db['raw']},{l_dev},{sub}" buff_size = sub_db['frame'] # should do play and capture separately if sub_db['play-pid'] or sub_db['cap-pid']: logging.debug( f" Device {ldev} already bridged or in use by other application") return if dev_db['hdmi']: logging.info("HDMI device, setting buffer to 4096") buff_size = "4096" elif dev_db['internal'] and (buff_size < 128): logging.info("Internal device, minimum buffer 128, using 128") buff_size = "128" dsr = str(sub_db['rate']) if dsr not in dev_db['rates']: logging.info(f"sample rate {dsr} for {ldev} not valid") if "48000" in dev_db['rates']: dsr = "48000" elif "44100" in dev_db['rates']: dsr = "44100" elif len(dev_db['rates']): dsr = dev_db['rates'][0] else: logging.info(f"{ldev} has no sample rates, no bridge") return logging.info(f"Using {dsr} instead") # we found it and it seems to have this sub if sub_db['playback']: if not sub_db['play-pid']: # this should detect if the user has manually set # either in or out numbers channels = 0 if sub_db['play-chan']: channels = sub_db['play-chan'] elif dev_db['usb'] and not conf_db['extra']['usb-single']: channels = 100 elif ldev == conf_db['extra']['phone-device']: channels = 100 if channels: cmd = f"/usr/bin/zita-j2a -j {sub_db['name']}-out" cmd = f"{cmd} -d hw:{raw_xp} -r {dsr} -p {buff_size}" cmd = f"{cmd} -n {str(sub_db['nperiods'])} -c {str(channels)}" cmd = f"{cmd} -O {str(sub_db['play-latency'])}" logging.debug(f"device bridging comand line: {cmd}") procout = subprocess.Popen(shlex.split(cmd), shell=False) pidout = procout.pid logging.debug(f" Device {ldev} out has pid: {pidout}") procs.append(procout) sub_db['play-pid'] = pidout else: logging.debug( f" Device {ldev} playback already bridged or " "in use by other application") if sub_db['capture']: if not sub_db['cap-pid']: chanels = 0 if sub_db['cap-chan']: channels = sub_db['cap-chan'] elif dev_db['usb']: channels = 100 if channels: cmd = f"/usr/bin/zita-a2j -j {sub_db['name']}-in" cmd = f"{cmd} -d hw:{raw_xp} -r {dsr} -p {buff_size}" cmd = f"{cmd} -n {str(sub_db['nperiods'])} -c {str(channels)}" cmd = f"{cmd} -I {str(sub_db['cap-latency'])}" logging.debug(f"Device input comand line: {cmd}") procin = subprocess.Popen(shlex.split(cmd), shell=False) pidin = procin.pid logging.debug(f" Device {ldev} in has pid: {pidin}") procs.append(procin) sub_db['cap-pid'] = pidin else: logging.debug( f" Device {ldev} capture already bridged or " "in use by other application") time.sleep(1) import_config("start_slave") def kill_slave(ldev, devs_db): ''' takes the device as a parameter and if the device exists and is bridged to jack, stops the bridge ''' global conf_db global procs dname, l_dev, sub = ldev.split(",", 2) logging.debug(f"{ldev} kill in progress") # dev_db = conf_db['devices'][dname] dev_db = devs_db[dname] if len(dev_db['sub']): sub_db = dev_db['sub'][l_dev] logging.debug( f"{dname} has {str(len(dev_db['sub']))} sub devices, " f"want sub device {l_dev}") if sub_db['play-pid']: logging.debug( f"{ldev} found Playback bridge to kill. " f"PID: {str(sub_db['play-pid'])}") for i, pr in enumerate(procs): if pr.pid == sub_db['play-pid']: logging.debug( f"kill {str(dname)} sub: {str(l_dev)} " f"PID: {str(sub_db['play-pid'])}") pr.send_signal(signal.SIGINT) try: rt = pr.wait(timeout=15) except subprocess.TimeoutExpired: logging.debug( f"kill PID: {str(sub_db['play-pid'])} failed") pr.terminate() outs, errs = pr.communicate() del procs[i] else: logging.debug(f"{ldev} no Playback bridge found") if sub_db['cap-pid']: logging.debug( f"{ldev} found Capture bridge to kill. " f"PID: {str(sub_db['cap-pid'])}") for i, pr in enumerate(procs): if pr.pid == sub_db['cap-pid']: logging.info( f"kill {str(dname)} sub: {str(l_dev)} " f"PID: {str(sub_db['cap-pid'])}") pr.send_signal(signal.SIGINT) try: rt = pr.wait(timeout=15) except subprocess.TimeoutExpired: logging.debug( f"kill PID: {str(sub_db['cap-pid'])} failed") try: os.kill(int(sub_db['cap-pid']), 9) except Exception: print("") del procs[i] else: logging.debug(f"{ldev} no Capture bridge found") # get rid of pid entries import_config("kill_slave") def we_die(): global lock_file global midiproc if 'midiproc' in globals(): midiproc.send_signal(signal.SIGINT) try: rt = midiproc.wait(timeout=15) except subprocess.TimeoutExpired: logging.debug(f"kill a2jmidid failed") try: os.kill(int(sub_db['cap-pid']), 9) except Exception: print("") cp = subprocess.run(["/usr/bin/killall", "-9", "jackdbus", "jackd", "a2jmidid"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) jack_stat("Stopped") logging.debug(f"Kill jack and friends: {cp.stdout.strip()}") bus = dbus.SessionBus() systemd1 = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager') job = manager.RestartUnit('pulseaudio.service', 'fail') logging.debug(f"Restart PA: {job.strip()}") if os.path.isfile(lock_file): new_pid = str(os.getpid()) if os.path.isfile(lock_file): with open(lock_file, "r") as lk_file: for line in lk_file: # only need one line old_pid = line.rstrip() if new_pid == old_pid: os.remove(lock_file) os._exit(0) def phones_switch(plugged): ''' Does the actual phones signal and level switching ''' global conf_db global phones global phone_port global jack_client global jack_alive phones = plugged logging.debug(f"Changing Headphone to plugged in = {str(plugged)}") logging.debug(f"Headphone port: {str(phone_port)}") if (not conf_db['jack']['on']) or not jack_alive: # we can't do anything up to pulse (I think) return if 'phone_port' not in globals() or (phone_port == 'none'): logging.debug("phone device and port not set: setting phones to False") phones = False return logging.debug( f"Headphone phone action = {str(conf_db['extra']['phone-action'])}") pdev_list = [] if conf_db['extra']['phone-device'] == 'system' and conf_db['jack']['driver'] == 'alsa': if conf_db['jack']['usbdev'] != 'none': pdev_list = conf_db['jack']['usbdev'].split(",", 2) else: pdev_list = conf_db['jack']['dev'].split(",", 2) else: pdev_list = conf_db['extra']['phone-device'].split(",", 2) alsadev = False if len(pdev_list) == 3: dname, l_dev, sub = pdev_list dev_db = conf_db['devices'][dname] sub_db = dev_db['sub'][l_dev] alsadev = True if conf_db['extra']['phone-action'] == "switch": logging.debug("Switching outputs") if alsadev and dev_db['internal']: spkr = "" logging.debug( "Headphone device is internal change it's mixer settings") my_mixers = alsaaudio.mixers(device=f"hw:{dname}") if "Front" in my_mixers: spkr = "Front" elif "Speakers" in my_mixers: spkr = "Speakers" spkr_mix = alsaaudio.Mixer(control=spkr, device=f'hw:{dname}') hp_mix = alsaaudio.Mixer(control='Headphone', device=f'hw:{dname}') if plugged: spkr_mix.setvolume(0) spkr_mix.setmute(1) hp_mix.setvolume(100) hp_mix.setmute(0) else: spkr_mix.setvolume(100) spkr_mix.setmute(0) hp_mix.setvolume(0) hp_mix.setmute(1) logging.debug(f"Headphone jack is: {phone_port}") hp_search = f"{phone_port.split(':', 1)[0]}*" port_list = jack_client.get_ports( name_pattern=hp_search, is_audio=True, is_input=True, is_physical=True) if not plugged: # switch to monitor switch_outputs(phone_port, conf_db['extra']['monitor']) # BUG sub_db may not exist if not alsadev if alsadev and not sub_db['play-chan']: logging.debug( "Headphone device had no jack port ... " "kill unneeded bridge") kill_slave(conf_db['extra']['phone-device'], conf_db['devices']) else: # switch to phone port if alsadev and port_list == []: logging.debug( "Headphone device has no jack port ... create bridge") start_slave(conf_db['extra']['phone-device']) time.sleep(1) mn_search = f"{conf_db['extra']['monitor'].split(':', 1)[0]}*" port_list = jack_client.get_ports( name_pattern=mn_search, is_audio=True, is_input=True, is_physical=True) switch_outputs(conf_db['extra']['monitor'], phone_port) elif conf_db['extra']['phone-action'] == "script": # run a script to perform this instead logging.debug("Use script") fpath = expanduser('~/.config/autojack/headphone') if os.path.isfile(fpath) and os.access(fpath, os.X_OK): cp = subprocess.run([fpath, str(plugged)], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) logging.debug(f"Running script: {fpath} {str(plugged)}") else: logging.info("headphone script not found or not executable") else: logging.debug("No headphone action chosen") def phones_check(): ''' check to see if there are phones plugged in. We only do this on start up''' logging.debug("Checking for phones plugged in") global phones global conf_db phones = False import_config("phones_check") # need to check if jack is running if len(conf_db['extra']['phone-device'].split(',')) < 3: logging.info(f"Phones device: {conf_db['extra']['phone-device']} invalid") return dname, ddev, dsub = conf_db['extra']['phone-device'].split(',') if dname not in conf_db['devices']: logging.info(f"Phones device: {conf_db['extra']['phone-device']} invalid") return dev_db = conf_db['devices'][dname] sub_db = dev_db['sub'][ddev] logging.log(7, f"phonecheck: got device") if int(dev_db['number']) < 0: logging.info(f"Phones device: {dname} not present") return logging.debug(f"Checking phones device: {dname}") if dev_db['usb']: # phones device is USB if sub_db['playback']: # If USB make sure it has audio playback phones_switch(True) return else: logging.warning( f"Headphone device {conf_db['extra']['phone-device']} " "appears to have no outputs") elif dev_db['internal']: # this is internal we can use amixer logging.debug("Checking for phones with internal device") try: my_mixers = alsaaudio.mixers(device=f"hw:{dname}") except Exception: logging.info(f"Phones device: {dname} mixer not accessable") return hp_sw = "" if "Front" in my_mixers: hp_sw = "Front " cmd = f"/usr/bin/amixer -D hw:{dname} cget" cmd = f"{cmd} iface=CARD,name='{hp_sw}Headphone Jack'" logging.debug(f"amixer command: {cmd}") cp = subprocess.run(shlex.split(cmd), universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) for line in cp.stdout.split(":"): logging.debug(f"amixer line: {line.strip()}") if line.strip() == "values=on": phones_switch(True) return elif line.strip() == "values=off": phones_switch(False) return logging.debug(f"headphone detect string: {cp.stdout.strip()}") def phones_plug(*args, **kwargs): ''' callback means headphones have been plugged in lets make sure phones are unmuted and have a level higher than -inf. We also may want to mute speakers ''' global conf_db global phone_signal logging.info("Got phones plugged in signal.") dname, ddev, dsub = conf_db['extra']['phone-device'].split(',') dev_db = conf_db['devices'][dname] if int(dev_db['number']) > -1 and dev_db['internal']: # changed to: phone_signal = 1 # the line below to be moved to check_jack_status # phones_switch(True) else: logging.info("Ignored phone plug: not correct device") def phones_unplug(*args, **kwargs): ''' callback means headphones have been unplugged in lets make sure phones are muted and the speakers are unmuted and have a level higher than -inf.''' global conf_db global phone_signal logging.info("Got phones unplugged signal.") dname, ddev, dsub = conf_db['extra']['phone-device'].split(',') dev_db = conf_db['devices'][dname] if int(dev_db['number']) > -1 and dev_db['internal']: # changed to: phone_signal = -1 # the line below to be moved to check_jack_status # phones_switch(False) else: logging.info("Ignored phone unplug: not correct device") def jack_error(mesg): ''' jack call back to deal with jack errors ''' global jack_alive if jack_alive: if "not running" in mesg: logging.warning(f"Jack Message: {mesg}") def jack_info(mesg): ''' jack call back to deal with jack info ''' logging.info(f"Jack Message: {mesg}") def ses_cb_command(*args, **kwargs): ''' Generic signal receiver ''' global control_signal control_signal = args def command_run(): ''' Generic signal receiver ''' global version global last_status global phones global control_signal args = control_signal rec_string = f"args: {str(args[0])}" logging.debug(f"got signal - {rec_string}") if 'start' in args: logging.info("Got start signal.") config_start() return if 'config' in args: logging.info("Got config signal.") reconfig() return if 'stop' in args: logging.info("Got stop signal.") config_start() return if 'ping' in args: logging.info("Got ping signal.") time.sleep(3) sendbs.state(version, last_status) return if 'quit' in args: logging.warning("Got quit signal.") we_die() return if 'phones' in args: logging.info("Got phones signal.") phones_switch(True) phones = True logging.debug(f"Manual phones switch to: {str(phones)}") return if 'monitor' in args: logging.info("Got monitor signal.") phones_switch(False) phones = False logging.debug(f"Manual phones switch to: {str(phones)}") return def handler(signum, frame): ''' a handler for system signals that may be sent by the system. we want to trap sigint, sigkill and sigterm and do the same as above. ''' logging.warning(f"Got signal number: {str(signum)} - Dying.") we_die() def main(): ''' Autojack runs at session start and manages audio for the session. this is the daemon for studio-controls''' global cards_f global config_path global con_dirty global fw_exists global install_path global jack_alive global jack_client global jack_count global jack_died global last_master global lock_file global phones global procs global sendbs global startup global version global control_signal global device_signal global phone_signal control_signal = [] device_signal = 0 phone_signal = 0 auto_jack.check_user() # don't run as system or root cards_f = "none" con_dirty = False fw_exists = False phones = False jack_alive = False jack_client = 0 jack_count = 0 jack_died = False last_master = "" procs = [] startup = True print("starting up") print(f"install path: {install_path}") version = auto_jack.version() print(f"version: {version}") # set up logging logpath = expanduser("~/.log") # make sure the logfile directory exists if not os.path.exists(logpath): os.makedirs(logpath) logfile = expanduser("~/.log/autojack.log") logging.basicConfig( filename=logfile, format='%(asctime)s - AutoJack - %(levelname)s - %(message)s', level=logging.DEBUG) logging.info('Autojack started: logging started') print("logging started") # Try and kill any other running instance config_path = expanduser("~/.config/autojack") if not os.path.isdir(config_path): os.makedirs(config_path) lock_file = f"{config_path}/autojack.lock" print("sending quit to any old autojack") dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) sendbs = sendbus() sendbs.signal('quit') time.sleep(3) new_pid = str(os.getpid()) old_pid = new_pid if os.path.isfile(lock_file): file_stats = os.stat(lock_file) if int(file_stats.st_size): # other instance still hasn't gone maybe hung with open(lock_file, "r") as lk_file: for line in lk_file: # only need one line old_pid = line.rstrip() if new_pid != old_pid: print("old lock file found, killing old pid") try: os.kill(int(old_pid), 9) except Exception: print("") time.sleep(1) with open(lock_file, "w") as lk_file: lk_file.write(new_pid) print("Lock file created") phones_check() signal.signal(signal.SIGHUP, handler) signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGQUIT, handler) signal.signal(signal.SIGILL, handler) signal.signal(signal.SIGTRAP, handler) signal.signal(signal.SIGABRT, handler) signal.signal(signal.SIGBUS, handler) signal.signal(signal.SIGFPE, handler) # signal.signal(signal.SIGKILL, handler) unhandlable :) signal.signal(signal.SIGUSR1, handler) signal.signal(signal.SIGSEGV, handler) signal.signal(signal.SIGUSR2, handler) signal.signal(signal.SIGPIPE, handler) signal.signal(signal.SIGALRM, handler) signal.signal(signal.SIGTERM, handler) system_bus = dbus.SystemBus() system_bus.add_signal_receiver( msg_cb_new, dbus_interface='org.freedesktop.systemd1.Manager', signal_name='UnitNew') system_bus.add_signal_receiver( msg_cb_removed, dbus_interface='org.freedesktop.systemd1.Manager', signal_name='UnitRemoved') system_bus.add_signal_receiver( phones_plug, dbus_interface='org.studio.control.event', signal_name='plug_signal') system_bus.add_signal_receiver( phones_unplug, dbus_interface='org.studio.control.event', signal_name='unplug_signal') user_bus = dbus.SessionBus() user_bus.add_signal_receiver( ses_cb_command, dbus_interface='org.studio.control.command', signal_name='signal') sendbs.signal('start') jack_stat("Undetermined") timeout_id = GLib.timeout_add(500, check_jack_status, None) loop = GLib.MainLoop() loop.run() if __name__ == '__main__': main() studio-controls-2.3.9/usr/bin/studio-cmd000077500000000000000000000047661432334377300203020ustar00rootroot00000000000000#!/usr/bin/python3 -u import dbus, dbus.service, dbus.exceptions import dbus.mainloop.glib import json import os import sys import time from gi.repository import GLib from os.path import expanduser global install_path install_path = os.path.abspath(f"{sys.path[0]}/..") sys.path.insert(1, f"{install_path}/lib/python3/dist-packages") import auto_jack global name_base global control_interface_name global configure_interface_name global service_name name_base = 'org.jackaudio' control_interface_name = name_base + '.JackControl' configure_interface_name = name_base + '.Configure' service_name = name_base + '.service' class sendbus(dbus.service.Object): def __init__(self): dbus.service.Object.__init__(self, dbus.SessionBus(), "/") @dbus.service.signal(dbus_interface="org.studio.control.command", signature="s") def signal(self, sg): pass def set_db(jack): global conf_db c_file = expanduser(auto_jack.new_name) print(c_file) if not os.path.isfile(c_file): sys.exit("Configuration file not created, Please run Studio Controls first") # config file exists, read it in no_check = True # (autojack should have already done that) auto_jack.log = False auto_jack.check_new(True) auto_jack.our_db['jack']['on'] = jack auto_jack.write_new() time.sleep(.5) def goodbye(dummy): """ This is because we have to run a mainloop to send dbus messages so we wait some time to allow message to be sent and then this is called to exit the mainloop """ sys.exit() def main(argv): ''' Control-cmd takes either start or stop as a command line parameter to send to auto jack ''' dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) sendbs = sendbus() if len(sys.argv) == 2: command = sys.argv[1] if command == "start": # idea to think on: # ping and check autojack version to restart if not current jack = True set_db(jack) sendbs.signal(command) elif command == "stop": jack = False set_db(jack) sendbs.signal(command) elif command == "phones" or command == "monitor": sendbs.signal(command) else: print("invalid argument") sys.exit(2) else: print("invalid argument") sys.exit(2) timeout_id = GLib.timeout_add(100, goodbye, None) loop = GLib.MainLoop() loop.run() if __name__ == '__main__': main(sys.argv[1:]) studio-controls-2.3.9/usr/bin/studio-controls000077500000000000000000001733071432334377300214000ustar00rootroot00000000000000#!/usr/bin/python3 import dbus import dbus.service import dbus.exceptions import getpass import gi import grp import jack import json import os import re import resource import shlex import shutil import signal import socket import subprocess import sys import time from os.path import expanduser gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib from dbus.mainloop.glib import DBusGMainLoop # the auto_jack module might be in local in some cases global install_path install_path = os.path.abspath(f"{sys.path[0]}/..") sys.path.insert(1, f"{install_path}/lib/python3/dist-packages") import auto_jack DBusGMainLoop(set_as_default=True) class sendbus(dbus.service.Object): def __init__(self): dbus.service.Object.__init__(self, dbus.SessionBus(), "/") @dbus.service.signal(dbus_interface="org.studio.control.command", signature="s") def signal(self, ping): print(f"sent it {ping}") pass class SysInfo: """Get information about the system""" # get info about if rtaccess is setup right def user_audio(self): """Checks if current user is in the audio group, or not""" audio_users = [] audio_users = grp.getgrnam("audio")[3] user = getpass.getuser() if user in audio_users: return True return False def check_pam_files(self): '''Checks for the existence of files''' if os.path.isfile("/etc/security/limits.d/audio.conf"): return True return False def check_rlimits(self): '''returns hard rlimit values for RTPRIO and MEMLOCK''' return {resource.getrlimit(resource.RLIMIT_RTPRIO)[1], resource.getrlimit(resource.RLIMIT_MEMLOCK)[1]} class StudioControls: global lock_file config_path = auto_jack.config_path def __init__(self): '''Activate the SysInfo class''' # this is a long chunk of code that initializes every thing # it should probably be split into tabs at least global lock_file global install_path global version auto_jack.check_user() # don't run as system or root print(f"install path: {install_path}") self.sysinfo = SysInfo() c_dir = expanduser(auto_jack.config_path) if not os.path.isdir(c_dir): os.makedirs(c_dir) lock_file = expanduser(f"{self.config_path}/studio-controls.lock") new_pid = str(os.getpid()) if os.path.isfile(lock_file): old_pid = new_pid with open(lock_file, "r") as lk_file: for line in lk_file: # only need one line old_pid = line.rstrip() if new_pid != old_pid: try: os.kill(int(old_pid), 9) except Exception: pass time.sleep(1) with open(lock_file, "w") as lk_file: lk_file.write(new_pid) version = auto_jack.version() signal.signal(signal.SIGHUP, self.sig_handler) signal.signal(signal.SIGINT, self.sig_handler) signal.signal(signal.SIGQUIT, self.sig_handler) signal.signal(signal.SIGILL, self.sig_handler) signal.signal(signal.SIGTRAP, self.sig_handler) signal.signal(signal.SIGABRT, self.sig_handler) signal.signal(signal.SIGBUS, self.sig_handler) signal.signal(signal.SIGFPE, self.sig_handler) # signal.signal(signal.SIGKILL, self.sig_handler) signal.signal(signal.SIGUSR1, self.sig_handler) signal.signal(signal.SIGSEGV, self.sig_handler) signal.signal(signal.SIGUSR2, self.sig_handler) signal.signal(signal.SIGPIPE, self.sig_handler) signal.signal(signal.SIGALRM, self.sig_handler) signal.signal(signal.SIGTERM, self.sig_handler) # Create the GUI builder = Gtk.Builder() builder.add_from_file( f"{install_path}/share/studio-controls/studio-controls.glade") # Get windows self.window_main = builder.get_object('window_main') self.window_help = builder.get_object('window_help') self.message_dialog_changes_info = builder.get_object( 'message_dialog_changes_info') self.message_dialog_rt_info = builder.get_object( 'message_dialog_rt_info') self.message_dialog_changes_info.set_transient_for(self.window_main) self.message_dialog_rt_info.set_transient_for(self.window_main) self.title_label = builder.get_object('label_main_top') self.button_msg_ok = builder.get_object('button_msg_ok') self.title_label.set_text( f"Studio Set Up Utility (version: {version})") # Get buttons for system tab self.rt_button = builder.get_object('rt_button') self.rt_warning = builder.get_object('rt_warning') self.combo_governor = builder.get_object('combo_governor') self.combo_boost = builder.get_object('combo_boost') self.logging_comb = builder.get_object('logging_comb') self.combo_fw = builder.get_object('combo_fw') # audio tab stuff # right side menu self.mixer_start = builder.get_object('mixer_start') # master tab self.jack_device_combo = builder.get_object('jack_device_combo') self.jack_usb_dev_combo = builder.get_object('jack_usb_dev_combo') self.chan_in_spin = builder.get_object('chan_in_spin') self.chan_out_spin = builder.get_object('chan_out_spin') self.jack_rate_combo = builder.get_object('jack_rate_combo') self.combobox_late = builder.get_object('combobox_late') self.combo_periods = builder.get_object('combo_periods') self.combo_backend = builder.get_object('combo_backend') self.monitor_combo = builder.get_object('monitor_combo') self.cap_lat_spin = builder.get_object('cap_lat_spin') self.play_lat_spin = builder.get_object('play_lat_spin') self.jack_midi_check = builder.get_object('jack_midi_check') self.jack_midi_u = builder.get_object('jack_midi_u') self.jack_ind = builder.get_object('jack_ind') self.jack_state = builder.get_object('jack_state') self.dsp_label = builder.get_object('dsp_label') self.xrun_lab = builder.get_object('xrun_lab') # extra tab self.usb_plug_check = builder.get_object('usb_plug_check') self.xdev_select = builder.get_object('xdev_select') self.cap_chan_spin = builder.get_object('cap_chan_spin') self.play_chan_spin = builder.get_object('play_chan_spin') self.hide_check = builder.get_object('hide_check') self.xdev_name = builder.get_object('xdev_name') self.xdev_rate_drop = builder.get_object('xdev_rate_drop') self.xdev_buff_drop = builder.get_object('xdev_buff_drop') self.xdev_nperiods_drop = builder.get_object('xdev_nperiods_drop') self.xdev_cap_lat = builder.get_object('xdev_cap_lat') self.xdev_play_lat = builder.get_object('xdev_play_lat') # phones self.hp_action = builder.get_object('hp_action') self.hp_device = builder.get_object('hp_device') self.hp_left_pt = builder.get_object('hp_left_pt') # self.hp_switch = builder.get_object('hp_switch') # pulse tab self.pj_combo = builder.get_object('pj_combo') self.pj_direction = builder.get_object('pj_direction') self.pj_name = builder.get_object('pj_name') self.pj_count = builder.get_object('pj_count') self.pj_con = builder.get_object('pj_con') # Session Manager self.jk_connect_mode = builder.get_object('jk_connect_mode') # network tab # audio self.znet_bridge = builder.get_object('znet_bridge') self.znet_direction = builder.get_object('znet_direction') self.znet_count = builder.get_object('znet_count') self.znet_ip = builder.get_object('znet_ip') self.znet_port = builder.get_object('znet_port') self.znet_name = builder.get_object('znet_name') self.znet_bits = builder.get_object('znet_bits') self.znet_late = builder.get_object('znet_late') self.znet_warn = builder.get_object('znet_warn') # midi self.mnet_warn = builder.get_object('mnet_warn') self.mnet_count = builder.get_object('mnet_count') self.mnet_type = builder.get_object('mnet_type') # Dbus monitoring user_bus = dbus.SessionBus() user_bus.add_signal_receiver( self.db_state_cb, dbus_interface='org.studio.control.state', signal_name='state') user_bus.add_signal_receiver( self.new_jack_port, dbus_interface='org.jackaudio.JackPatchbay', signal_name='PortAppeared') user_bus.add_signal_receiver( self.new_jack_port, dbus_interface='org.jackaudio.JackPatchbay', signal_name='PortDisappeared') self.sendbs = sendbus() # Set default window icon for window managers self.window_main.set_default_icon_name( 'com.github.ovenwerks.studio-controls') # Check if audio.conf and/or audio.conf.disabled # exists, returns are true or false # self.rt_file = False self.jack_file_exists = self.sysinfo.check_pam_files() if self.jack_file_exists and self.sysinfo.user_audio(): rtprio, memlock = self.sysinfo.check_rlimits() if rtprio == 0: self.rt_button.set_label("Reboot required") self.rt_button.set_sensitive(False) self.message_dialog_rt_info.show() self.rt_warning.set_text( "Session restart required for Real Time Permissions") else: # turn off warning text, check on, deactivate self.rt_warning.set_text("") self.rt_button.set_label("Real Time Permissions Enabled") self.rt_button.set_sensitive(False) # read in autojack config file self.conf_db = auto_jack.convert() self.jackdb = self.conf_db['jack'] self.extra = self.conf_db['extra'] # show current CPU Governor self.combo_governor.append_text("Performance") if os.path.exists("/sys/devices/system/cpu/intel_pstate/"): self.combo_governor.append_text("Powersave") else: self.combo_governor.append_text("Ondemand") if self.conf_db['cpu-governor']: self.combo_governor.set_active(0) else: self.combo_governor.set_active(1) # show boost state if os.path.exists("/sys/devices/system/cpu/intel_pstate/no_turbo"): if self.conf_db['boost']: self.combo_boost.set_active(0) else: self.combo_boost.set_active(1) else: self.combo_boost.set_sensitive(False) if os.path.exists("/etc/modprobe.d/blacklist-studio.conf"): self.combo_fw.set_active_id("ffado") self.combo_backend.append("firewire", "firewire") # Audio stuff global autojack global newusb global jack_alive global jack_ports_changed global jackstate global jackstring jackstring = "unknown" jackstate = False autojack = True newusb = False jack_alive = False jack_ports_changed = True self.jack_error_mesg = "" self.jack_info_mesg = "" self.dirty = False self.not_applied = False jack.set_error_function(callback=self.jack_error) jack.set_info_function(callback=self.jack_info) self.logging_comb.set_active_id(str(self.conf_db['log-level'])) # fill in Jack master widgets self.combo_periods.set_sensitive(False) self.combo_backend.set_sensitive(False) self.chan_in_spin.set_sensitive(False) self.chan_out_spin.set_sensitive(False) self.cap_lat_spin.set_sensitive(False) self.play_lat_spin.set_sensitive(False) self.jack_midi_check.set_sensitive(False) self.jack_midi_u.set_sensitive(False) self.usb_plug_check.set_sensitive(False) self.jk_connect_mode.set_sensitive(False) self.combo_periods.set_active_id(str(self.jackdb['period'])) self.combo_backend.set_active_id(self.jackdb['driver']) self.chan_in_spin.set_range(1, 128) self.chan_out_spin.set_range(1, 128) self.chan_in_spin.set_value(self.jackdb['chan-in']) self.chan_out_spin.set_value(self.jackdb['chan-out']) self.cap_lat_spin.set_range(0, 1000) self.play_lat_spin.set_range(0, 1000) self.cap_lat_spin.set_value(self.jackdb['cap-latency']) self.play_lat_spin.set_value(self.jackdb['play-latency']) self.jack_midi_check.set_active(self.extra['a2j']) if "a2j_u" not in self.extra: self.extra['a2j_u'] = False self.jack_midi_u.set_active(self.extra['a2j_u']) # Fill Extra devices widgets self.usb_plug_check.set_active(self.extra['usbauto']) if 'phone-left' not in self.extra: self.extra['phone-left'] = '1' self.hp_left_pt.set_value(int(self.extra['phone-left'])) # pulse bridge defaults self.pj_direction.set_sensitive(False) self.pj_count.set_range(1, 99) # Session Manger settings self.jk_connect_mode.set_active_id(self.jackdb['connect-mode']) # net settings self.znet_direction.set_sensitive(False) self.combo_periods.set_sensitive(True) self.combo_backend.set_sensitive(True) self.chan_in_spin.set_sensitive(True) self.chan_out_spin.set_sensitive(True) self.cap_lat_spin.set_sensitive(True) self.play_lat_spin.set_sensitive(True) self.jack_midi_check.set_sensitive(True) self.jack_midi_u.set_sensitive(True) self.usb_plug_check.set_sensitive(True) self.jk_connect_mode.set_sensitive(True) self.refresh_dropdowns() self.pj_bridge = "" self.refresh_pulse_tab(self.pj_bridge) self.znetbridge = "" self.refresh_net(self.znetbridge) handlers = { "on_window_main_delete_event": self.on_window_main_delete_event, "on_window_help_delete_event": self.on_window_help_delete_event, "on_main_button_cancel_clicked": self.on_main_button_cancel_clicked, "on_main_button_help_clicked": self.on_main_button_help_clicked, "combo_governor_changed_cb": self.combo_governor_changed_cb, "combo_boost_changed_cb": self.combo_boost_changed_cb, "logging_change": self.logging_changed, "firewire_cb": self.firewire_cb, "rt_button_hit": self.rt_button_hit, "on_button_msg_ok_clicked": self.on_button_msg_ok_clicked, "on_button_rt_info_ok_clicked": self.on_button_rt_info_ok_clicked, "on_button_help_ok_clicked": self.on_button_help_ok_clicked, "jack_device_changed": self.jack_device_changed, "usb_master_changed": self.usb_master_changed, "jack_driver_changed": self.jack_driver_changed, "xrun_reset": self.xrun_reset, "cb_jack_start": self.cb_jack_start, "cb_jack_stop": self.cb_jack_stop, "cb_audio_apply": self.cb_audio_apply, "mixer_cb": self.mixer_cb, "pavucontrol_cb": self.pavucontrol_cb, "carla_cb": self.carla_cb, "generic_cb": self.generic_cb, "xdev_select_cb": self.xdev_select_cb, "xdev_changed": self.xdev_changed, "xdev_cap_all_cb": self.xdev_cap_all_cb, "xdev_play_all_cb": self.xdev_play_all_cb, "usb_plug_cb": self.usb_plug_cb, "hp_action_cb": self.hp_action_cb, "hp_switch_cb": self.hp_switch_cb, "switchtomon_cb": self.switchtomon_cb, "pj_combo_cb": self.pj_combo_cb, "pj_add_cb": self.pj_add_cb, "pj_rem_cb": self.pj_rem_cb, "pj_name_cb": self.pj_name_cb, "znet_bridge_cb": self.znet_bridge_cb, "znet_changed_cb": self.znet_changed_cb, "znet_add_cb": self.znet_add_cb, "znet_rem_cb": self.znet_rem_cb, "mnet_count_cb": self.mnet_count_cb, "mnet_type_cb": self.mnet_type_cb, "ray_cb": self.ray_cb, "nsm_cb": self.nsm_cb, "agordejo_cb": self.agordejo_cb, } builder.connect_signals(handlers) self.timeout_id = GLib.timeout_add(500, self.check_jack_status, None) self.signal_autojack("ping") autojack = False print("initialization complete") def jack_error(self, mesg): if self.jackdb['on']: if "not running" in mesg: print(f"jack message received: {mesg}") def generic_cb(self, widget): ''' changes have been made but not yet applied ''' if not widget.get_sensitive(): return self.not_applied = True def jack_info(self, mesg): print(f"jack_info received: {mesg}") def db_state_cb(*args, **kwargs): ''' received signal from autojack ''' global version global autojack global jackstate global jackstring global newusb if version == args[1]: if not autojack: autojack = True print("autojack is running") else: print( f"Autojack version is {args[1]} will be" "restarted on any command") if args[2] == 'usb': newusb = True print("autojack sees usb change") else: jackstring = args[2] jackstate = True print(f"version: {args[1]} status:{args[2]}") def check_jack_status(self, user_data): '''Check if jack has died and the client needs to be closed. Check if jack is running then set jack status indicator. Check to see if the device lists have changed and update the gui if so. Updating GUI prevents race with secondary updates caused by updating''' # these variables need to be global as they are used by callbacks global newusb global jack_client global jack_alive global jack_ports_changed global jackstate global jackstring bus = dbus.SessionBus() controller = bus.get_object( "org.jackaudio.service", "/org/jackaudio/Controller") control_iface = dbus.Interface( controller, "org.jackaudio.JackControl") jack_check = False if jackstring[0:4] == "Stop": jack_check = False else: jack_check = True if jackstate: # This signal comes from autojack self.jack_state.set_text(f" {jackstring}") jackstate = False if int(control_iface.IsStarted()) and jack_check: xrun_count = control_iface.GetXruns() self.xrun_lab.set_text(f" {str(xrun_count)}") load = control_iface.GetLoad() self.dsp_label.set_text(f"DSP: {str(load)[0:4]}%") if not jack_alive: try: jack_client = jack.Client( 'controls', use_exact_name=False, no_start_server=True) except jack.JackError: self.xrun_lab.set_text(" 0") return True jack_client.activate() jack_alive = True jack_ports_changed = True self.refresh_pulse_tab(self.pj_bridge) else: if jack_alive: # Jack has just stopped jack_client.deactivate() jack_client.close() jack_check = True jack_alive = False self.dirty = True self.refresh_pulse_io() self.dsp_label.set_text("DSP: 0%") self.xrun_lab.set_text(" 0") # device changed, update GUI if self.dirty or newusb: self.refresh_dropdowns() self.dirty = False newusb = False if jack_ports_changed: jack_ports_changed = False self.refresh_pulse_io() return True def new_jack_port(*args, **kwargs): ''' jack has added a port tell someone ''' global jack_ports_changed jack_ports_changed = True def xrun_reset(self, button): ''' user asked for an xrun reset so do it ''' bus = dbus.SessionBus() controller = bus.get_object( "org.jackaudio.service", "/org/jackaudio/Controller") control_iface = dbus.Interface(controller, "org.jackaudio.JackControl") control_iface.ResetXruns() def refresh_dropdowns(self): '''this call refreshes the device lists for all drop downs that use devices. If backend is not "alsa" then the jack master and USB master are set but not changable However, all alsa devices will still be available for bridging and as output device. ''' temp_db = auto_jack.check_devices(self.conf_db) # refresh devices for temp_dev in temp_db['devices']: # transfer current device info into db without changing # settings that may not yet be saved dev_db = self.conf_db['devices'][temp_dev] check_db = temp_db['devices'][temp_dev] if temp_dev in self.conf_db['devices']: dev_db['number'] = check_db['number'] dev_db['id'] = check_db['id'] dev_db['bus'] = check_db['bus'] else: dev_db = temp_db['devices'][temp_dev] xdev_current = self.xdev_select.get_active_id() self.current_ex_dev = xdev_current fw_mixer = False # driver is probably ok to leave above self.combo_backend.set_active_id(self.jackdb['driver']) self.cap_lat_spin.set_value(self.jackdb['cap-latency']) self.play_lat_spin.set_value(self.jackdb['play-latency']) self.jack_device_combo.set_sensitive(False) self.jack_usb_dev_combo.set_sensitive(False) self.chan_in_spin.set_sensitive(False) self.chan_out_spin.set_sensitive(False) self.combobox_late.set_sensitive(False) self.combo_periods.set_sensitive(False) self.jack_rate_combo.set_sensitive(False) self.xdev_select.set_sensitive(False) self.mixer_start.set_sensitive(False) # popdown any combo boxes before changing self.jack_device_combo.popdown() self.xdev_select.popdown() self.jack_usb_dev_combo.popdown() self.jack_rate_combo.popdown() self.combobox_late.popdown() self.mixer_start.popdown() self.jack_device_combo.get_model().clear() self.xdev_select.get_model().clear() self.jack_usb_dev_combo.get_model().clear() self.jack_rate_combo.get_model().clear() self.combobox_late.get_model().clear() self.mixer_start.get_model().clear() self.mixer_start.append("none", "Open Device Mixer") self.mixer_start.set_active_id("none") if self.combo_fw.get_active_id() == "ffado": self.mixer_start.append("ffado", "FFADO Device Mixer") fw_mixer = True rates = [] def_rates = ['32000', '44100', '48000', '88200', '96000', '192000'] frames = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096] self.jack_usb_dev_combo.append("none", "No USB Master") if self.jackdb['usbdev'] == "" or self.jackdb['usbdev'] == "none": self.jack_usb_dev_combo.set_active_id("none") self.hp_device.popdown() self.hp_device.get_model().clear() self.hp_device.append( self.extra['phone-device'], self.extra['phone-device']) self.hp_device.set_active_id(self.extra['phone-device']) if self.extra['phone-device'] != 'system': self.hp_device.append( 'system', 'JACK Master (system:*)') for this_dev in self.conf_db['devices']: dev_db = self.conf_db['devices'][this_dev] if 'firewire' in dev_db and dev_db['firewire'] and not fw_mixer: self.mixer_start.append("ffado", "FFADO Device Mixer") fw_mixer = True if dev_db['number'] != -1: # we only want plugged devices self.mixer_start.append(dev_db['raw'], dev_db['raw']) for this_sub in dev_db['sub']: sub_db = dev_db['sub'][this_sub] d_type = "" if sub_db['capture']: d_type = "capture" if sub_db['playback']: if d_type == "": d_type = "playback" else: d_type = f"{d_type} and playback" next_id = "" dname = "" next_d = "" next_id = f"{this_dev},{this_sub},0" if dev_db['usb']: dname = f"({dev_db['raw']})" if dev_db['number'] == -1: dname = "(unplugged)" next_d = f"{next_id} {dname} {d_type} {sub_db['description']}" if "Loopback" in [this_dev]: self.xdev_select.append(next_id, next_d) else: self.xdev_select.insert(0, next_id, next_d) if not sub_db['hide']: # not hidden so we can add it if "Loopback" not in [this_dev]: self.hp_device.append(next_id, next_d) if dev_db['usb']: self.jack_usb_dev_combo.append(next_id, next_d) else: self.jack_device_combo.append(next_id, next_d) if next_id == self.extra['phone-device']: self.hp_device.set_active_id(next_id) if next_id == self.jackdb['dev']: self.jack_device_combo.set_active_id(next_id) if self.jackdb['usbdev'] == "none": # this is jack master get rates rates = dev_db['rates'] if next_id == self.jackdb['usbdev']: self.jack_usb_dev_combo.set_active_id(next_id) # this is jack master get rates rates = dev_db['rates'] if self.jackdb['driver'] != "alsa" or rates == []: rates = def_rates for rate in rates: self.jack_rate_combo.append(rate, rate) if str(self.jackdb['rate']) in rates: self.jack_rate_combo.set_active_id(str(self.jackdb['rate'])) else: self.jack_rate_combo.set_active(0) minlat = 16 fw = False if self.jackdb['driver'] == "alsa": if self.jackdb['usbdev'] != 'none': minlat = self.conf_db[ 'devices'][ self.jackdb['usbdev'].split(',')[0]][ 'min_latency'] else: minlat = self.conf_db['devices'][self.jackdb['dev'].split(',')[ 0]]['min_latency'] fw = self.conf_db['devices'][self.jackdb['dev'].split(',')[ 0]]['firewire'] for frame in frames: tplt = "" if frame == minlat and fw: tplt = " For lower latency Use the FFADO kernal modules." if frame >= minlat: self.combobox_late.append(str(frame), f"{str(frame)}{tplt}") if self.jackdb['frame'] in frames and self.jackdb['frame'] >= minlat: self.combobox_late.set_active_id(str(self.jackdb['frame'])) else: self.combobox_late.set_active_id(str(minlat)) self.mixer_start.set_sensitive(True) self.jack_rate_combo.set_sensitive(True) self.combobox_late.set_sensitive(True) if self.jackdb['driver'] == "alsa": self.jack_device_combo.set_sensitive(True) self.jack_usb_dev_combo.set_sensitive(True) self.combo_periods.set_sensitive(True) elif self.jackdb['driver'] == "firewire": self.combo_periods.set_sensitive(True) elif self.jackdb['driver'] == "dummy": self.chan_in_spin.set_sensitive(True) self.chan_out_spin.set_sensitive(True) self.redraw_extra(xdev_current) '''Functions for all the gui controls''' def on_window_help_delete_event(self, window, event): self.window_help.hide_on_delete() return True def on_main_button_help_clicked(self, button): self.window_help.show() def rt_button_hit(self, button): pke = shutil.which("pkexec") if pke is None: print("no pkexec, Fail") return subprocess.run( [pke, f"{install_path}/sbin/studio-system", "fix"], shell=False) self.rt_button.set_label("Logout required") self.rt_button.set_sensitive(False) self.message_dialog_rt_info.show() self.rt_warning.set_text( "Session restart required for Real Time Permissions") # system tweaks def combo_governor_changed_cb(self, button): newval = False if button.get_active_text() == "Performance": newval = True if self.conf_db['cpu-governor'] != newval: self.conf_db['cpu-governor'] = newval self.cb_audio_apply(button) def combo_boost_changed_cb(self, button): newval = False if button.get_active_text() == "off": newval = True if self.conf_db['boost'] != newval: self.conf_db['boost'] = newval self.cb_audio_apply(button) def logging_changed(self, widget): newval = widget.get_active_id() if self.conf_db['log-level'] != newval: self.conf_db['log-level'] = newval self.cb_audio_apply(widget) def firewire_cb(self, widget): newval = widget.get_active_id() if newval == "alsa" or newval == "ffado": pke = shutil.which("pkexec") if pke is None: print("no pkexec, Fail") return subprocess.run( [pke, f"{install_path}/sbin/studio-system", newval], shell=False) # Audio setup call backs def xdev_select_cb(self, widget): a_id = str(widget.get_active_id()) if a_id != "None" and a_id != self.current_ex_dev: self.redraw_extra(a_id) def xdev_changed(self, widget): # self.xdev_select.set_sensitive(False) if not self.xdev_select.get_sensitive(): return self.cap_chan_spin.set_sensitive(False) self.play_chan_spin.set_sensitive(False) self.hide_check.set_sensitive(False) self.xdev_rate_drop.set_sensitive(False) self.xdev_buff_drop.set_sensitive(False) self.xdev_nperiods_drop.set_sensitive(False) self.xdev_cap_lat.set_sensitive(False) self.xdev_play_lat.set_sensitive(False) self.xdev_name.set_sensitive(False) this_dev = str(self.xdev_select.get_active_id()) dev_db = self.conf_db['devices'][this_dev.split(',')[0]] sub_db = dev_db['sub'][str(this_dev.split(',')[1])] sub_db['cap-chan'] = self.cap_chan_spin.get_value_as_int() sub_db['play-chan'] = self.play_chan_spin.get_value_as_int() sub_db['hide'] = self.hide_check.get_active() sub_db['rate'] = int(self.xdev_rate_drop.get_active_id()) sub_db['frame'] = int(self.xdev_buff_drop.get_active_id()) sub_db['nperiods'] = int(self.xdev_nperiods_drop.get_active_id()) sub_db['name'] = self.xdev_name.get_text().split(' ')[0] # get_value_as_int self.hide_check.set_sensitive(True) if not sub_db['hide']: self.cap_chan_spin.set_sensitive(True) self.play_chan_spin.set_sensitive(True) self.xdev_rate_drop.set_sensitive(True) self.xdev_buff_drop.set_sensitive(True) self.xdev_nperiods_drop.set_sensitive(True) self.xdev_cap_lat.set_sensitive(True) self.xdev_play_lat.set_sensitive(True) self.xdev_name.set_sensitive(True) def xdev_cap_all_cb(self, widget): if not widget.get_sensitive(): return self.not_applied = True widget.set_sensitive(False) # self.cap_chan_spin.set_sensitive(False) # widget.set_active(False) if widget.get_label() == ' All ': self.cap_chan_spin.set_value(100) widget.set_label(' Off') else: widget.set_label(' All ') self.cap_chan_spin.set_value(0) widget.set_sensitive(True) self.cap_chan_spin.set_sensitive(True) def xdev_play_all_cb(self, widget): if not widget.get_sensitive(): return self.not_applied = True widget.set_sensitive(False) # self.play_chan_spin.set_sensitive(False) # widget.set_active(False) if widget.get_label() == ' All ': self.play_chan_spin.set_value(100) widget.set_label(' Off') else: widget.set_label(' All ') self.play_chan_spin.set_value(0) widget.set_sensitive(True) self.play_chan_spin.set_sensitive(True) def redraw_extra(self, next_device): ''' change all widgets to reflect current values of the selected device ''' frames = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096] self.xdev_select.set_sensitive(False) self.cap_chan_spin.set_sensitive(False) self.play_chan_spin.set_sensitive(False) self.hide_check.set_sensitive(False) self.xdev_rate_drop.set_sensitive(False) self.xdev_buff_drop.set_sensitive(False) self.xdev_nperiods_drop.set_sensitive(False) self.xdev_cap_lat.set_sensitive(False) self.xdev_play_lat.set_sensitive(False) self.xdev_name.set_sensitive(False) self.xdev_rate_drop.popdown() self.xdev_buff_drop.popdown() self.xdev_rate_drop.get_model().clear() self.xdev_buff_drop.get_model().clear() if next_device == 'none': for def_dev in self.conf_db['devices']: next_device = f"{def_dev},0,0" break self.xdev_select.set_active_id(next_device) dev_db = self.conf_db['devices'][next_device.split(',')[0]] sub_db = dev_db['sub'][str(next_device.split(',')[1])] if dev_db['number'] == -1: # device is unplugged, some info may be missing if 'rates' not in dev_db: # the device may not handle all these rates # but missing some would be bad dev_db['rates'] = ["32000", "44100", "48000", "88200", "96000", "192000"] self.cap_chan_spin.set_value(sub_db['cap-chan']) self.play_chan_spin.set_value(sub_db['play-chan']) self.hide_check.set_active(sub_db['hide']) for rate in dev_db['rates']: self.xdev_rate_drop.append(str(rate), str(rate)) self.xdev_rate_drop.set_active_id(str(sub_db['rate'])) for frame in frames: if dev_db['min_latency'] <= frame: self.xdev_buff_drop.append(str(frame), str(frame)) self.xdev_buff_drop.set_active_id(str(sub_db['frame'])) self.xdev_nperiods_drop.set_active_id(str(sub_db['nperiods'])) if sub_db['name'] == 'none': sub_db['name'] = next_device if not self.dirty: self.dirty = True self.xdev_name.set_text(sub_db['name']) self.xdev_cap_lat.set_value(sub_db['cap-latency']) self.xdev_play_lat.set_value(sub_db['cap-latency']) self.xdev_select.set_sensitive(True) self.hide_check.set_sensitive(True) if not sub_db['hide']: self.cap_chan_spin.set_sensitive(True) self.play_chan_spin.set_sensitive(True) self.xdev_rate_drop.set_sensitive(True) self.xdev_buff_drop.set_sensitive(True) self.xdev_nperiods_drop.set_sensitive(True) self.xdev_cap_lat.set_sensitive(True) self.xdev_play_lat.set_sensitive(True) self.xdev_name.set_sensitive(True) def hp_action_cb(self, widget): if not widget.get_sensitive(): return self.not_applied = True a_id = str(widget.get_active_id()) if a_id == "script": print("Script is called ~/.config/autojack/phones.sh") # this needs to be shown as a dialog def hp_switch_cb(self, button): self.signal_autojack("phones") def switchtomon_cb(self, button): self.signal_autojack("monitor") def jack_device_changed(self, button): if not button.get_sensitive(): return self.not_applied = True a_id = str(button.get_active_id()) a_desc = str(button.get_active_text()) if a_id != "None": self.conf_db['jack']['dev'] = a_id self.dev_desc = a_desc if not self.dirty: self.dirty = True def jack_driver_changed(self, button): if not button.get_sensitive(): return self.not_applied = True a_driver = str(button.get_active_text()) self.conf_db['jack']['driver'] = a_driver if not self.dirty: self.dirty = True def usb_master_changed(self, button): if not button.get_sensitive(): return self.not_applied = True a_id = str(button.get_active_id()) if a_id != "None": self.conf_db['jack']['usbdev'] = a_id if not self.dirty: self.dirty = True def usb_plug_cb(self, widget): if not widget.get_sensitive(): return self.not_applied = True a_id = widget.get_active() # Pulse bridge calls def refresh_pulse_io(self): ''' the ports that can be connected to pulse ports varies with what jack offers. This refreshes the two drop downs ''' global jack_client global jack_alive # need to know what the current direction is self.pj_con.set_sensitive(False) self.monitor_combo.set_sensitive(False) direction = self.pj_direction.get_active_id() our_db = self.conf_db['pulse']['outputs'] if direction == 'in': our_db = self.conf_db['pulse']['inputs'] br_exists = False if our_db != {}: br_exists = True if self.pj_bridge == "": for br in in_db: self.pj_bridge = br break self.pj_con.popdown() self.pj_con.get_model().clear() self.pj_con.append("none", "no connection") if direction == 'out': self.pj_con.append("monitor", "Main Output Ports") self.pj_con.set_active_id('none') if br_exists: our_con = our_db[self.pj_bridge]['connection'] if our_con != 'none': self.pj_con.append(our_con, our_con) self.pj_con.set_active_id(our_con) self.monitor_combo.popdown() self.monitor_combo.get_model().clear() monitor = self.conf_db['extra']["monitor"] self.monitor_combo.append(monitor, monitor) self.monitor_combo.set_active_id(monitor) if jack_alive: if direction == 'in': # get capture ports with audio and hardware jack_cap = jack_client.get_ports( "", is_audio=True, is_output=True, is_physical=True) extra = "" last_dev = "" for jport in jack_cap: port = jport.name dev = port.split(':', 1)[0] paliases = jport.aliases for palias in paliases: adev = palias.split(':', 1)[0] if adev == "system": extra = f" <{dev}>" dev = adev port = palias self.pj_con.append(port, f"{port} {extra}") if br_exists and port == our_db[ self.pj_bridge]['connection']: self.pj_con.remove(self.pj_con.get_active()) self.pj_con.set_active_id(port) jack_play = jack_client.get_ports( "", is_audio=True, is_input=True, is_physical=True) extra = "" last_dev = "" for jport in jack_play: port = jport.name dev = port.split(':', 1)[0] paliases = jport.aliases for palias in paliases: adev = palias.split(':', 1)[0] if adev == "system": extra = f" <{dev}>" dev = adev port = palias if direction == 'out': self.pj_con.append(port, f"{port} {extra}") if br_exists and port == our_db[ self.pj_bridge]['connection']: self.pj_con.remove(self.pj_con.get_active()) self.pj_con.set_active_id(port) self.monitor_combo.append(port, f"{port} {extra}") if port == monitor: self.monitor_combo.remove(self.monitor_combo.get_active()) self.monitor_combo.set_active_id(monitor) else: self.pj_con.append( "none", "Ports cannot be displayed unless JACK is running.") self.monitor_combo.append( "none", "Ports cannot be displayed unless JACK is running.") self.pj_con.set_sensitive(True) self.monitor_combo.set_sensitive(True) def refresh_pulse_tab(self, new_bridge): ''' Fill in all pulse related widgets ''' global jack_ports_changed out_db = self.conf_db['pulse']['outputs'] in_db = self.conf_db['pulse']['inputs'] self.pj_combo.set_sensitive(False) self.pj_direction.set_sensitive(False) self.pj_name.set_sensitive(False) self.pj_count.set_sensitive(False) self.pj_con.set_sensitive(False) self.pj_combo.popdown() self.pj_combo.get_model().clear() if out_db == {} and in_db == {}: # no bridges self.pj_combo.set_active(0) self.pj_name.set_text("") self.pj_count.set_value(1) self.pj_con.set_active_id('none') self.pj_direction.set_active_id('out') self.pj_bridge = '' else: for bridge in out_db: self.pj_combo.append(bridge, bridge) if not new_bridge: new_bridge = bridge for bridge in in_db: self.pj_combo.append(bridge, bridge) if not new_bridge: new_bridge = bridge our_db = {} if new_bridge in out_db: our_db = out_db self.pj_direction.set_active_id('out') if new_bridge in in_db: our_db = in_db self.pj_direction.set_active_id('in') self.pj_combo.set_active_id(new_bridge) self.pj_name.set_text(new_bridge) self.pj_count.set_value(our_db[new_bridge]['count']) self.pj_con.set_active_id(our_db[new_bridge]['connection']) self.pj_bridge = new_bridge self.pj_combo.set_sensitive(True) self.pj_name.set_sensitive(True) self.pj_count.set_sensitive(True) self.pj_con.set_sensitive(True) # rebuild the the connection dropdowns jack_ports_changed = True def pj_name_cb(self, widget): ''' call back for any pulse bridge input name or connect change to current values ''' if not widget.get_sensitive(): return self.not_applied = True if self.pj_direction.get_active_id() == 'in': temp_db = self.conf_db['pulse']['inputs'] if self.pj_direction.get_active_id() == 'out': temp_db = self.conf_db['pulse']['outputs'] if temp_db != {}: old_name = self.pj_combo.get_active_id() new_name = self.pj_name.get_text().split()[0] if new_name != old_name: if old_name in temp_db: temp_db[new_name] = temp_db.pop(old_name) else: temp_db[new_name][ 'connection'] = f"{self.pj_con.get_active_id()}" temp_db[new_name][ 'count'] = self.pj_count.get_value_as_int() self.refresh_pulse_tab(new_name) def pj_combo_cb(self, widget): ''' callback to look at different pa bridge. need to save name and connection, then refresh name and connections to match ''' if not widget.get_sensitive(): return if widget.get_active() < 0: return self.pj_bridge = self.pj_combo.get_active_id() self.refresh_pulse_tab(self.pj_bridge) def pj_add_cb(self, widget): ''' need to create a name for the bridge and assign connect as "none". Before switching to display the new bridge we need to save the current bridge info ''' if not widget.get_sensitive(): return direct = 'out' temp_db = {} if widget.get_active_id() == 'in': direct = 'in' temp_db = self.conf_db['pulse']['inputs'] elif widget.get_active_id() == 'out': direct = 'out' temp_db = self.conf_db['pulse']['outputs'] else: widget.set_active_id('label') return widget.set_sensitive(False) self.not_applied = True indx = 1 done = False new_name = "" while not done: new_name = f"pulse{str(indx)}-{direct}" if new_name not in temp_db: done = True else: indx = indx + 1 temp_db[new_name] = { 'connection': "none", 'count': 2 } self.refresh_pulse_tab(new_name) widget.set_active_id('label') widget.set_sensitive(True) def pj_rem_cb(self, widget): ''' get index of current bridge remove name from list by index remove connection from list by index ''' if not widget.get_sensitive(): return self.not_applied = True name = self.pj_combo.get_active_id() if name in self.conf_db['pulse']['inputs']: del self.conf_db['pulse']['inputs'][name] elif name in self.conf_db['pulse']['outputs']: del self.conf_db['pulse']['outputs'][name] self.pj_bridge = "" self.refresh_pulse_tab(self.pj_bridge) # network callbacks and refresh def refresh_net(self, new_bridge): ''' set up values for first bridge and midi ''' # midi self.mnet_count.set_sensitive(False) self.mnet_type.set_sensitive(False) qmn = shutil.which("qmidinet") if qmn is None: self.mnet_warn.set_text("Please Install qmidinet First") self.mnet_count.set_value(0) else: self.mnet_warn.set_text("") self.mnet_count.set_value(self.conf_db['mnet']['count']) self.mnet_type.set_active_id(self.conf_db['mnet']['type']) self.mnet_count.set_sensitive(True) self.mnet_type.set_sensitive(True) # zita-njbridge znet_db = self.conf_db['znet'] self.znet_bridge.set_sensitive(False) self.znet_direction.set_sensitive(False) self.znet_count.set_sensitive(False) self.znet_ip.set_sensitive(False) self.znet_port.set_sensitive(False) self.znet_bits.set_sensitive(False) self.znet_name.set_sensitive(False) self.znet_late.set_sensitive(False) znj = shutil.which("zita-n2j") if znj is None: self.znet_warn.set_text("Please Install zita-njbridge First") else: self.znet_warn.set_text("") self.znet_bridge.get_model().clear() our_ip = "0.0.0.0" if znet_db == {}: # no bridges self.znet_bridge.set_active(0) self.znet_direction.set_active_id('out') self.znet_count.set_value(1) self.znet_ip.set_text(our_ip) self.znet_port.set_value(8300) self.znet_bits.set_active_id('none') self.znet_late.set_value(0) self.znet_name.set_text("") self.znetbridge = "" return else: for bridge in znet_db: self.znet_bridge.append(bridge, bridge) if not new_bridge: new_bridge = bridge self.znet_direction.set_active_id( znet_db[new_bridge]['direction']) self.znet_bridge.set_active_id(new_bridge) self.znet_name.set_text(new_bridge) self.znet_count.set_value(znet_db[new_bridge]['count']) self.znet_ip.set_text(znet_db[new_bridge]['ip']) self.znet_port.set_value(znet_db[new_bridge]['port']) if znet_db[new_bridge]['direction'] == 'out': self.znet_bits.set_active_id(znet_db[new_bridge]['bits']) self.znet_late.set_value(0) else: self.znet_bits.set_active_id('none') self.znet_late.set_value(znet_db[new_bridge]['latency']) self.znetbridge = new_bridge if shutil.which("zita-n2j") is None: self.znet_warn.set_text("Please install zita-njbridge") else: self.znet_warn.set_text("") if znet_db[new_bridge]['direction'] == 'in': self.znet_late.set_sensitive(True) if znet_db[new_bridge]['direction'] == 'out': self.znet_bits.set_sensitive(True) self.znet_bridge.set_sensitive(True) self.znet_count.set_sensitive(True) self.znet_ip.set_sensitive(True) self.znet_port.set_sensitive(True) self.znet_name.set_sensitive(True) # audio def znet_bridge_cb(self, widget): ''' callback to look at different znet bridge. need to save name and connection, then refresh name and connections to match ''' if not widget.get_sensitive(): return if widget.get_active() < 0: return self.znetbridge = self.znet_bridge.get_active_id() self.refresh_net(self.znetbridge) def znet_changed_cb(self, widget): if not widget.get_sensitive(): return this_db = self.conf_db['znet'] if this_db != {}: old_name = self.znet_bridge.get_active_id() new_name = self.znet_name.get_text().split()[0] if new_name != old_name: if old_name in this_db: this_db[new_name] = this_db.pop(old_name) else: # probably a "" string return this_db[new_name]['count'] = self.znet_count.get_value_as_int() this_db[new_name]['ip'] = self.znet_ip.get_text() this_db[new_name]['port'] = self.znet_port.get_value_as_int() if this_db[new_name]['direction'] == 'out': if self.znet_bits.get_active_id() != 'none': this_db[new_name]['bits'] = self.znet_bits.get_active_id() else: this_db[new_name]['bits'] = 'none' if this_db[new_name]['direction'] == 'in': this_db[new_name][ 'latency'] = self.znet_late.get_value_as_int() else: this_db[new_name]['latency'] = 0 self.not_applied = True self.refresh_net(new_name) def znet_add_cb(self, widget): ''' need to create a name for the bridge and default values before switching to display the new bridge ''' if not widget.get_sensitive(): return direct = 'out' if not widget.get_active_id() in ['in', 'out']: widget.set_active_id('label') return widget.set_sensitive(False) self.not_applied = True indx = 1 done = False new_name = "" temp_db = self.conf_db['znet'] while not done: new_name = f"net{str(indx)}-{widget.get_active_id()}" if new_name not in temp_db: done = True else: indx = indx + 1 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] s.close() late = 10 bits = "none" if widget.get_active_id() == 'out': ipl = ip.split('.') ip = f"{ipl[0]}.{ipl[1]}.{ipl[2]}." late = 0 bits = "16bit" dialog = Gtk.MessageDialog( transient_for=self.window_main, flags=0, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK, text="Warning, The IP is not complete", ) response = dialog.run() dialog.destroy() temp_db[new_name] = { 'direction': widget.get_active_id(), 'ip': ip, 'port': 8300, 'bits': bits, 'latency': late, 'count': 2 } self.refresh_net(new_name) widget.set_active_id('label') widget.set_sensitive(True) def znet_rem_cb(self, widget): ''' Remove the currently displayed bridge and refresh display (which defaults to the first in the list)''' if not widget.get_sensitive(): return self.not_applied = True name = self.znet_bridge.get_active_id() if name in self.conf_db['znet']: del self.conf_db['znet'][name] self.znetbridge = "" self.refresh_net(self.znetbridge) # midi def mnet_count_cb(self, widget): if not widget.get_sensitive(): return self.conf_db['mnet']['count'] = widget.get_value_as_int() self.not_applied = True def mnet_type_cb(self, widget): if not widget.get_sensitive(): return self.conf_db['mnet']['type'] = widget.get_active_id() self.not_applied = True # External applications calls def mixer_cb(self, widget): '''callback for mixer button. This starts QASMixer with the device set to whatever is jack master''' if not widget.get_sensitive(): return widget.set_sensitive(False) widget.popdown() if widget.get_active == -1: widget.set_active_id("none") return if widget.get_active_id() == "none": return if widget.get_active_id() == "ffado": ffm = shutil.which("ffado-mixer") if ffm is None: print("ffado-mixer not installed") else: try: subprocess.Popen([ffm], shell=False).pid except Exception: print("ffado-mixer errors are normal :P") else: mixdevice = widget.get_active_id() qasm = shutil.which("qasmixer") if qasm is None: print("qasmixer not installed") else: subprocess.Popen( [qasm, "-n", f"--device=hw:{str(mixdevice)}"], shell=False).pid widget.set_active_id("none") widget.set_sensitive(True) def pavucontrol_cb(self, button): '''callback for pulse control button, opens pavucontrol''' pvc = shutil.which("pavucontrol") if pvc is None: pvc = shutil.which("pavucontrol-qt") if pvc is None: print("pavucontrol not installed") else: subprocess.Popen([pvc], shell=False).pid def carla_cb(self, button): '''callback for carla button, opens carla''' crla = shutil.which("carla") if crla is None: button.set_label("Please Install Carla First") else: subprocess.Popen([crla], shell=False).pid def ray_cb(self, button): '''callback for raysession button, opens raysession''' rays = shutil.which("raysession") if rays is None: button.set_label("Please Install RaySession First") else: subprocess.Popen([rays], shell=False).pid def nsm_cb(self, button): '''callback for nsm button, opens New Session Manager''' ns = shutil.which("nsm-legacy-gui") if ns is None: button.set_label("Please Install New Session Manager First") else: subprocess.Popen([ns], shell=False).pid def agordejo_cb(self, button): '''callback for agordejo button, opens agordejo''' agdo = shutil.which("agordejo") if agdo is None: button.set_label("Please Install Agordejo First") else: subprocess.Popen([agdo], shell=False).pid # Autojack signalling calls def cb_jack_start(self, button): ''' call back for Jack (re)start button''' self.jackdb['on'] = True self.config_save() self.not_applied = False self.signal_autojack("start") def cb_jack_stop(self, button): self.jackdb['on'] = False self.config_save() self.not_applied = False self.signal_autojack("stop") def cb_audio_apply(self, button): '''callback for audio tab apply button''' self.config_save() self.not_applied = False self.signal_autojack("config") def config_save(self): ''' Write audio setting to ~/.config/autojack/autojack.json''' self.jackdb['chan-in'] = int(self.chan_in_spin.get_value_as_int()) self.jackdb['chan-out'] = int(self.chan_out_spin.get_value_as_int()) self.jackdb['rate'] = int(self.jack_rate_combo.get_active_id()) self.jackdb['frame'] = int(self.combobox_late.get_active_id()) self.jackdb['period'] = int(self.combo_periods.get_active_id()) self.jackdb['connect-mode'] = str(self.jk_connect_mode.get_active_id()) self.jackdb['cap-latency'] = int(self.cap_lat_spin.get_value_as_int()) self.jackdb['play-latency'] = int( self.play_lat_spin.get_value_as_int()) self.extra['a2j'] = self.jack_midi_check.get_active() self.extra['a2j_u'] = self.jack_midi_u.get_active() self.extra['usbauto'] = self.usb_plug_check.get_active() self.extra['monitor'] = str(self.monitor_combo.get_active_id()) self.extra['phone-action'] = str(self.hp_action.get_active_id()) self.extra['phone-device'] = str(self.hp_device.get_active_id()) self.extra['phone-left'] = str(self.hp_left_pt.get_value_as_int()) auto_jack.our_db = self.conf_db auto_jack.write_new() time.sleep(1) return def signal_autojack(self, signal): global autojack if autojack: self.sendbs.signal(signal) else: time.sleep(5) if autojack: self.sendbs.signal(signal) else: print("Starting Autojack...") # first tell any old autojack to die self.sendbs.signal('quit') # do it subprocess.Popen( [f"{install_path}/bin/autojack"], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, shell=False).pid def on_button_help_ok_clicked(self, button): self.window_help.hide() def on_button_rt_info_ok_clicked(self, button): self.message_dialog_rt_info.hide() # All the ways we can die def on_button_msg_ok_clicked(self, button): self.we_die() def on_main_button_cancel_clicked(self, button): self.we_die() def on_window_main_delete_event(self, *args): self.we_die() def sig_handler(self, signum, frame): ''' a handler for system signals that may be sent by the system. we want to trap sigint, sigkill and sigterm and do the same as above. ''' self.we_die() def we_die(self): global jack_alive global jack_client global lock_file if jack_alive: jack_client.close() # self.window_main = builder.get_object('window_main') if self.not_applied: dialog = Gtk.MessageDialog( transient_for=self.window_main, flags=0, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.NONE, text="Warning, New Settings have not been Applied", ) dialog.add_buttons("Apply Settings", 10, "Exit", 11) response = dialog.run() if response == 10: self.config_save() self.not_applied = False self.signal_autojack("config") dialog.destroy() if os.path.isfile(lock_file): new_pid = str(os.getpid()) if os.path.isfile(lock_file): with open(lock_file, "r") as lk_file: for line in lk_file: # only need one line old_pid = line.rstrip() if new_pid == old_pid: os.remove(lock_file) Gtk.main_quit() us = StudioControls() us.window_main.show_all() Gtk.main() studio-controls-2.3.9/usr/lib/000077500000000000000000000000001432334377300162655ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/python3/000077500000000000000000000000001432334377300176715ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/python3/dist-packages/000077500000000000000000000000001432334377300224105ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/python3/dist-packages/auto_jack.py000066400000000000000000001165641432334377300247370ustar00rootroot00000000000000 # common calls for both autojack and studio-controls import configparser import fcntl import getopt import glob import json import os import re import shutil import sys import time from os.path import expanduser global install_path install_path = os.path.abspath(f"{sys.path[0]}/..") global vers vers = "not installed" global config_path global log global new_name global old_name global temp_name # config_path = "~/software/Studio/controls-debug/" config_path = "~/.config/autojack/" new_name = f"{config_path}autojack.json" old_name = f"{config_path}/autojackrc" temp_name = f"{config_path}/temp_aj.json" # log = False def version(): ''' get the version we are running as. Used mostly to detect if a new version has been installed but the old version of autojack is still running. Rather than restart autojack right away, stopping audio, studio-controls will do so at the next apply, jack start or stop ''' global install_path global vers if vers == "not installed": vfile = f"{install_path}/share/studio-controls/version" if os.path.isfile(vfile): with open(vfile, "r") as version_file: for line in version_file: vers = line.strip() # return after first line return vers return vers def check_user(): """ Check that user is a human and is not root """ uid = os.getuid() if uid == 0: print("JACK should never be run as root: goodbye") os._exit(0) elif uid < 1000: print("Studio controls is not meant to be run as a system process") os._exit(0) def get_gvnr_set(): """ Find out if old style cpu governor setting has been used """ cpu_gov = False boost = False govfile = '/etc/default/studio' if os.path.isfile(govfile): with open(govfile, "r") as g_file: for line in g_file: if 'NO_TURBO' in line: if line.rstrip().find("1") > -1: boost = True if 'GOVERNOR' in line: if line.rstrip().find("performance") > -1: cpu_gov = True return cpu_gov, boost def get_gvnr_hw(): """ Find out what the HW is set to now """ perf = False no_boost = False boost_path = "/sys/devices/system/cpu/intel_pstate/no_turbo" if os.path.exists(boost_path): with open(boost_path, "r") as boost_test: for line in boost_test: if re.match("1", line.rstrip()): no_boost = True if os.path.isfile("/sys/devices/system/cpu/cpufreq" "/policy0/scaling_governor"): with open("/sys/devices/system/cpu/cpufreq" "/policy0/scaling_governor", "r") as perform_file_test: for line in perform_file_test: if re.match("performance", line.rstrip()): perf = True return perf, no_boost def get_default_dev(): # Ideally, this is the HDA device # certainly it should be internal and not USB # priority: # HDA non-hdmi # other internal (pci, ac97) # firewire # HDMI # none # if none, and usbdev unset or empty # 1st USB should be appended global log tmpname = 'none' internal = "none" pci = "none" usb = "none" fw = "none" HDMI = 'none' if log: print("Try to figure out a good default device") for adir in glob.glob("/proc/asound/card*/"): tmpname = 'none' if os.path.isfile(f"{adir}/id"): with open(f"{adir}/id", "r") as card_file: for line in card_file: tmpname = line.rstrip() else: tmpname = "no-id" if os.path.isfile(f"{adir}/codec#0"): if tmpname != "HDMI" and tmpname != "NVidia": internal = f"{tmpname},0,0" elif HDMI == 'none': HDMI = f"{tmpname},0,0" elif os.path.isfile(f"{adir}/usbbus"): if usb == 'none': usb = f"{tmpname},0,0" elif os.path.exists(f"{adir}/firewire"): if fw == 'none': fw = f"{tmpname},0,0" elif pci == 'none': pci = f"{tmpname},0,0" if internal != 'none': return internal, usb if pci != 'none': return pci, usb if fw != 'none': return fw, usb if HDMI != 'none': return HDMI, usb return 'none', usb def get_dev_name(testname): # convert from raw to devname global our_db if testname == 'none': return 'none' name, dev, sub = testname.split(',') for device in our_db['devices']: if our_db['devices'][device]['raw'] == name: return f"{device},{dev},{sub}" elif device == name: return testname return 'none' def make_db(): ''' read old config file. ''' global config global def_config global old_name global our_db global log if log: print("set some reasonable defaults") config = configparser.ConfigParser() def_config = config['DEFAULT'] usbdev = 'none' default_device, usbonly = get_default_dev() if default_device == 'none': usbdev = usbonly # first set defaults, This makes sure there is always something to convert config['DEFAULT'] = { 'JACK': "False", 'DRIVER': "alsa", 'CHAN-IN': "0", 'CHAN-OUT': "0", 'RATE': "48000", 'FRAME': "1024", 'PERIOD': "2", 'CONNECT-MODE': "n", 'ZFRAME': "512", 'XDEV': "", 'PULSE-IN': "pulse_in", 'PULSE-OUT': "pulse_out", 'PJ-IN-CON': 'system:capture_1', 'PJ-OUT-CON': 'monitor', 'PJ-IN-COUNT': '2', 'PJ-OUT-COUNT': '2', 'A2J': "True", 'DEV': default_device, 'USBAUTO': "True", 'USB-SINGLE': "False", 'USBDEV': usbdev, "PULSE": "True", "LOG-LEVEL": "15", "BLACKLIST": "", "PHONE-ACTION": "switch", "PHONE-DEVICE": default_device, "MONITOR": 'system:playback_1' } if log: print("read old style file if it exists") c_file = expanduser(old_name) if os.path.isfile(c_file): # config file exists, read it in config.read(c_file) # rename to *.bak so we don't use it again os.replace(c_file, f"{c_file}.bak") # fix some well known problems if def_config['MONITOR'] == "none": def_config['MONITOR'] = 'system:playback_1' if def_config['PJ-IN-CON'] == "0": def_config['PJ-IN-CON'] = "none" if def_config['PJ-OUT-CON'] == "0": def_config['PJ-OUT-CON'] = "none" if def_config['DEV'] == "default": def_config['DEV'] = default_device if not def_config['CONNECT-MODE'] in ['a', 'A', 'e', 'E']: def_config['CONNECT-MODE'] = "n" ''' Stuff config into this db: Version: text log-level: int (20) cpu_gov: True (true means performance) boost: bool(True) (true means no_boost) JACK: # things that require restart Used: bool (False) driver: string (alsa) chan_in: int (2) chan_out: int (2) rate: int (48000) frame: int (1024) nperiods: int (2) connect_mode: char ('n') dev: string (1st internal non-hdmi) USBdev: string (blank) cap-latency: 0, play-latency: 0 extra: # Stuff that can be changed without restart A2J: bool (True) A2J_U: bool (False) USBAUTO: bool (True) USBSingle: bool (False) Monitor: string (system:playback_1) Phone_action: string (none) Phone_device: string (default device) pulse: pulse_in: name: connection: "none", count: 2 pulse_out: name: { connection: "none", count: 2 Devices: {} ''' if log: print("make up to date config file") our_db = {'version': version()} our_db['log-level'] = int(def_config['log-level']) our_db['cpu-governor'], our_db['boost'] = get_gvnr_set() if def_config['usbdev'] == '': def_config['usbdev'] = 'none' jack_db = {'on': bool(def_config['jack'] in ['True']), 'driver': def_config['driver'], 'chan-in': int(def_config['chan-in']), 'chan-out': int(def_config['chan-out']), 'rate': int(def_config['rate']), 'frame': int(def_config['frame']), 'period': int(def_config['period']), 'connect-mode': def_config['connect-mode'], 'dev': def_config['dev'], 'usbdev': def_config['usbdev'], 'cap-latency': 0, 'play-latency': 0} extra_db = {'a2j': bool(def_config['a2j'] in ['True']), 'usbauto': bool( def_config['usbauto'] in ['True']), 'usb-single': bool( def_config['usb-single'] in ['True']), 'monitor': def_config['monitor'], 'phone-action': def_config['phone-action'], 'phone-device': def_config['phone-device'] } our_db['jack'] = jack_db our_db['extra'] = extra_db pulse_in_db = {} pulse_out_db = {} pulse_db = {'inputs': pulse_in_db, 'outputs': pulse_out_db} our_db['pulse'] = pulse_db for idx, bridge in enumerate(def_config['PULSE-IN'].split()): con = def_config['PJ-IN-CON'].split() if len(con) < (idx + 1): this_con = "none" else: this_con = def_config['PJ-IN-CON'].split()[idx] cnt = def_config['PJ-IN-COUNT'].split() if len(cnt) < (idx + 1): this_cnt = "2" else: this_cnt = def_config['PJ-IN-COUNT'].split()[idx] temp_db = {'connection': this_con, 'count': int(this_cnt)} pulse_in_db[bridge] = temp_db for idx, bridge in enumerate(def_config['PULSE-OUT'].split()): con = def_config['PJ-OUT-CON'].split() if len(con) < (idx + 1): this_con = "none" else: this_con = def_config['PJ-OUT-CON'].split()[idx] cnt = def_config['PJ-OUT-COUNT'].split() if len(cnt) < (idx + 1): this_cnt = "2" else: this_cnt = def_config['PJ-OUT-COUNT'].split()[idx] temp_db = {'connection': this_con, 'count': int(this_cnt)} pulse_out_db[bridge] = temp_db our_db['znet'] = {} our_db['mnet'] = {"type": "jack", "count": 0} # Now devices we just want to add devices in the config file # Since get_devices should start with the current db, create one # from what device the old config knows about devices_db = {} our_db['devices'] = devices_db devicelist = [] if log: print("adding devices to devicelist") if default_device != 'none': devicelist.append(default_device) if def_config['DEV'] != 'none' and def_config['DEV'] not in devicelist: devicelist.append(def_config['DEV']) if (def_config['USBDEV'] != 'none') and ( def_config['USBDEV'] not in devicelist): devicelist.append(def_config['USBDEV']) if def_config['PHONE-DEVICE'] != 'none' and def_config[ 'PHONE-DEVICE'] not in devicelist: devicelist.append(def_config['PHONE-DEVICE']) if log: print( f"adding devices from xdev: " "{ str(def_config['XDEV'].split())}" "and blacklist: " "{str(def_config['BLACKLIST'].split())}") devicelist = devicelist + \ def_config['XDEV'].split() + def_config['BLACKLIST'].split() for rdev in devicelist: rdev_l = rdev.split(',') if log: print(f"fleshing out device: {str(rdev_l)}") if len(rdev_l) == 3: dev, sub, ssub = rdev_l dev_db = make_dev_temp() if def_config[ 'USBDEV'] != 'none' and dev == def_config[ 'USBDEV'].split(',')[0]: if 'USB1' not in devices_db: devices_db['USB1'] = dev_db dev_db['usb'] = True un, ud, us = def_config['USBDEV'].split(',') jack_db['usbdev'] = f"USB1,{ud},{us}" else: dev_db = devices_db['USB1'] else: if dev not in devices_db: dev_db = make_dev_temp() devices_db[dev] = dev_db else: dev_db = devices_db[dev] dev_db['raw'] = dev if sub not in dev_db['sub']: dev_db['sub'][sub] = make_sub_temp() sub_db = dev_db['sub'][sub] sub_db['name'] = rdev if rdev in def_config['XDEV'].split(): sub_db['play-chan'] = 100 sub_db['cap-chan'] = 100 if rdev in def_config['BLACKLIST'].split(): sub_db['hide'] = True return def make_dev_temp(): ''' make a biolerplate device with no sub ''' dev_temp = {'number': -1, 'usb': False, "internal": False, 'hdmi': False, 'firewire': False, 'rates': ['32000', '44100', '48000', '88200', '96000', '192000'], 'min_latency': 16, 'id': 'none', 'bus': 'none', 'sub': {}} return dev_temp def make_sub_temp(): ''' make a template for a sub device ''' global def_config sub_temp = {'name': 'none', 'playback': True, 'capture': True, 'rate': int(def_config['RATE']), 'frame': int(def_config['FRAME']), 'nperiods': int(def_config['PERIOD']), 'hide': False, 'cap-latency': 0, 'play-latency': 0} return sub_temp def write_new(): ''' write new config file ''' if log: print("write new config file") global our_db global new_name global config_path global temp_name # print(json.dumps(our_db, indent = 4)) c_file = expanduser(new_name) ck_file = expanduser(temp_name) count = 0 while count < 10: if not os.path.isfile(ck_file): break # someone is writing, wait if log: print("File is being written don't write yet") time.sleep(.5) count += 1 c_dir = expanduser(config_path) if not os.path.isdir(c_dir): os.makedirs(c_dir) with open(ck_file, 'w') as json_file: if json_file.writable(): fcntl.lockf(json_file, fcntl.LOCK_EX) json.dump(our_db, json_file, indent=4) json_file.write("\n") json_file.flush() os.fsync(json_file.fileno()) # Release the lock on the file. if json_file.writable(): fcntl.lockf(json_file, fcntl.LOCK_UN) if os.path.exists(c_file): os.remove(c_file) os.rename(ck_file, c_file) def usb_duplicate(device): # find out if this is a duplicate global log global our_db ret = 'none' check_db = our_db['devices'][device] for good_dev in our_db['devices']: if device == good_dev: # this is always a duplicate of itself continue if len(good_dev) > 3 and good_dev[ 0:3] == 'USB' and good_dev[3].isdigit(): good_db = our_db['devices'][good_dev] if check_db['id'] == 'none': ret = good_dev if check_db['id'] == good_db['id']: # same kind of device if good_db['bus'] == check_db['bus']: ret = good_dev if log: print(f"Dup check returns: {ret}") return ret def usb_number(): # get a free usb number global our_db numbers = [] for dev in our_db['devices']: if len(dev) > 3 and dev[0:3] == 'USB' and dev[3].isdigit(): # this is usb numbers.append(int(dev[3:])) for n in range(1, 20): if n not in numbers: return n def check_devices(our_db): ''' take an exsiting db['devices'] db and make sure it agrees with the actual devices in the machine. If a listing in the db does not have a real card right now, card is set to -1 (unplugged) otherwsie card is set to current card number (so we don't duplicate it) if there are devices not in the list they are added according to settings''' global log dev_list = list(our_db['devices']) for device in dev_list: # do sanity check dev_db = our_db['devices'][device] dev_db['number'] = -1 if 'raw' not in dev_db: dev_db['raw'] = device if 'id' not in dev_db: dev_db['id'] = 'none' if 'bus' not in dev_db: dev_db['bus'] = 'none' if 'hdmi' not in dev_db: dev_db['hdmi'] = False if 'internal' not in dev_db: dev_db['internal'] = False if 'firewire' not in dev_db: dev_db['firewire'] = False if 'usb' not in dev_db: dev_db['usb'] = False if 'rates' not in dev_db: dev_db['rates'] = ["32000", "44100", "48000", "88200", "96000", "192000"] if 'min_latency' not in dev_db: dev_db['min_latency'] = 16 if dev_db['hdmi']: dev_db['min_latency'] = 4096 elif dev_db['internal']: dev_db['min_latency'] = 128 elif dev_db['firewire']: dev_db['min_latency'] = 256 elif dev_db['usb']: dev_db['min_latency'] = 32 if len(device) < 4 or not ( device[0:3] == 'USB' and device[3].isdigit()): # check for duplicate # dup means id and bus match if usb_duplicate(device) != 'none': our_db['devices'].pop(device) continue else: new_dev = f"USB{str(usb_number())}" our_db['devices'][new_dev] = our_db['devices'].pop(device) dev_db = our_db['devices'][new_dev] if our_db['jack']['usbdev'] != 'none': usb_split = our_db['jack']['usbdev'].split(',') if len(usb_split) == 3: un, ud, us = usb_split if un == device: our_db['jack']['usbdev'] = f"{new_dev},{ud},{us}" dup = usb_duplicate(device) if dup != 'none': print(f"dupcheck for {device} returns {dup}") our_db['devices'].pop(device) continue for sub in dev_db['sub']: sub_db = dev_db['sub'][sub] if 'playback' not in sub_db: sub_db['playback'] = False else: sub_db['playback'] = bool( str(sub_db['playback']) in ['True', 'true']) if 'capture' not in sub_db: sub_db['capture'] = False else: sub_db['capture'] = bool( str(sub_db['capture']) in ['True', 'true']) if 'play-chan' not in sub_db: if dev_db['usb'] and (not our_db['extra']['usb-single']): sub_db['play-chan'] = 100 elif device == get_default_dev()[0].split(',')[0]: sub_db['play-chan'] = 100 elif device == our_db['jack']['dev'].split(',')[0]: sub_db['play-chan'] = 100 else: sub_db['play-chan'] = 0 else: sub_db['play-chan'] = int(sub_db['play-chan']) if 'cap-chan' not in sub_db: if dev_db['usb']: sub_db['cap-chan'] = 100 elif device == get_default_dev()[0].split(',')[0]: sub_db['cap-chan'] = 100 elif device == our_db['jack']['dev'].split(',')[0]: sub_db['cap-chan'] = 100 else: sub_db['cap-chan'] = 0 else: sub_db['cap-chan'] = int(sub_db['cap-chan']) if 'rate' not in sub_db: sub_db['rate'] = 48000 else: sub_db['rate'] = int(sub_db['rate']) if 'frame' not in sub_db: sub_db['frame'] = 1024 else: sub_db['frame'] = int(sub_db['frame']) if 'nperiods' not in sub_db: sub_db['nperiods'] = 2 else: sub_db['nperiods'] = int(sub_db['nperiods']) if 'hide' not in sub_db: sub_db['hide'] = False else: sub_db['hide'] = bool(str(sub_db['hide']) in ['True', 'true']) if 'name' not in sub_db: sub_db['name'] = f"{dev_db['raw']},{sub},0" if 'cap-latency' not in sub_db: sub_db['cap-latency'] = 0 if 'play-latency' not in sub_db: sub_db['play-latency'] = 0 sub_db['play-pid'] = 0 sub_db['cap-pid'] = 0 if "description" not in sub_db: sub_db['description'] = "unknown" # now find the real number ndevs = -1 if os.path.exists("/proc/asound/cards"): with open("/proc/asound/cards", "r") as cards_file: for line in cards_file: # last one is highest dev number sub = line.rstrip()[1:] sub2 = sub.split(" ") first_el = line.rstrip()[1:].split(" ")[0] if first_el.isdigit(): ndevs = int(first_el) else: return our_db if ndevs == -1: return our_db ndevs += 1 for x in range(0, ndevs): # card loop rates = ['32000', '44100', '48000', '88200', '96000', '192000'] drates = [] cname = "" usb = False bus = 'none' d_id = 'none' cdescription = 'unknown' # first get device raw name if os.path.exists(f"/proc/asound/card{str(x)}"): if os.path.isfile(f"/proc/asound/card{str(x)}/id"): with open(f"/proc/asound/card{str(x)}/id", "r") as card_file: for line in card_file: # only need one line cname = line.rstrip() else: cname = str(x) else: # not all numbers may be used continue # now check for USB device remembering to go by id/bus not raw name if os.path.exists(f"/proc/asound/card{str(x)}/usbid"): usb = True with open(f"/proc/asound/card{str(x)}/usbid", "r") as id_file: for line in id_file: d_id = line.strip() if os.path.isfile( f"/proc/asound/card{str(x)}/usbbus"): with open( f"/proc/asound/card{str(x)}/usbbus", "r") as bus_file: for line in bus_file: oldbus = line.strip().split('/')[0] if os.path.isfile(f"/proc/asound/card{str(x)}/stream0"): line0 = True with open( f"/proc/asound/card{str(x)}/stream0", "r") as desc_file: for line in desc_file: if line0: prebus = line.strip().split(' at ')[1] bus = line.strip().split(' at ')[1].split(', ')[0] cdescription = line.strip().split(' at ')[0] line0 = False if 'Rates:' in line: fnd_rates = line.split()[1:] for rate in fnd_rates: rate = rate.split(',')[0] if rate not in drates: drates.append(rate) # find this device in db or add to db found_name = "" for dev in our_db['devices']: dev_db = our_db['devices'][dev] if usb and [dev_db['id'], dev_db['bus']] == [d_id, bus]: # this is the device... dev_db['usb'] = True found_name = dev break elif usb and [dev_db['id'], dev_db['bus']] == [d_id, oldbus]: # this is an old style device... well maybe # check if it already has a number dev_db['usb'] = True found_name = dev break elif usb and [dev_db['id'], dev_db['bus']] == ['none', 'none']: # really old style if dev_db['raw'] == cname: found_name = dev dev_db['usb'] = True break elif dev == cname: dev_db['raw'] = cname dev_db['usb'] = False dev_db['number'] = x found_name = dev break if found_name == "": if log: print("device not found") if usb: found_name = f"USB{usb_number()}" our_db['devices'][found_name] = { 'number': x, 'usb': True, "internal": False, 'hdmi': False, 'firewire': False, 'min_latency': 32, 'rates': drates, 'raw': cname, 'id': d_id, 'bus': bus, 'sub': {}} else: found_name = cname our_db['devices'][cname] = { 'number': x, 'usb': False, "internal": False, 'hdmi': False, 'firewire': False, 'min_latency': 16, 'rates': ['32000', '44100', '48000', '88200', '96000', '192000'], 'raw': cname, 'id': 'none', 'bus': 'none', 'sub': {}} # print(f"{cname}") ''' get device info from system files ''' dev_db['internal'] = False dev_db['firewire'] = False dev_db['min_latency'] = 16 dev_db['hdmi'] = bool(dev_db['raw'] in ['HDMI', 'NVidia']) if 'id' not in dev_db: dev_db['id'] = 'none' if 'bus' not in dev_db: dev_db['bus'] = 'none' sub = 0 if dev_db['usb']: dev_db['raw'] = cname dev_db['number'] = x dev_db['id'] = d_id dev_db['bus'] = bus dev_db['min_latency'] = 32 elif os.path.exists( f"/proc/asound/card{str(x)}/codec#0"): dev_db['internal'] = True dev_db['min_latency'] = 128 # can get supported rates from file above with open( f"/proc/asound/card{str(x)}/codec#0", "r") as card_file: node = False for line in card_file: if 'Node' in line: node = True if 'Digital' in line and not dev_db['hdmi']: node = False if node and 'rates' in line: fnd_rates = line.split()[2:] for rate in fnd_rates: if rate not in drates: drates.append(rate) if len(drates): rates = drates elif os.path.exists(f"/proc/asound/card{str(x)}/firewire"): dev_db['firewire'] = True dev_db['min_latency'] = 256 if dev_db['hdmi']: dev_db['min_latency'] = 4096 dev_db['rates'] = rates for y in range(0, 20): cap = False play = False cap_pid = 0 play_pid = 0 dname = "" if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}p"): play = True if os.path.exists( f"/proc/asound/card{str(x)}/pcm{str(y)}p/sub0"): with open( f"/proc/asound/card{str(x)}/" f"pcm{str(y)}p/sub0/status", "r") as info_file: for line in info_file: if re.match("^owner_pid", line.rstrip()): play_pid = int(line.rstrip().split(": ", 1)[1]) with open(f"/proc/asound/card{str(x)}/" f"pcm{str(y)}p/sub0/info", "r") as info_file: for line in info_file: clean_line = line.rstrip() if re.match("^name:", clean_line): line_list = clean_line.split(": ", 1) if len(line_list) > 1: dname = line_list[1] if os.path.exists(f"/proc/asound/card{str(x)}/pcm{str(y)}c"): cap = True if os.path.exists(f"/proc/asound/card{str(x)}" f"/pcm{str(y)}c/sub0"): with open(f"/proc/asound/card{str(x)}" f"/pcm{str(y)}c/sub0/status", "r") as info_file: for line in info_file: if re.match("^owner_pid", line.rstrip()): cap_pid = int(line.rstrip().split(": ", 1)[1]) with open(f"/proc/asound/card{str(x)}" f"/pcm{str(y)}c/sub0/info", "r") as info_file: for line in info_file: clean_line = line.rstrip() if re.match("^name:", clean_line): line_list = clean_line.split(": ", 1) if len(line_list) > 1: dname = line_list[1] # print(f"cap: {str(cap)} play:{str(play)}") if cap or play: if str(y) not in dev_db['sub']: dev_db['sub'][str(y)] = {'playback': play, 'capture': cap} sub_db = dev_db['sub'][str(y)] sub_db['play-pid'] = play_pid sub_db['cap-pid'] = cap_pid if 'playback' not in sub_db: sub_db['playback'] = play if 'capture' not in sub_db: sub_db['capture'] = cap if "play-chan" not in sub_db: if dev_db['usb'] and our_db['extra']['usbauto']: sub_db['play-chan'] = 100 else: sub_db['play-chan'] = 0 if 'cap-chan' not in sub_db: if dev_db['usb'] and our_db['extra']['usbauto']: sub_db['cap-chan'] = 100 else: sub_db['cap-chan'] = 0 if "rate" not in sub_db: jackrate = str(our_db['jack']['rate']) if jackrate not in dev_db['rates']: sub_db['rate'] = 48000 if ('48000' not in rates) and len(rates): sub_db['rate'] = int(rates[0]) else: sub_db['rate'] = our_db['jack']['rate'] if 'frame' not in sub_db: sub_db['frame'] = 1024 if 1024 < dev_db['min_latency']: sub_db['frame'] = dev_db['min_latency'] if 'nperiods' not in sub_db: sub_db['nperiods'] = 2 if 'hide' not in sub_db: sub_db['hide'] = False if 'name' not in sub_db or sub_db['name'] == 'none': sub_db['name'] = f"{found_name},{str(y)},0" if "cap-latency" not in sub_db: sub_db['cap-latency'] = 0 if "play-latency" not in sub_db: sub_db['play-latency'] = 0 if dev_db['usb']: sub_db['description'] = cdescription else: sub_db['description'] = dname return our_db def check_new(no_check=False): global log global new_name global our_db global temp_name if log: print(f"checking config file for compatability with version {version()}") c_file = expanduser(new_name) ck_file = expanduser(temp_name) count = 0 while count < 10: if not os.path.isfile(ck_file): break # someone is writing, wait if log: print("File is being written don't read yet") time.sleep(.5) count += 1 time.sleep(.5) if os.path.isfile(c_file) and os.path.getsize(c_file) > 100: # config file exists, read it in with open(c_file) as f: our_db = json.load(f) else: print(f"Error, {c_file} not found. Make one.") make_db() check_db() return if our_db['version'] != version(): # see if saved file with our version exists if os.path.isfile(f"{c_file}.{version()}"): print( "A config file for this version exists: " f"{c_file}.{version()} Using it") shutil.copyfile(f"{c_file}.{version()}", c_file) return if log: print(f"config file is version: {our_db['version']} updating") print(f"saving old config file to: {c_file}.{our_db['version']}") shutil.copyfile(c_file, f"{c_file}.{our_db['version']}") if no_check: return check_db() def check_db(): global our_db # check all parameters for sanity if log: print("checking data base") our_db['version'] = version() if 'log-level' not in our_db: our_db['log-level'] = 15 temp_gov, temp_boost = get_gvnr_set() if 'cpu-governor' not in our_db: our_db['cpu-governor'] = temp_gov if 'boost' not in our_db: our_db['boost'] = temp_boost if 'jack' not in our_db: our_db['jack'] = {} if 'extra' not in our_db: our_db['extra'] = {} if 'pulse' not in our_db: our_db['pulse'] = {} if 'devices' not in our_db: our_db['devices'] = {} if 'znet' not in our_db: our_db['znet'] = {} if 'mnet' not in our_db: our_db['mnet'] = {'type': 'jack', 'count': 0} extra_db = our_db['extra'] # check_devices should be universal. our_db = check_devices(our_db) if log: print(" Devices checked, continue data base check") jack_db = our_db['jack'] if 'on' not in jack_db: jack_db['on'] = False else: jack_db['on'] = bool(str(jack_db['on']) in ['True', 'true']) if 'driver' not in jack_db: jack_db['driver'] = "alsa" if 'chan-in' not in jack_db: jack_db['chan-in'] = 1 else: jack_db['chan-in'] = int(jack_db['chan-in']) if 'chan-out' not in jack_db: jack_db['chan-out'] = 1 else: jack_db['chan-out'] = int(jack_db['chan-out']) if 'rate' not in jack_db: jack_db['rate'] = 48000 else: jack_db['rate'] = int(jack_db['rate']) if 'frame' not in jack_db or not str(jack_db['frame']).isnumeric(): jack_db['frame'] = 1024 else: jack_db['frame'] = int(jack_db['frame']) if 'period' not in jack_db: jack_db['period'] = 2 else: jack_db['period'] = int(jack_db['period']) if 'connect-mode' not in jack_db: jack_db['connect-mode'] = 'n' else: if jack_db['connect-mode'] not in ['a', 'A', 'o', 'O']: jack_db['connect-mode'] = "n" if 'dev' not in jack_db: jack_db['dev'] = get_default_dev()[0] if ('usbdev' not in jack_db) or ( jack_db['usbdev'] == "") or jack_db['usbdev'] == 'none': if jack_db['dev'] == 'none': jack_db['usbdev'] = get_default_dev()[1] else: jack_db['usbdev'] = 'none' else: jack_db['usbdev'] = get_dev_name(jack_db['usbdev']) if 'cap-latency' not in jack_db: jack_db['cap-latency'] = 0 else: jack_db['cap-latency'] = int(jack_db['cap-latency']) if 'play-latency' not in jack_db: jack_db['play-latency'] = 0 else: jack_db['play-latency'] = int(jack_db['play-latency']) if 'a2j' not in extra_db: extra_db['a2j'] = True else: extra_db['a2j'] = bool(str(extra_db['a2j']) in ['True', 'true']) if 'a2j_u' not in extra_db: extra_db['a2j_u'] = False else: extra_db['a2j_u'] = bool(str(extra_db['a2j_u']) in ['True', 'true']) if 'usbauto' not in extra_db: extra_db['usbauto'] = True else: extra_db['usbauto'] = bool( str(extra_db['usbauto']) in ['True', 'true']) if 'usb-single' not in extra_db: extra_db['usb-single'] = False else: extra_db['usb-single'] = bool(str(extra_db['usb-single']) in ['True', 'true']) if 'monitor' not in extra_db: extra_db['monitor'] = "system:playback_1" if 'phone-action' not in extra_db: extra_db['phone-action'] = "switch" if 'phone-device' not in extra_db: # we could use system but default is most likely to # have ph jack we can detect extra_db['phone-device'] = get_default_dev()[0] if 'phone-left' not in extra_db: extra_db['phone-left'] = '1' pulse_db = our_db['pulse'] if 'inputs' not in pulse_db: pulse_db['inputs'] = {} else: for bridge in pulse_db['inputs']: if "connection" not in pulse_db['inputs'][bridge]: pulse_db['inputs'][bridge]['connection'] = 'none' elif pulse_db['inputs'][bridge]['connection'].isdigit(): pulse_db['inputs'][bridge][ 'connection'] = "system:capture_" f"{pulse_db['inputs'][bridge]['connection']}" if 'count' not in pulse_db['inputs'][bridge]: pulse_db['inputs'][bridge]['count'] = 2 else: pulse_db['inputs'][bridge]['count'] = int( pulse_db['inputs'][bridge]['count']) if 'outputs' not in pulse_db: pulse_db['outputs'] = {} else: for bridge in pulse_db['outputs']: if "connection" not in pulse_db['outputs'][bridge]: pulse_db['outputs'][bridge]['connection'] = 'none' elif pulse_db['outputs'][bridge]['connection'].isdigit(): pulse_db['outputs'][bridge][ 'connection'] = "system:playback_" f"{pulse_db['outputs'][bridge]['connection']}" if 'count' not in pulse_db['outputs'][bridge]: pulse_db['outputs'][bridge]['count'] = 2 else: pulse_db['outputs'][bridge]['count'] = int( pulse_db['outputs'][bridge]['count']) write_new() def convert(quiet=True): global config_path global install_path global log global new_name global old_name global our_db log = bool(not quiet) c_file = expanduser(new_name) if log: print(f"Search for: {c_file}") if os.path.isfile(c_file): if log: print("config file found, check it") check_new() else: if log: print("Config file not found, Create one") make_db() check_db() return our_db studio-controls-2.3.9/usr/lib/systemd/000077500000000000000000000000001432334377300177555ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/systemd/user/000077500000000000000000000000001432334377300207335ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/systemd/user/indicator-messages.service.wants/000077500000000000000000000000001432334377300273065ustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/systemd/user/indicator-messages.service.wants/session-monitor.service000077700000000000000000000000001432334377300410002../session-monitor.serviceustar00rootroot00000000000000studio-controls-2.3.9/usr/lib/systemd/user/session-monitor.service000066400000000000000000000007431432334377300254710ustar00rootroot00000000000000[Unit] Description=Studio - session monitor # Stops autojack which starts Studio audio in the same way the session # ended as well as providing a back end for studio-controls for # changing various settings. BindsTo=indicator-messages.service After=indicator-messages.service [Service] Type=simple ExecStart=/bin/sleep infinity TimeoutStopSec=4 ExecStop=/bin/sleep 5 ExecStopPost=/bin/systemctl --user start systemd-exit.service [Install] WantedBy=indicator-messages.service studio-controls-2.3.9/usr/sbin/000077500000000000000000000000001432334377300164525ustar00rootroot00000000000000studio-controls-2.3.9/usr/sbin/studio-system000077500000000000000000000052071432334377300212350ustar00rootroot00000000000000#! /bin/sh # # Script to change do system side things for studio-controls # # GOVERNOR="ondemand" NO_TURBO="0" set_governors() { # do this here in case we hit exit 0 before the end echo "NO_TURBO=$NO_TURBO\n" > /etc/default/studio echo "GOVERNOR=${GOVERNOR}\n" >> /etc/default/studio AVAILABLE="/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors" [ -f $AVAILABLE ] || exit 0 read governors < $AVAILABLE case "$GOVERNOR" in performance) case $governors in *performance*) GOVERNOR="performance" ;; *) exit 0 ;; esac ;; *) case $governors in *ondemand*) GOVERNOR="ondemand" ;; *powersave*) GOVERNOR="powersave" ;; *) exit 0 ;; esac ;; esac for CPUFREQ in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor do [ -f $CPUFREQ ] || continue echo -n $GOVERNOR > $CPUFREQ done #reswrite just in case we corrected something echo "NO_TURBO=$NO_TURBO\n" > /etc/default/studio echo "GOVERNOR=${GOVERNOR}\n" >> /etc/default/studio } # any setting in this file will override the above defaults. # studio-controls will normally write the defaults file if [ -f /etc/default/studio ] ; then . /etc/default/studio fi case "$1" in performance) GOVERNOR="performance" set_governors ;; ondemand) GOVERNOR="ondemand" set_governors ;; powersave) GOVERNOR="powersave" set_governors ;; fix) if [ -f /etc/security/limits.d/audio.conf.disabled ]; then mv /etc/security/limits.d/audio.conf.disabled /etc/security/limits.d/audio.conf elif ! [ -f /etc/security/limits.d/audio.conf ]; then cp /usr/share/studio-controls/audio.conf /etc/security/limits.d/audio.conf fi if [ ${PKEXEC_UID} -gt 999 ]; then /usr/sbin/usermod `id -nu ${PKEXEC_UID}` -aG audio fi ;; boost) if [ -f /sys/devices/system/cpu/intel_pstate/no_turbo ]; then echo "NO_TURBO=0\n" > /etc/default/studio echo "GOVERNOR=${GOVERNOR}" >> /etc/default/studio echo "0" | tee /sys/devices/system/cpu/intel_pstate/no_turbo fi ;; noboost) if [ -f /sys/devices/system/cpu/intel_pstate/no_turbo ]; then echo "NO_TURBO=1\n" > /etc/default/studio echo "GOVERNOR=${GOVERNOR}" >> /etc/default/studio echo "1" | tee /sys/devices/system/cpu/intel_pstate/no_turbo fi ;; nosys) /etc/init.d/cron stop ;; sys) /etc/init.d/cron start ;; ffado) if ! [ -f /etc/modprobe.d/blacklist-studio.conf ]; then cp /usr/share/studio-controls/blacklist-studio.conf /etc/modprobe.d/blacklist-studio.conf fi ;; alsa) if [ -f /etc/modprobe.d/blacklist-studio.conf ]; then rm /etc/modprobe.d/blacklist-studio.conf fi ;; *) exit 0 ;; esac studio-controls-2.3.9/usr/share/000077500000000000000000000000001432334377300166215ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/applications/000077500000000000000000000000001432334377300213075ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/applications/com.github.ovenwerks.studio-controls.desktop000066400000000000000000000004201432334377300321260ustar00rootroot00000000000000[Desktop Entry] Name=Studio Controls GenericName=Multimedia Controls Comment=Setup Audio, Video and Graphics Exec=studio-controls Icon=com.github.ovenwerks.studio-controls Terminal=false Type=Application Categories=AudioVideoEditing;Audio; Keywords=Audio;Utility;Settings studio-controls-2.3.9/usr/share/icons/000077500000000000000000000000001432334377300177345ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/000077500000000000000000000000001432334377300213735ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/128x128/000077500000000000000000000000001432334377300223305ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/128x128/apps/000077500000000000000000000000001432334377300232735ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/128x128/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000202731432334377300332350ustar00rootroot00000000000000PNG  IHDR>a pHYsMCStEXtSoftwarewww.inkscape.org< IDATxyxU?;}@ŽQY]QQGEqDgdyuwF}AQADWEEEdB'tgV &ݕ<{=s1A[ @@`0`0hӦԗЮ ]P6^7dA/ЄMa73 ++ U2+Yp̀Eil6($Q|tyT?zO   -F )]v}>cWѤCE +cNGDMNɉgegtH*j*ZU#ABmbhFIl;{st9RX)H{d޲[vgOY=2c꼎qQF,#ua*?HuUv߲jO|>o$Fϸyɗ_8 ll]DӮ~Sx_:̈́`4>i> A: |xSP D# |m*UQ-_ZG fm]8rL1x۳û_7w=v^724PntE&싱/6{'gYx{tϏH>X7IJ}/:OW-ňۘ  ? H2[UOѭ^Q7|0cyf3fvƛٙ11x/!*HfR/HqР>\,E_zG\݊Ojq9w^IݥӮu/sd:V^_?TQUwݏ=SP=FI72oG> [Y־:2BFH e+/,䝩ZXu  T E'DI> ;죅Vm9\KúQP3~Tv9Ȃ`1՛e?yKk=ZI|9FP Nc;U^G{sqyA[MLߕO[W22T䘾H ?ex$O&WwDG%B{ے!P@KXu`1QLY~«WX[A5:;_]XZ ֨W-ĉ+&EաC _ BF [egEM VO[}ż/67 `>_ܞԎX3Myy_P@l}]5ӷ0PwE[PC miZD9x{v W…sJa T/-b/FQmrW#$PlǯGB*aU(GڴW#Fdٴ`Z܊ *71?k;|_@ K*KvFѼ`@ `EǃAx0&rJM&:j Bm]鈫'_opYLFߴq| QF GO4< z jBEen~ԅsv!6=BČ0%پkk̃sC/9ʧ]!{~ @CƦVƢR ?tobfw*}ү]ෘAOU-7[49jPr"vP~(9Ϯ7!Yfc}TsҲ/##BE:nӐg7%>6A'j_V_>fgUG쀩Q6PxҒJh9ڢ>K1v۞zdHVDe00{k M3-PSbuL4w_\a>Pv!WPh:a%T&E^hjwyI#+;<ǣ"vPtX2=%1?X떟[a3wdT4ݞ_s{#H)^Im8 I5=:kev<ХGk^|QڮJi g(@ `fPk/¥Zu[50)}: [#b+;"A~L݇8֎8֞ ]1(FE 'BئKgH#>E uo psߌzy<=%ة&u5gSn+ 6e0! *o_bPѷ[޵[wv5|oef88A}ѹ]_iOΛn~2wN?Œq Q@m{z?ur:"\# G"@ S2lZUߏ3i,7=玃m+%`?PPIkw?-/b2N ח{5S> UGc#y!>6*q`TEg!do t:w?-*M9m;O=t+}_(Gy!6wCh˸rѴ=uu)f9.[g ±t# P:jpj4 aUXއ Z䗝SՈ]"đ:r+OHaIlj}hٚ#vA\]zCJ^w/`m#\ef=qg0]Y'Q 5,h@keU޾w~B'QO$W@eW\mVsqk:lxz{%cw7\_Xrr@lKN~oZ<qoO-Átsg!'Y.dW:`mMJ~U>0'=ӡAm@^WA<99\ZN^˪=`04/.+sZU[1}[kE"^<<ҪRKC˶=Dyj{ _1b~5̽-{f $I(~S}'>i׭萤9>}»{!х0,hŏ];jio_%4ah" Jq? $;ɰd6 Eߗv:|Ƿ4[J+'|.~qXP 2ɔ?{uoM3-7[b_  g'#%š@릹po ,/ #M1,V3,b/.{ꯆ˴e^,- T7.u_i}|~x}tŰ.޳\왫|?J ;BN|픮X :Wl=}շWrכwBL91HvG8 -P>{~#>+.Np9q\UGN_`0G&t%"lCpWp_N >kʒH!aA؍Uė[T5'XiET>oEg+CUQ2'bC'l+30>߇O1_À1jOB˰/d2e?u׍o"jD{ îTSAb%'3}ҋ61a'<8(99hC#痞FLUxj 6x{i%w=dVA;aꩄP5eA4uٚMTVf@ R9V(98kny9$ؾP 8%‰$D;"#ȼn>ܮ)qwuVEY97|gOza}GĂ!]FT2xw wW`Ǡih'i @nai}a-ȬSΖ>@zOQ5?[! *?A-RD1WX}kZ 7<4>wumI0"nxtpG>^?B! C"BC`B%e~A zˮG='s\A7!YA9{, !f C %j;K=yCslijq),IBls8N}Sجw!ױuvۻ[P`aiE|Qye4''*8t1;%ǟW>[n×|{:yLH. Av#K<4b`ߠPOϏۖ $>ZXW_#RD9,}2Sݨ9ǟY&=N;!dpѢ҈ESca[C/P3At6$^ܭ|y'i*b;+Hc@}sNضPw- =?{!@DN8!vHPl|ax\34;NUj3 Sju70@k+sJ DEJeA3]r5dZt-nOYZSƲĸ L2)Y G̈́?jLD$-% K*H[YN{ԟ}z/S2{ t6О mD{5= \LlNOLM*CL!L. }IàE6kʧFA`T>/#OiZ&hIw+E;E)e; $G]ՙ?;";i' 7@T$Qu cA1HMQ:--n3S\ie6Mm4L槙-0c>|Oa<8#JTs=!&/ }1R*~; 5'/j q1Sǎ، @5aM -d>n)%bpoĀM&S1i }M w\-Q+Z,޻'^_&Lɤ X'>3`W'P%' k#+c B&*OOz"_^)'/}&|Cݿjr|w pOC0u$͕ؗ \ۣsAĹj=CWP9w̜ӈPq@0~6mF"toؒǦ] R^n(٥ˆ@B62n"A=~ T̺EQ#(DiN(Y~ۺ';K$o8#L) "‹@/|s&>7`="ǁPԾH)u7:Mٵgf0Cm9 54qFǤvBDIoS{w)>?=/Gwf3ƀ)<3esb~8>Z}zdlAd7)AT4گȼc>׋cчX7l#d4Vp1ӬlVsw?~YUS@?ab,oǩ¾Zs*|ï]l6f87|ͥAd*C1t!|ÜfvlfxmgxtÛ fmLJ0?e[vj:q*X-Cw#?rсT]tgm<[~V1f'Ꮛ& 3a,Py|!>yჿC|d #fES:LOycִt1痢?,!A_<ws[Qye\x{&wk7!Ĩ@`NhAxe,j 2:$?z_"ci)JQRw#_nTUt)p qN{ܰ_ξs5ۯ@D"x5!-";o|f|NAqsm.+/摩׮:|9ʯAڥ/CW2x 7\婯JsrBȁӕ_^9b7tCKtd4 yF8Z6~ܗ7'?%`@zszfwW\0`™#'O&F%42p22D"Jw;_?wǁ+Prk*mO.zgC&obeUf/CPCe$QB6@A%h&)()knJnQi|^qylkY->?r3:$wRҧK2VpKe"U>NLB[>! AΧ ry^š?XnnjwΊzr` 7_69 lV#'V t#==j*XXl+NPo%2?CHӾ$ٝ'K?zߕ'GA2P%9%K?y7XFH W5Z}jgrʑE+$HFIUG;=M:/3S )Zas6+"!\cy EvJ] 'C$M~D#lye :, lr6Lʁf+-S—S611n/[CI<OŒ?#4H%K,o~zp~o5WlwbJ rLXB9ҝCo7FIENDB`studio-controls-2.3.9/usr/share/icons/hicolor/20x20/000077500000000000000000000000001432334377300221465ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/20x20/apps/000077500000000000000000000000001432334377300231115ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/20x20/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000016411432334377300330510ustar00rootroot00000000000000PNG  IHDR pHYs tEXtSoftwarewww.inkscape.org<.IDAT8mHTYܚqtB`m# ۍRw2%R#YP Mm# ٍ\{bRKN%5f.ۋT46^ufq4͔·{8s $%.gҥƜ1 昮l@bX͉02mG+>T^a?mvX5J;8GHe0 _?rN!BE9r;k~yBʍM9n&/^摜J2dHfG} ӒC-$9$kwV>ɷ2etQr ?mo~BOTի'@02;tW뱤cQMddHIQ rf"\BڂnI| ^KN rYP^Ӎ{Y!ЃdšZrKtq3iJ! fXוf54XS/eh]Y OA{t>cB6T:2nAoU9 i~֚V{,dѾc6f5Qvlٿ'Bك1րs[wgoIbƀ{i})H[M{dYVBK:;E[k2śc[7Jii4N ž g}IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/22x22/000077500000000000000000000000001432334377300221525ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/22x22/apps/000077500000000000000000000000001432334377300231155ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/22x22/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000020011432334377300330440ustar00rootroot00000000000000PNG  IHDRĴl; pHYs<tEXtSoftwarewww.inkscape.org<IDAT8]lUƟӵkWZK9 L #na1 :U.~&}818.$f. RvҵU$=GcK߇ |:!Wc?xwViw3T- u7͙4|N7B9F/\̫ Ҳ<(n!g2 ,h29@*mߝlu̵~:x GZKv8ӫ>! lyx?{k§XYr{CScB7`P䐻5*IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/24x24/000077500000000000000000000000001432334377300221565ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/24x24/apps/000077500000000000000000000000001432334377300231215ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/24x24/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000021711432334377300330600ustar00rootroot00000000000000PNG  IHDRw= pHYsӊtEXtSoftwarewww.inkscape.org<IDATHU}Le<;@8^A\P0b ]9` ԒZìfFrFf5t%;6VH,CwyK#9X[y|a$CtkL)س I !dOw>LeAVSwqOHL zĐԲcsAg=/Ϳ^{O E!D{YBEBܱ#w384jd. !m&;>n64zTi!AE"f<4 wު-d ,%)ג5$cH.?oClQ+wYvKl$o$7{?v:Tj@̻OB7ȸj; `uw_]# \I T&5bee`BRWy$T_5AࡄV(qoQܮyᙾdsP<I3;.B9~x^p%l^1׾ZQoBR ayk/IP~:`h 8:^S.}+a:%fs*,3vL>HfD>ܣ#uFj *Y^I3I_)G"]=~R\tKJBLoTXuk&JV .U{::9׳W&]6ܑ(IH>߸nBIŁ |,{$U1Uќ.Lc0g 3d/c|VW{e%{T0QoҋlJ]1&7jqZgvF\< 9%{ q Tǩ^kd&AWRqYw8}T* F Y\%y~6_?z_CL@#.j5ukbO|B#=鑑B @#蔗HZ^5N]J9k~oȠA@BsZ_WGxX kG`"~9cȠ QII541D2N z$^#AE0"?0AP/%b_=j%+O}Z WQM+#@ ABi$K tz_b@L>j~Z=PyD?'XCD bU{ZHiA멋@=Baؾ?7m_SE'J+jRiw z[PqFi6 fj6SmԒ6MzthyU M=J]gbҠ iQ#KDi!7:x :wj@r#0"g|BO[H]&Ɲ2c}p8TvqyUr=~$I zgˬe_.yTV;8 <&sFӪ؞''~?7y 9UR^]4?tE;&^wD<M.~FF*Si'X-^߶GUmAdAuwi_yҍWn7tUG7X#FQȳ/|3 $D--8_.zeG:lSՀnO5zuOEk_@"`$P;0"DԉO >@ʿwxo۹'+*kעI<-j@U"m"D6p`N}:~/%O``b]O,zoԤӷ )w _S+28]: %g̠N}gV' $9tdm{Ed$z%˟7|m*"BD#>08+ӂo \ {zyowx=φĢG WmP^# ZqaĠ6H 2*GO6SYS;k_VЭm ; 'JL, #q]TCDݷ p]Y^m6]7^h4j~?,"eɠ!Ӏ!`$ Nۥ}Dvoo;xTk +%SFοK(E* a`p~:uxɩ Y.e#naZyPT#g?f2'E; B`p ?}ӶtxA{^Gwl[V<|g }EXFxOT$,_٘v Zga_>>?(F(lHRYdo1ABv%H2]߯i[+^;t==n/5&IB*TL vajXO hr7luL~?to:O|1H /v'6л_[sA#+j9~ _\Z1d~FIRlW?ߛFlF _,T=|'E0"Fн]c>,CtAO2 Z ֣jpQ5ra{dY/j)7$3kd~A:;>a8F4_vh~N$|0:we k4*Āx~iAP5b?WO~mvxu@q5S\BUeG´0Xw^$5?kHP)BtipfO3G[9ۯ㦫+aG ̨r\]gJHJ0xw:]BB/<ib?~㢧5Z6EOݯG ? _L^?yĩ96~ŽrC1fLB?@G<Dw3^!4vL6jKg?u6^/8#AxZ6I;r>+vzFC@O`X @qMןN}~{(R`{]N}'xUpy'LFspV3QH7Q&BĻ@|l~oh ?g4*?6-0_?\&wE﮹5"4;.J 9bqK@5w C/? Og6D 5 | 8POG<̇@B6` JFNǓßhEۦK+&[ [!QW0τ_qI"VS>}͉MޔPd&^KkAT_Z3` ?gA@wlMCddDk۾K+ k`oI;G&瀸#u;'$8nO oֲ.\z8>ƓJ`#^ @<̃_p7zh,׀F7?Cڣ+Plm[:\}yͥr/p ~Jű~珸!-#_%ҝ/mQ\RQHs ӯ ¹~1߈`3?(4R.'iu܌%I6nxVP܋Bv?I  Ћ h?#k_-2E d9Tk6@L>S Po`vyW ҋ\RQh ,DHI>?QG'  /Hߟ`s/-|d`7P< w+6\&fV/9#T~B<x9gӳ3\k9a9`?&HfKVWP=al) m9 1(tٯtIyҶGȵ,?Ls&0·dUx @ӇxQ>V$BE@`@0Bv?-o ((TM;d9q|vj`sZ\0_(;e<-OQF dYJih 0Gܩ AHL_-Y՛)_eM"TLk𕕏^R?@P$H$#u;uk?uZJ1PxXp)p<Zӟ E@3s Τ-8-K,XJQ l%b BejR @zysse|HtsR5ӟ8 9Q]Z8 e)JO~'T=Þ'ɱ[p9R &?]bQYO Aq&@P}xZqy,E~X&C{r;YtdkW0Gl?8 ermH@BL@ ~XuqY9A}a?Dϲ<:͗z2(h $@1Yr4Cb3)"Grøl5 k{h<_'E>-A՟ -(@U??r_]\SSk~jl/}g?Dz3E}Zҝ,*\ O G|N+ N}؆a<{P";x tҪNeϿ@yʎx <}CzEgbJ$ 8YT*K^W>^UE,9hnzJ/6'V4PI\4$96%`|}Gd[p~xn6a F4@H@S\^) 4i?ͻ`Z\5msX?#b@dcJ*"^>S_c˦6`qFvF9'X~g9w@?#hR ȿκ`gp}>${KNL_Xs&HbS@ @Upi}>_kIowʾnbb/B(_9cJ$)+N qfYz Q*74MO*thP*T9J|~?ov?tn_;"L](3HS0p0d_}&y ‡7I BS@Y(DŨ-2ҊX\{D .gYawLo|ϼ!-^ C01(hM>n훟c'RQgqTs~]s ]@…D `Ab J$@H>.욯Ѩ뜦r{9! D =Qب:Ř۷m^0'-B]H 8x-&3f;+2Qs T`NEH^@0fH@ @OB96[ew(yx;9̀؁*@xWiT~?q3Rax ȫ.ɓ&TIӟVqWTo! "h ݷ[Y&b푿f4iZ͚)Lhw0t B'8 " F % mxFŅߴSe{M#>mhq{ݗ!و\jjԌ  T237>\ @A:٭mA}֨и[B7 BP\aȈTi^޾A96䝌J\Hsi@Q{tͳ-&cݫ$Me/OtoF Bn(4җ,BډH Pa>+zwSq_r-%+Ԁm`n3I4nڦi,L+fFCS~#0|ڰ||QBe  @R\#9h6d9 lQ>#VωVE( HmJ*R}ɯXxBtD!`MHk Rjuev"Fڃ`XU * ёZ_eA-KP%99*$txa ]/`y佯nv$s{[:f~Bt EO E?v۰UF rZ up<1wQGBgtJmhtPR$^☣%啶Pq~Zj+ it|h@Zݱe^9wɵig(r5;pS` 5y:%;;'D@ch$Z##DUsbD_t[{݇_5U6C͚9U2Sj҈!+嬓7-\'D5S8R<"dK୵@B 4ĉ~Jҙ>E=vB!}Ns?v53-H'bq2,j zRM?Ԣw IDATeSY ~?Sg@DrUeq 0x :A"hz$t`fݙ -8b=y3 h 3BaL΍4y0E%>j^«a8 -ZKܼ`BN_ 8QB¢f7>=SE sD\@ _Tecxw&<sI'?V s!n!עGJDI"\oo"+_?EF☣C0\t]mgIiX^Ni>9#;yq*%`F! 8^tZVV XNYÁ >))P'zw`m |<ɑ8HXu(8[@Vs2!:uz!S@<,ء@qE5h*zɻo׵r_:mX/AHK6Nmp6#ˀ[oG(?ErSSI?[AWN3NTDXgok}%$W@&`j0$#~JFfSw&5Rv>GJ3bFe4DXqfxۆ"!S89H3PCz0] Wɓwcqa׈?o at\'u9D?6@" arЎ.ed> MFT<7]}{}4i?4 0SqJ)|PޮY)0/Z,f4oO"Nr=xNr?JOP/ZK[~$ ` T89*#9{s>Rp)aS[f6 $P8G Ѻҿ`]U^Vn7t-tŹP$:r':I":INB$hyh;F ECcBv7?CqkN^Gz-d'&Hzhrq32G\?IɥL\GsS:=75>* QIT4@;@@}~絗Q!ve}gƥ&p.d(3}Ԧj 25s"ބө!~G o``l`s6eNfQ\6{ ;=%<0$:5x}>k_-}G Ѥّ؃p: B?I"QBbhu0.!Xx*o6BnɴG~up]|~F,۸   DFL (yq\ڣSTg]nĄ\ne4)Xa] *`D;) \uR~ 8*o罄|>Ͽ"U'wFwq0Aܯ_]۴{{ߑ iG_Z{IC$k>WH+Ԑ=@ȚBNG?z7[ ƽ/@8QAwǩO|*w>_;#_s$&@W @I3 x[y3|@#W87o0/$.F"H) t_ԓѺy'B()וu&`%P_Ds}&Wsv$xzt}09c3/\+ŀ@d' ` 4MK_ƔLJ+CӦ^3b>gH5lyP6BP*PvM| |2cQ kL5?~Uh- FyIL\+PU_v3sT a- \]R ¾}}jnY#( d%4Z-*9zÒuwp )nF5@*L$$P 8#91wc˦GܿCv@)nIO/򥦿 :< |yGW'ގ9D8ô~@*3!OEiIK-Ǧg%ݐ<|8MNBi D=` `$~fȶ=G.8x 50wHx#@ɠ+\3nϺVauJ:`l!4]RK^hg]IH8 99@˙ug>aly}^o_P=108H ЃE$V xooZmDzMYLeO^#i$'@:d.'zCh"v?pep]d[oն~:( F1r Ā&@ʇKw=]~?LMOf^|>l|^$^^m ~&+3THOΑl%qi轀&hP`clU$No)8(ETᐸaT?dks8/b'D[鈁O(dxlu8^X7-vgN]S #YO iAdF.H|mvɓٿ[?ħN/Z < "q B9)/cG ;6TIu㥉7-OIUUo_w;˪쩑Ӡ:_zѯNWNG CI 1:@ s:o0U1t#eY˨׹v{)ɔx T^ɑCD@E:,GClqm^P\d1\sw6 =ЃdIoNύ5Ṃ=uϗ۷C ! N>h{Sxbkp$=:&Z/5owק}@^CeŒ{$ xhB&I9aL}vk>[bjg1O1nr3x5)"~H24?xp_0.Yg˲e'f" 9A9X>u4x[04D$'3ATrӞ'f/k ΃. O&T;\Mr^O"xO|'p}, ?.d<;H f $2`A 8D%"{F8\8+{/=E d]Y> \q{F }eY2js;C@Y6A0  EDbߓFdɧrZzgܶho9#ا  d'tAQ#8$Z7axT܍IIA1XAtZ-ɫ'r!9tHú]D40;q]mJuΔT;\t&{P 0=YDpٱobN @nkmy>l N:>_z&OQq`CpMz|u=[ ڮjdc}F ^CMq#>7 Ue5LWV olY9q4"`s!W^1V/|㱷[78^eX)9 fm+ |ƂbF qBzFa)^9mz0sPvh b g˲NQY%(VPR(@ ,Žܿ`,uYeԖ+)ITT ,@@̢!H* q]K}jn-D8@'Cv Q$F1 @gTc[6-Zs *p3gɾ\'&ZMשvL_Sb}c~* ]68^eJb+.5rZ1h o{9-r߃kp~[?T' k ‰ۑK@?w6,[]}M2jy!`И@t?;mu~O' U!KmcAIPbW0yN,U,o꧷TTeW\+[]!lz^%b0h $@7!0>roi^6a1C]`èagr,{njA3Z7q$;E' X0mv! rLi+_.CO>Su<.si͒@l>ɔBI@jw0C+T=Zm`hɲN\նYfB/6F1+2/+ /<(R: :uWB"$`S ?}%__wT9o7 o!:[dh @P'?]CJ@ʝ-N]Q:g-aF)d8F^ +s)h eYsfr Ǒ&DuěQy@6T=|''n^(\np[SwʲJµ.<!xp' #S}Ÿp wkp 廎 7C-o@qPޑ8!Ä%-eYk!0#_fVI@'3 ^,k q/B@Gm0h@$q~ xO|#0&hP8%Rw{O[rAPt@:#_.YiQ M m,0,*e[/#VWmP x0."Fuhp0,_y:::!'; ӃWy}.nqK?yUĢ'ǏXd9'H d3eFVx: *ˎ:xW':'u:/QQ>avHW> ݞò"3-;P0L`P+=O'm?i 0j*w8a\_VʺJc^H8J? FJЉz/<՟V| ^ noޅO} ۷}՗꿸o\Y $}?y',p>d'0W&ݲ@E"8w`S6mߓ>_@v30>s@ iPW|E.x'꿸7lF!>Zs~$ N`~fKBl 9Фhzo Ӊ?.P]'` Ft՞гm.)vyFn,& ` c˘/%}v<TXߚ.L?)XLǏ=KV Kx>!RúH{V5#kZKRG|tB7h9q*j{*ap@1R4'@:h/'Tz@/΃ymF?J+u]z *Ohvt5,EO㶡/ ~'?߄P ?de-jJ 4O!/eB62b IDAT;4W|s7 a?#μ8E+`X Uw dᘇ`TnOԷk43oޫPG<Oa@kOT%H|M:ȇUd9AQvl-r~6Xf-z2#Vxn < _\a?1^PTEa|_/[Q.}H\}HZdɵtCg-@nh1W~̽Z7I??qO;䶌¦02QCn*6n^ ?ukX3~үk ?]3 21j0.[5tAp] ¸/h }`]ѫ^OUc'4bVD9^﬏u@,IVcO+u_~V<=vC'/ xu۶yVxPU4o)L—OpO.tߥ~>7~{I O^]!]&8 9tl _lx۵?*&4ʃPtBl늌ĢY/6@b{t{ߍCgj4Ѕ;\Ɣ&BI>e`~9L8OFi4~۠x@-n5򲾻K&} h7FyHYyW?}׉3%1Sog&}ݪII*?~D~&# @R@*mI{Gf pQs6t7&D+Ov`_^ 5#Bdo"vn.,)h[V :lvG}+1}")Ł @Q^4D˓'s W2@a5^=p'B>9=>s$ AOBD#H\}_?nC:5]7.hjߠ]6؟"tӂO*?sF 2hTйv{gNi 7PT]&޴h`">Gw3{_f08 8#/@P x2w5CO7̝T*:nvQW:zp=UKyG!=" AMsoRPT 7P\׶}5}:h'=qxg08GH$_@A" f/]%]P^xtZwNmv>0?B'=O}rS/D@ZIB ,˞ӊ8г.s9_#;3-pn[&v] =9m^vDm!u$|?Gwt=gx-oqe(xaViO+D iD+0jm?ؼ'WT;*krgN#^mepZBȾhU FuBy@O6B7>n[_mpn܂vAːU*ғmfnMزYB<}=}{!-Lo@0 b2R)n0(Ei{+hvLVaIYzYϡQ}ɶiɧ[6(Ѯeu:Z).u?.3108%ЦZ:U\nvh RΔUڪNKir8M~?vFN^XFb'&X3SZglRԭ]@ܸE]Ork&1FQi 4Bh*B}siQ 5^.-b rG>Yr"R>ډ(Ekb~1K $}D-]``POiQJ;MT-~=BG8.瑄=xʃ ~#98XC, MfS`&F Ȁ~.'*mG=J=$R.~ōQ@ 85j 9@^X#=1o`  p?̈́]`E&Y @ሠI0f?K1ȥ[*^IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/32x32/000077500000000000000000000000001432334377300221545ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/32x32/apps/000077500000000000000000000000001432334377300231175ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/32x32/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000016411432334377300330570ustar00rootroot00000000000000PNG  IHDR pHYs tEXtSoftwarewww.inkscape.org<.IDAT8mHTYܚqtB`m# ۍRw2%R#YP Mm# ٍ\{bRKN%5f.ۋT46^ufq4͔·{8s $%.gҥƜ1 昮l@bX͉02mG+>T^a?mvX5J;8GHe0 _?rN!BE9r;k~yBʍM9n&/^摜J2dHfG} ӒC-$9$kwV>ɷ2etQr ?mo~BOTի'@02;tW뱤cQMddHIQ rf"\BڂnI| ^KN rYP^Ӎ{Y!ЃdšZrKtq3iJ! fXוf54XS/eh]Y OA{t>cB6T:2nAoU9 i~֚V{,dѾc6f5Qvlٿ'Bك1րs[wgoIbƀ{i})H[M{dYVBK:;E[k2śc[7Jii4N ž g}IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/36x36/000077500000000000000000000000001432334377300221645ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/36x36/apps/000077500000000000000000000000001432334377300231275ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/36x36/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000035531432334377300330730ustar00rootroot00000000000000PNG  IHDR$$ pHYs  DtEXtSoftwarewww.inkscape.org<IDATX{PE"DG(/˴M옎Nh>bI8idԊ$Fh$F5ZhTav;c躻ss^E'DD D-`mJ)#sN}ߡ'2]K9".+kkNW4"9q7` ^_~9G+ ]7 L<“Sޜ>fx pA)废@M6n}W3L7[P4u6- 1o/}WJy_AB u8)?uo? AZŞ_<(>XJ .=HkC[~2 R?[~D_UV;Oh\2WB‘#%7>V5BAD?Q"ifYy9gh _3, ""bӟ/[2Ea4.-ܧޙIv|+.+~R}xaC#0>ۍ~A-y9'p/ ìAZ]=^_{Zƛ;2q} n| t""DO[DtVDg˱ӷ"GF{q%"O<ʓ9+ ` Ħ)ja0MW_P, aLl=uo`۾Cp`R͙Fozzʘ"&np- &5Ac>װeH>޵Oa?H).xq#IHyѿGAu3+Ta1CKsAhs8 yt/[nHObmE55?*7nߋ_ݒeoA ' ObÖ_<3h9iZ~Y_C|Q<@i:WSRa6G_M<:r82U 7 B)3\xR_1x/涶9o .={r bkJr*Y3qX)8O28P1`KˇNU%9.B Own1 pz%st#'|XT:[t] c3aă%SF X:~A؁''Є)ϤgCѕ^IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/40x40/000077500000000000000000000000001432334377300221525ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/40x40/apps/000077500000000000000000000000001432334377300231155ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/40x40/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000040401432334377300330510ustar00rootroot00000000000000PNG  IHDR((m pHYs++VtEXtSoftwarewww.inkscape.org<IDATXyP?ooqpP1"6A/NjJZ3idN5ӈ1H=1HxU}\5U~ ͼm)@X+P8֬/BȲʚEүU$-vCHЃΡҷ%D^jnXB@EC>sJK6]kNqW9 Ǽ(LBst[XQ(q K"2c``R+2PĒpG]޲koWքy 2 qAEUY-tŧQ5xIMJ,ڱ$m+j @{2y58b<摯! +exw" X4 4(,(ߘ}al..-}mU{O\CfvP,ςSI);ӈLY2lU.Rѳ@ʍRnRRJD#<Mht(Ƀ@ՔvOڍ{|3R- c0]RNXϒOH]v#̎U4N ![-[P <ؒ_z:\U-y&z`9B]ta],v#Mtj2@bR@xIFs&8M⯢Z@4&B=︱^.<00jz礦oѵ j`{G:)/`Cݏ%>' qH^ɽ.UBu:ЧpQqcgY3dl}H'e=)EܗSLq[1cr^iJ,hMڷQa~P4A7m1Cw:rp&lծÌ+R *No N'hvr`X 'ѐ1OX1cPSJ͞q6ӷc_bc3ƦTH^Q` -eÅ&hցv IK˭-.h\ݐNIMLyoq`ᑓр `V8r`n>d4MWI5:nъݐ6ΊyS3&"5ϵuN^p`H7{9@r\Lz^W H)eR>ˍ1%ǾEIä+/ [+3dxF`r)eǖ܎b zD <ew*jNOW-[y&ї.,{е߀nHO!IMwJ]ڭ>qúh\GLV>!qڵ_m b`ZH& ('rT'v4qvǓ*\jA5A@Lmq1UeS!O9-)wZ4=pD?UG{w9xxD G*@k.ą&cPKJ~p%EBO XH}n8 3 B%zŨV%~QPeAwh >y!Yv9An#`ʈ_ rGgX,+zqUtdEpe|[L Jv5RTiHf-^xLc?I`t<f ܸasv ѝy>;U}=4 B=X#G믨yuz-cW͜=&}yw_o]R|rSam!@Kѿ.,4$PBZD> "b @(( pN(x2rF`SU5_ :YV5حe uf!ǒbcˁ EQZ~PV neΑo0j2SK S3&@(`m/~qdWD(}i%9hԺ:jSVmzm7Qگ7bУ**gA$j_/شv\|LW Tn_MYm/_it>8“ت*?T-aD[4;J3(`_pݦ^k8o@hMcȘJYFjEKM1J̄b(D4}RD<""}E$4K)7 +8>&߸t=4 bspԕXӨ}.p75>u#ˇ:Sk ξ KtߖhUV^Gx##pߛޮAXNf i!X͊Qа%mIM]f{^:;v;@-)H\r56զ >g:\ m= (1,ߣb1 ַ v2yًkNVws~:Gd0^Ù P[OϮ%%sH?{๤b;>DO]:iLjJ\\}jbl=trUs~%9O<4CuU;;fI튈("cѺ-;3rN)R%]D:y}EDdEb&̕Q|\D$z?gf=bvo;: N .k@=8Sg~}[٪Q<53jN)1'=0xw#,w''07`T5z|Y͕9o%^tUUh^@f/gyUy/*s`{o896cU }7#yF\`𝁊Χxm2 :fТP{|us+j͡Ί]2dBs !lDX̮7>;=51>h(j^#:,#ՋdJyWY ~c# ./f*ayaFӆ? qVED"."_0vg&$L."H^k/H z y jەNEHpgc%٧d\H,w!&ت+I)1-6EVE-@3s3RW?~8ʸ~km0I@xt>ؾ+6!~v?@_JUN Ci%pCVPĭsc/|Z҅H-FD-Ŕ?u5`Gů37vNU=z!18~:olWhC?%q6k_ZtV|A=qQMV~FWܵwFd]y;ҏ-r qvSkPKMvIyݿ\!r @|nWL67q.t@!PD?M3{*.2yOr+g*l>%Qi^f kݑ7-LkgC6ЖYќ+F 8B> r؅a+s  q# \^cg?]T ~= EG-< 'bpY>eRJpx&}jṗWa? x@͚ *c Vfzƚ~m-*)kԷPuÇi(T_P<^4q& ͎єZik`PT]G gNHF$N /^HBAK+½ MqMlPTVqv@, wT5a~ڭ[FJ0uZ!{\X5{p5n^xi2}5vJRՃ?ހEJ o_Nzɫ|Rzj&z޷T Ε=i@k@\$% I ӈF Hʼn c#^]a@<( Rjv>ϫgMZ_ne0WN?ȴ;ڵjyjKK}ޚDܓFw1vFαEPʗBZe,w?>7e#m/>9ߣ(BPꀳLU`Aiu6P(VߪL_ȾPKlʈvsbWi U9 %,xk? {a,W+ C3 6C7O|TCqtM|lf%nIkp,NEvpPyh 4Zu}I-U5oornRo:ڵSKQͤ=wnMjz/ػa0u$#C;mDJ#Q4aXw_Ȭ Hn7P;rþ9P51ܑWROQƿ"4J)lܽo̠뫖K)٩ON+>x1MJ|IAJYח){͛$Gs( D:)i3~%w< n*Z"ҟV#zl^)@?LKͅ3@)4OҰWc @pok/,eR E@̔(;a "ʽ;lahb5 "S}m R~i;mwZ<:7˵oT?VMkLh€]/f#}v_K(b {Q[qM(wJB׭ITKoP{wLxXJ/lW)B Zf-wvhn$Z|w׎=2~UꮛgMD(;yQ{3{.Dٹ9h9&~  u[|;Pޡ%{3: f{ 2JLmiR܊.9DBfX23|AIqї^7|%PИE럼XnZ9,H\tlWyFZйy:]>` EO1ygӐ[vfF 6oGw V#s?~~LUK$-\Ue#ݷS;<a);|h7jzgˀ_~o^H)M/`J\5eԠO=xd !0eǭٶ籅;*8));ΞMfǤ/Q^19-X n^vg:V\6K`pk{Sí=kר ^[J9s*KHV3 `E.Šif!P_<%~jGi$W)M}σ]IENDB`studio-controls-2.3.9/usr/share/icons/hicolor/64x64/000077500000000000000000000000001432334377300221665ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/64x64/apps/000077500000000000000000000000001432334377300231315ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/icons/hicolor/64x64/apps/com.github.ovenwerks.studio-controls.png000066400000000000000000000070611432334377300330730ustar00rootroot00000000000000PNG  IHDR@@iq pHYs tEXtSoftwarewww.inkscape.org< IDATx͛y|TEǿl&! I `"`D<dPQ"y 2uPpdqcT`XEQ"B@B $k5fIza}>{ϭsN:%q!5REoǥ|{$IlN^!zH忷[ZX2w8+Jǵ**kة.UآB- z[ltv[ 󾛳2O灋ȤXJ"B H\-t:jL : of@.p$`@( HxgcJ{\vo.M`p c- >ŝ;^հmN:zP ?/ȝwm@ PmsV7qBV<u%*h6Ci54$ ?,8w-$ T 7wFD%HA"D9 |`DIڄ뇵 HZa]f pB`غ~P@ P/nٸmNjz+Q/-Er|/^va#APSU[߸ /rK%TM{MA @ђi/Ms=}4Zļc]ʯAѰ#4p~a_YBKSz ٽ2)ipOXeu$ʹf/?֧;~ʔV7ei}t^{4I,GNkW^Ug1_~ƷG;h6?k46sBlE:៛$ͽfг 6Scq(6 N)֩+^SFw(~]Μ2f*Njw閇Rodi@Bj"=H$NN/7}ۯaq6s\p3~Խ7g;t I ?9f]T#+Ϗ_h~![Ħ rn~hⲊJZcj+`X :EV % Ow9#`I Mr!eiZܫۿ|* jߑnW`7-v 9s( c}ʪԆ),ɼ{W JʻIJ8.h :k̉qVKXnP:p B~@"ȄXn1oPdsv38䅏U"2 9!t:j_x$_MM t,xIp {-9ixߜJwH) Pq}C]!{ybA+&=}ʊJ/Xf8o4HB~nݱϿҁ0wc΄џs ,^v}|b]Sx89,1nvUO-_yé;QR7sbzU;ObyEg5@#Im 4$IIp 5Qs?ioRU4Is{%,v2F@ݭb""ISgXq5&tjD6^&sW_Xc"-Yirzl@0d$sN?9ACS*,J3 %lf“NE<\|An"$P6PG#G`v;`H'u}tZ{ܠ;m @c?붣>w,'+mǑr Bj |q=+W}mej牑?B Odi9 r8̀ADk@p_Mszn%J v;NVL. z"?Տ{{ YЅ"[\Xunw&uvÓ&/>@ Y|wVyaܰ`Ȃ ! @F //8S sW<=M~15[}+&zQoy@]{0Ӝ˅qv +{2@}ڟNߛ?DĄaU:b4Ry>ܶ1yav#MABԣ1H1uwiB= ;i TS쎬4sG<쯐ͅ䴘! +@|QiEVM] cnɌh4' jPS!/|gq7GN*:{ K^d.۞;x^\C=DS)<+tY:KZ- IR#LI1GMlTui*–^2 ȄD  Ai;^y3 < image/svg+xml studio-controls-2.3.9/usr/share/man/000077500000000000000000000000001432334377300173745ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/man/man1/000077500000000000000000000000001432334377300202305ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/man/man1/studio-cmd.1000066400000000000000000000015441432334377300223660ustar00rootroot00000000000000.TH studio-cmd 1 "18 May 2022" "version 2.3.2" .SH NAME studio-cmd \- Is an audio setup utility for sound studio use .SH SYNOPSIS studio-cmd [start|stop|phones|monitor] .SH DESCRIPTION Studio-cmd is a command line utility for sending some commands directly to the studio-controls back end (autojack). This is to allow adding convenience buttons to a systray with extra code or for remote control. .LP Studio-cmd is always run with exactly one command. .SH OPTIONS start - Start or restart the JACK server and bridges .LP stop - Stop the JACK server and restart pulseaudio .LP phones - Switch the monitoring outputs to the headphone outputs .LP monitors - Switch the headphone outputs to the monitoring outputs .SH SEE ALSO studio-controls(1), autojack(2), studio-system(2) .SH BUGS No known bugs. .SH AUTHOR Len Ovens (len@ovenwerks.net) studio-controls-2.3.9/usr/share/man/man1/studio-controls.1000066400000000000000000000023231432334377300234620ustar00rootroot00000000000000.TH studio-controls 1 "16 June 2018" "version 2.0" .SH NAME studio-controls \- Is an audio setup utility for sound studio use .SH SYNOPSIS studio-controls .SH DESCRIPTION Studio-controls is an audio setup utility. It has a GUI that first checks that the user has correct real time permissions to run professional audio applications. The user is warned if real time permissions are not correct and if the user desires, corrects permissions. The user will be asked to log out and back in so these permissions can take effect. .LP The running of jackdbus and extra utilities is also set up with studio-controls. Along with jackdbus itself, extra audio devices can be set up as jack clients, USB devices can become jack master when plugged in or added as jack clients. A2jmidid can be started to bridge ALSA MIDI devices and applications to jack. Pulseaudio can be bridged and connected to the right outputs of an output device. .LP Studio-controls tries to be non-invasive and when jack is turned off the system should run normally using Pulseaudio for sound. .SH OPTIONS Studio-controls does not take any options. .SH SEE ALSO studio-cmd(1), autojack(2), studio-system(2) .SH BUGS No known bugs. .SH AUTHOR Len Ovens (len@ovenwerks.net) studio-controls-2.3.9/usr/share/man/man2/000077500000000000000000000000001432334377300202315ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/man/man2/autojack.2000066400000000000000000000010731432334377300221160ustar00rootroot00000000000000.TH autojack 2 "16 June 2018" "version 2.0" .SH NAME autojack \- a jackdbus manager .SH SYNOPSIS autojack .SH DESCRIPTION Autojack is run at session start and using a config file generated by studio-controls to run jackdbus and other audio utilities. .LP Autojack is meant to be run from /etc/xdg/autorun/ and will change configuration according to dbus signals received from studio-controls. .SH OPTIONS Autojack does not take any options. .SH SEE ALSO studio-controls(1), studio-cmd(1), studio-system(2) .SH BUGS No known bugs. .SH AUTHOR Len Ovens (len@ovenwerks.net) studio-controls-2.3.9/usr/share/man/man2/studio-system.2000066400000000000000000000017461432334377300231550ustar00rootroot00000000000000.TH studio-system 2 "16 June 2018" "version 2.0" .SH NAME studio-system \- The system end of studio-controls .SH SYNOPSIS .BI "studio-system " "command" .SH DESCRIPTION Studio-system is the system half of studio-controls. It runs commands as system that the gui requires. Only one command can be sent at a time. The commands are effective over reboot. .LP Security is ensured by not taking any random text as options or commands. Rather commands are limited to those listed. .SH COMMANDS .B fix - Fixes rt permissions for the user running studio-controls .LP .B boost - Turns CPU BOOST on .LP .B noboost - Turns CPU BOOST off .LP .B powersave - Sets CPU governor to powersave .LP .B ondemand - Sets CPU governor to ondemand or powersave if ondemand is not supported .LP .B performance - Sets CPU Governor to performance .SH OPTIONS studio-system does not take any options. .SH SEE ALSO studio-controls(1), studio-cmd(1), autojack(2) .SH BUGS No known bugs. .SH AUTHOR Len Ovens (len@ovenwerks.net) studio-controls-2.3.9/usr/share/polkit-1/000077500000000000000000000000001432334377300202615ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/polkit-1/actions/000077500000000000000000000000001432334377300217215ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/polkit-1/actions/com.studiocontrols.pkexec.studio-controls.policy000066400000000000000000000013521432334377300334400ustar00rootroot00000000000000 Authentication is required to run Studio Controls ubuntustudio-controls yes yes yes /usr/sbin/studio-system studio-controls-2.3.9/usr/share/studio-controls/000077500000000000000000000000001432334377300217715ustar00rootroot00000000000000studio-controls-2.3.9/usr/share/studio-controls/audio.conf000066400000000000000000000005171432334377300237440ustar00rootroot00000000000000# Provided by the studio-controls package. In case jackd is not installed # or is not installed correctly # # Changes to this file will be preserved. # # If you want to enable/disable realtime permissions, run # # dpkg-reconfigure -p high jackd @audio - rtprio 95 @audio - memlock unlimited #@audio - nice -19 studio-controls-2.3.9/usr/share/studio-controls/blacklist-studio.conf000066400000000000000000000005201432334377300261120ustar00rootroot00000000000000# Select the legacy firewire stack over the new ALSA one. blacklist snd-firewire-lib blacklist snd-isight blacklist snd-dice blacklist snd-fireworks blacklist snd-bebob blacklist snd-oxfw blacklist snd-firewire-digi00x blacklist snd-firewire-tascam blacklist snd-firewire-transceiver blacklist snd-firewire-motu blacklist snd-fireface studio-controls-2.3.9/usr/share/studio-controls/studio-controls.glade000066400000000000000000004526061432334377300261540ustar00rootroot00000000000000 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 100 1 10 Default_Audio For a user to be able to run the jack audio server in realtime mode, the system setting "Realtime Audio" needs to be enabled and the user needs to be a member of audio group. This utillity checks this and fixes it if requested. CPU Governor settings can affect low latency audio. Audio works best if the CPU speed is constant. For this to happen, The cpu Governor should be set to "performance" and "boost" should be turned off. These settings can be turned on and off on the fly or be set to take effect from boot. Turning performance off while not using low latency audio may increase battery life on a laptop. 500 False True True Studio Controls Help False True True False vertical True False 8 GENERAL False True 0 True True 8 8 4 False word 12 12 help_general_text_buffer False False 1 True True 8 8 4 False word 12 12 help_system_text_buffer False False 5 True False Ok 100 True True True 8 8 1 0 True False True 0 0 True False True 2 0 False True 6 1 100 1 2 10 100 1 10 1 64 1 10 100 1 10 8300 9000 1 10 400 False Studio Controls center-always 500 center True False 8 8 8 8 True True vertical True False 4 8 Studio Set Up Utility False True 0 True False True False end 6 vertical Start or Restart JACK True True True Starts JACK. Also saves audio settings and sets JACK auto start on session start. True False True 0 Stop JACK True True True Stops JACK. Also saves audio settings and unsets JACK auto start on session start. True False True 1 Apply Audio Settings True True True Saves audio setting to the config file at ~/.config/autojack/autojack.json and tells autojack to reread the config file. True False True 2 True False False True 3 True False 6 6 6 vertical True True False True False start JACK status: False True 1 True False end unknown 17 False True 8 2 False True 0 True False start DSP: False True 1 True False True True False start X-runs: False True 0 True False center 4 0 False True 1 Reset True True True end False True end 2 False True 2 False True 4 True False False True 5 True False 0 Open Device Mixer False True 6 Open Pulse Control True True True False True 7 Open Carla True True True Carla's patch bay can be used to set JACK's connections False True 8 True False False True 9 True False 6 vertical Help True True True 4 4 False True 0 Close True True True False True 1 False True 12 False True end 0 True False vertical True False 19 7 vertical False Warning: Real time permissions have not been properly installed! False True 6 0 Fix Real Time Permissions True True True False True 1 False True 0 True True center center True False center center True vertical True False These are system Settings that can be changed on the fly depending on the work being done. For low latency Audio work, The Governor should be set to performance and Boost should be off. For best battery life choose ondemand or powersave (depending on your CPU). Many FireWire devices will work with the ALSA Modules but using the FFADO modules often allows lower Latency operation with more stability. The ALSA Firewire modules are set to have minimum buffer size of 256. True False True 0 True False center True False end CPU Governor: 0 0 True False end Intel Boost: 0 1 True False 0 0 1 0 True False 0 0 off on 1 1 True False end Logging level: 0 2 True False 0 Warnings Info Debug Extra 1 2 True False end FireWire Module select (reboot to switch): 0 3 True False 0 Use ALSA FireWire Modules (default) Use FFADO FireWire Modules 1 3 True True 1 True False System Tweaks 0.04 False True False center center 8 True True False end Dummy Channel Count Capture/Playback: 0 3 True False end Main Output Ports: 0 4 True False These are the ports that go to the speakers 0 1 4 2 True False end JACK Master Device (no USB): right end 0 0 jack_device_combo True False 0 0 1 0 2 True False end USB device that should be Master: start 0 1 True False 0 on 0 1 1 2 True False start 4 True True False end JACK Backend: right 0 0 True False 0 alsa dummy 1 0 -1 True False end JACK Sample Rate: True 0 1 True False 3 22050 32000 44100 48000 88200 96000 192000 1 1 True False end JACK Buffer Size: 0 2 True False 6 16 32 64 128 256 512 1024 2048 4096 1 2 True False end JACK Periods: 0 3 True False 0 2 3 15 1 3 3 0 4 True False end JACK Extra Latency - Capture/Playback: 0 2 True True 0 ad_jack_play_lat 1 True 2 2 True True 1 number ad_jack_chan_play True if-valid 1 2 3 True True 0 ad_jack_cap_lat 1 True 1 2 True True 1 number ad_jack_chan_cap 1 True 1 1 3 Bridge ALSA to JACK MIDI (turn on a2jmidid) True True False center True True 0 6 3 True False center 0 5 4 Allow non-unique (a2jmidid -u) True True False True 3 6 1 True False JACK Master Settings 0.04 1 False True False center center True Hide this device (blacklist) True True False center True 0 1 True False center True False end Sample Rate: right 0 0 True False 0 1024 192000 1 0 True False end Frame (buffer) size: 0 1 True False 1024 1024 1 1 True False end Number of Periods to use: 0 2 True False 3 2 3 15 1 2 2 1 3 True False True False Capture Port Count: right 0 0 All True True True 2 0 True True 0 ad_xdev_chan_cap 0.01 True 1 0 True False end Capture Latency: 0 1 True True 0 ad_xdev_lat_cap 1 1 0 2 2 True False True True False end JACK Base Name: 0 0 True True 1 0 2 True False Playback Port Count: 0 1 True False end Playback Latency: 0 2 True True 0 ad_xdev_chan_play True 1 1 All True True True 2 1 True True 0 ad_xdev_lat_play 1 2 1 1 3 Switch to Headphones Output True True True Switch between Speakers and headphones in case it can not be auto detected. 0 8 Bridge USB Devices to JACK When Plugged In True True False Setting Capture or Playback ports to anything other than 0 or setting Hide will over ride this for that device. start True True 0 5 True False center 0 4 3 True False True False end Select Device to Edit: False True 8 0 True False 0 none none True True 1 0 0 3 True False True False end Headphone Devce: False True 0 True False Choose the device/ports where the headphones get plugged into. 0 True True 1 0 7 3 True False True False end Headphone plug in Action: False True 0 True False If script is selected, The script at ~/.config/autojack/phones.sh with a param that is True for phones plugged in and False otherwise. 1 No Action Switch outputs to Headphones User Script False True 1 True False Headphone Left Port Number: False True 2 True True phone_pts 2 True 1 False True 3 0 6 3 Switch to Monitor Output True True True 1 8 2 True False Extra Devices 2 False True False center center True False end Bridge to Configure: 0 0 True False Select a Bridge to edit 1 0 True False end Direction: 0 1 True False Bridge Names must be unique. They cannot be the same for input and output. end Change Bridge's Name: 0 2 True True Change this Bridge's Name. No spaces allowed 1 2 True False Must be unique. 2 2 True False end Bridge Channel Count: 0 3 True True 3 1 ad_pulse_chan_out 1 True 1 1 3 True False The auto connections are made starting at the port listed and continue to port count or the last device port, whichever is less. 2 3 2 True False end Auto Connect Port: 0 4 True False 1 4 Remove This Bridge True True True 2 5 True False 2 Add Input Bridge Add Output Bridge Add A new Bridge 0 5 True False 1 Input (JACK to Pulse) Output (Pulse to JACK) 1 1 3 True False Pulse Bridging 3 False True False center center vertical True False Most people will not want to change this. Use the default value unless you are using a session manager to make connections. Otherwise your applications may loose it's connections on reopening. In general prefer "Ignore" over "Fail" modes. center False True 0 True False center True False end JACK Self Connect Mode: False True 0 True False start 0 Don't restrict self connect requests (default) Fail self connect requests to external ports only Ignore self connect requests to external ports only Fail all self connect requests Ignore all self connect requests False True 1 False True 1 True False center True Start Agordejo True True True Agordejo Is the GUI for New Session manager and works with all NSM aware applications False True 0 Start RaySession True True True RaySession manager is a replacement for non-session manager that works with all NSM aware applications. False True 1 Start New Session Manager True True True The New Session Manger basic GUI ported from NTK False True 2 False True 3 4 True False JACK Session Managment 4 False True False center center True False center True False Zero Channels turns MIDI net working OFF end Channels: 0 2 True True Zero Channels turns MIDI net working OFF qnet_count_value True True if-valid 1 2 True False 0 1 4 True False MIDI networking 0 0 4 True False Select MIDI ports to be JACK or ALSA ports 0 JACK ALSA 3 2 True False 6 6 vertical 2 2 0 2 3 True False center True False JACK Network Audio Bridge 1 0 True False end Bridge to Edit: 0 1 True False 1 1 True False end Audio Direction: 0 3 True False 0 Playback Audio TO another Computer Capture Audio From another Computer 1 3 True False end Channel Count: 0 4 True True zn_cnt 1 4 True False end Bridge Name: 0 2 True True 1 2 True False 2 16bit integer 24bit integer 32bit float N/A (Receiver) 3 3 True False end Sender Bit Depth: 2 3 Remove this Bridge True True True 3 5 True False 2 Add Net Send Add Net Receive Add Network Bridge 0 5 True False end Bridge Receiver IP: 2 1 True True 3 1 True False end Bridge Receiver Port: 2 2 True True zn_port 3 2 True False label 1 5 2 True False end Receiver Latency (ms): 2 4 True True zn_late True 10 3 4 0 0 3 True False 12 8 0 1 3 5 True False Network 5 False False True 2 False True 1 False True 1 False 2 2 2 2 Info False center-always 400 115 True dialog center window_main window_main warning For changes to take effect, you need to do a reboot. False vertical 2 False 10 True end Ok True True True True True 1 False False 0 False 2 2 2 2 RT info False center-always 400 115 dialog True True center window_main window_main warning It seems you have made settings previously and not done a fresh login, or you already have realtime settings in effect, but you are not using the file supplied with the package jackd. If you want to use this application for administering realtime settings, please remove any custom settings and reboot your computer. False vertical 2 False True end Ok True True True True True 1 False False 0 studio-controls-2.3.9/usr/share/studio-controls/version000066400000000000000000000000061432334377300233750ustar00rootroot000000000000002.3.9