Repository: loh-tar/tbsm Branch: master Commit: f5f6ab3a901b Files: 16 Total size: 66.3 KB Directory structure: gitextract_pwe34j84/ ├── .gitignore ├── .kateconfig ├── Makefile ├── README.md ├── doc/ │ ├── 01_Manual.txt │ ├── 60_DefaultConfig.txt │ ├── 80_ReleaseNotes.txt │ ├── 81_ReleaseLog.txt │ ├── 90_ToDo.tx │ └── 99_License.txt ├── readme-install.txt ├── src/ │ ├── .shellcheckrc │ └── tbsm └── themes/ ├── austere ├── gently └── riddler ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # I suggest you use a global ignore file where you keep a list # of typical files to ignore. # Add here only pretty tbsm specific files or directories. ================================================ FILE: .kateconfig ================================================ https://docs.kde.org/stable5/en/applications/katepart/config-variables.html kate-wildcard(Makefile): replace-tabs off; remove-trailing-spaces modified; kate-wildcard(*.sgml): replace-tabs on; indent-width 2; remove-trailing-spaces modified; kate-wildcard(*.md;*.txt): replace-tabs on; indent-width 2; remove-trailing-spaces modified; kate-mimetype(text/plain): replace-tabs on; indent-width 2; remove-trailing-spaces modified; kate: remove-trailing-spaces modified; default-dictionary en_US; replace-tabs on; ================================================ FILE: Makefile ================================================ DESTDIR= MyName=tbsm # Thanks to https://stackoverflow.com/a/27132934 THIS_FILE := $(lastword $(MAKEFILE_LIST)) all: none none: @echo "To update of a new version of ${MyName} run 'make update'" @echo " => That will keep user created files in /etc untouched" @echo "To install, run 'make install'" @echo "To remove every thing, REALLY!?...well, run 'make uninstall' " install: install -pDm755 -t ${DESTDIR}/usr/bin src/${MyName} install -pDm644 -t ${DESTDIR}/etc/xdg/${MyName}/themes themes/* install -pDm644 -t ${DESTDIR}/usr/share/doc/${MyName} doc/* ln -srf -T ${DESTDIR}/usr/bin/${MyName} ${DESTDIR}/usr/share/doc/${MyName}/"70_SourceCode" update: rm -f ${DESTDIR}/usr/bin/${MyName} rm -rf ${DESTDIR}/usr/share/doc/${MyName} @# Only remove what we had installed, don't touch user created files -rm -f ${DESTDIR}/etc/xdg/${MyName}/themes/{austere,gently,riddler} @# Remove careful none empty directories -rm -d ${DESTDIR}/etc/xdg/${MyName}/themes -rm -d ${DESTDIR}/etc/xdg/${MyName} @echo @echo "Hint: If there is some 'error' printed, don't care! Ignore it as done by make" @echo @$(MAKE) -f $(THIS_FILE) install uninstall: rm -f ${DESTDIR}/usr/bin/${MyName} rm -rf ${DESTDIR}/etc/xdg/${MyName} rm -rf ${DESTDIR}/usr/share/doc/${MyName} .PHONY: all none install uninstall update ================================================ FILE: README.md ================================================ ## Terminal Based Session Manager **tbsm** is an application or session launcher, written in pure bash with no ncurses or dialog dependencies. It is inspired by cdm, tdm, in some way by krunner and related. **Last version is 0.7, Nov 2022** Details about **tbsm** can be found in the doc directory and on the [tbsm home page](https://loh-tar.github.io/tbsm/) with some screenshots. ### License GNU General Public License (GPL), Version 2.0 ================================================ FILE: doc/01_Manual.txt ================================================ tbsm v0.7, Nov 2022 Index ======= 1- Introduction 2- Things Of Interest 3- Post Install Tasks 4- Contact 5- Thanks To 6- Copying 1- Introduction ================= tbsm is an application or session launcher, written in pure bash with no ncurses or dialog dependencies. It is inspired by cdm, tdm, in some way by krunner and related. To became an overview of available options run: tbsm help It was attempted to design the behavior of tbsm to be as less pesky as possible, and to start daily tasks with as less key strokes. To do so, tbsm saves the last run session and a default session. These both are available by the quick menu where you can start the default session by simply hit or the last session when you delete the bang ! previous. You can run there any command tbsm knows without to remove the bang. The last run session will not changed when you choose the default session. You reach the quick menu from the full session menu by hitting on an empty prompt. To go back you have to give the needed command which is 'm'. You should know that there is not really any special at the 'full session menu'. It is only a pimped out listing of your tbsm configuration where as the commands 'search' and 'list' show a poor list. In any way you have to use the listed index numbers as argument for any command you like. The quick menu on the other hand is a little special but still works with the same intern list, of two entries only, default and last session. You can verify this when you give 'l' at the quick menu prompt. May you have a nice day, I run now 'q' 2- Things Of Interest ======================= He only works with .desktop files to start something, there is no other possibility. See 4-6- for a custom example. The following statements assume a usual configuration. However there will XDG_CONFIG_HOME and XDG_CONFIG_DIRS respected. When running tbsm without any argument he is scanning for available .desktop files in /usr/share/xsessions/ , /usr/share/wayland-sessions and $configDir/whitelist but skip any file referenced in $configDir/blacklist. Whereas $configDir is ~/.config/tbsm. The latter will created if not exist but not filled with default files, do it by yourself if needed. The former will searched for themes and the config file but not for white or black listed files. In the simplest form you can create a config file like so: echo "verboseLevel=2" > ~/.config/tbsm/tbsm.conf Or perhaps: echo "theme=austere" > ~/.config/tbsm/tbsm.conf When searching for any pattern he run a grep with the pattern on all .desktop files in /usr/share/applications/ The first session ever started became the default session. There may some option or command variant available which is not documented. They are there to be more comfortable or due to the laziness of the coder. 3- Post Install Tasks ======================= To auto start tbsm after login edit your ~/.bash_profile, or similar, and append to the end something like: # Auto start tbsm after login on first two VTs [[ $XDG_VTNR -le 2 ]] && tbsm This is only an suggestion, there may better ways. Look at section 4- how others configured their system. NOTE: Ensure you start no other display manager. Check your distribution's documentation how to disable system services or uninstall packages. 3-1- Customizing ------------------ Take a look at /etc/xdg/tbsm and subfolders. Create your own theme files in ~/.config/tbsm/themes. By default is tbsm a little chattering (--verbose) with the intend you become familiar with it. But quickly you will be annoyed and therefore have all themes a setting verboseLevel="2" (--info) which should be good for most cases. Run the command 'tbsm doc conf' to become an idea what else you can customize. 3-2- Special Start X Configuration ------------------------------------ By default will X started this way: startx ${bin[@]} -- ${XserverArg[@]} If that doesn't full fill your needs, you can create an own startup file in one of the config directories. It must be named "start-x" and will be called with the three shown arguments, but quoted. As the file is called, it must be executable. Here the simplest possible example: $ cat ~/.config/tbsm/start-x #!/bin/bash # $@ contains: "${bin} -- ${XserverArg}" but all splitted, so $2 may "--" # but not for sure. $3 is typically ":1" (display) $4 typically "-nolisten" # and so on. Depending on your config settings. # # do something before startx $@ # do something after For a little more complex example see 4-5- 4- Tips & Tricks ================== Here are some hints collected in order as they was reported. Should that list grow in the future I should better enable the Wiki feature on GitHub. 4-1- tbsm together with tmux ------------------------------ When using tmux, tbsm will run with the above mentioned setup in each terminal session. To solve this try the following: # Auto start tbsm only on tty1 if [[ ! ${DISPLAY} && ${XDG_VTNR} == 1 ]]; then exec tbsm fi Thanks to Anton, https://github.com/loh-tar/tbsm/issues/10 4-2- tbsm together with fish shell ------------------------------------ Add to .config/fish/config.fish if test "$DISPLAY" = "" -a "$XDG_VTNR" = 1 tbsm end If $DISPLAY is empty and XDG_VTR equals 1 then execute tbsm. Works with tmux. Test in sway, alacritty. Thanks to Anton, https://github.com/loh-tar/tbsm/issues/10 4-3- Prevent exiting out of twm for example --------------------------------------------- Instead of just calling tbsm, "exec" it similar to 4-1- # Auto start tbsm after login on first two VTs [[ $XDG_VTNR -le 2 ]] && exec tbsm Thanks to toke, https://github.com/loh-tar/tbsm/pull/12 4-4- To avoid trouble when also SSH access is needed ------------------------------------------------------ [[ -n "$XDG_VTNR" && $XDG_VTNR -le 2 ]] && tbsm Thanks to 0BAD-C0DE, https://aur.archlinux.org/packages/tbsm#comment-841043 4-5- Redirect Xorg output into custom logfile ----------------------------------------------- With this redirection will the terminal not fluted with unreadable chatter from the X server or the session. Interesting read! Of cause you can also dump it by redirect to /dev/null $ cat ~/.config/tbsm/start-x #!/bin/bash ownLogfile="/tmp/$USER/tbsm/xorg-chatter.log" mkdir -p $(dirname $ownLogfile) # We also set an option to improve readability on high resolution displays in # a slightly strange way. Typical we would set XserverArg in the conf file # Just remember: $@ is like "${bin} -- ${XserverArg}" startx $@ -dpi 120 2> ${ownLogfile} 4-6- Add a shell session to the menu -------------------------------------- In this example you will be dropped to a bash shell, other shells will have similar options. When your are done type 'exit' and you came back to tbsm. $ cat ~/.config/tbsm/whitelist/bash-session.desktop [Desktop Entry] Name=Bash Session Exec=bash -i Terminal=true Asked by bee-keeper, https://github.com/loh-tar/tbsm/issues/18 5- Contact ============ The home of tbsm https://github.com/loh-tar/tbsm Arch forum talk https://bbs.archlinux.org/viewtopic.php?id=207817 The author of that all loh.tar@googlemail.com 6- Thanks To ============== Ian Brunelli , not only for adding Wayland support Jan-Luca , for providing an openSUSE package All contributors of cdm and tdm, without which I probably would not have started tbsm in the first place stackexchange.com, bash-hackers.org, for their great knowledge base and tutorials dict.cc, google.com (till v0.5) and deepl.com (since v0.6), who make my english much more understandable Many more I have miss to note (sorry!), but not github.com for kindly hosting. Last but not least: Mom & Dad and archlinux.org 7- Copying ============ Copyright (C) 2016-2019, 2022 loh.tar@googlemail.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ================================================ FILE: doc/60_DefaultConfig.txt ================================================ tbsm v0.6, Feb 2022 Default Configuration ======================= This file is written with blood, sweat and tears only for your convenience and lists the hard coded defaults with some comments. You can use this file as inspiration or starting point for your customization. Create a tbsm.conf file with portions out of this file or extract all of it as blue print e.g. by running one of these commands: For your personal config: $ tbsm doc conf|sed -n "/^# ~COMPLETE~/,/^*$/p" >~/.config/tbsm/tbsm.conf To only extract some SUGGESTIONS use it instead of COMPLETE in the command. $ tbsm doc conf|sed -n "/^# ~SUGGESTIONS~/,/^*$/p" >~/.config/tbsm/tbsm.conf After that, edit the new file as you like to gain some effect. If you think it should fit as system wide file, move it to /etc/xdg/tbsm/tbsm.conf. As ever, these are the usual paths, adjust them to fit your system. HINT: We don't ship a config file in the system config directory to avoid trouble in case of an update. -------------------------------------------------------------------------------- # ~COMPLETE~ Default Configuration # If you read this as "tbsm.conf" then was this extracted from the tbsm # documentation. Try "tbsm doc conf" # Nothing in this file is enabled by default. # # # None theme related settings: # XserverArg="@Xdisplay@ -nolisten tcp" # @Xdisplay@ will replaced by number of tty # verboseLevel="3" # 0=quiet, 1=silent, 2=info, 3=verbose # theme="" # Ok, it is theme related # sessionPfads="/usr/share/xsessions:/usr/share/wayland-sessions" # Yeah, looks like a typo, but use it as written :-) # Available text (foreground) colors and attributes, protected not to change. # Use them to modify the look as you like. # txtClean # All attributes off # txtBold # Or bright, depend on your terminal # txtUScore # Underscore # txtBlink # txtRVideo # Reverse video # txtHide # Concealed, very useful here... # txtWipe # Clear the screen # colBlack # colRed # colGreen # colYello # colBlue # colMagenta # colArchBlue # Well, someone may call it Cyan # colWhite # These are the variables used to influence the look and feel. You can change # them as you like but not use without to set them before. What??? # Well, they are assigned (or not) after any config file is processed. # txtNormal="${txtClean}" # The reset key to show ordinary text # tbsmColor="${txtBold}" # promptCol="${tbsmColor}" # menuTitle="${tbsmColor}T${txtNormal}erminal ${tbsmColor}B${txtNormal}ased ${tbsmColor}S${txtNormal}ession ${tbsmColor}M${txtNormal}anager (${tbsmColor}${myName}${txtNormal} v${myVersion})" # colSeparator="${txtClean}" # menuSeparator="--------------------------------------------" # menuPrompt="${myName}:" # menuHint="${tbsmColor}Hint:${txtNormal}" # quickPrompt="${myName}:${txtNormal} What's next?" # infoPrefix=" ${tbsmColor}i${txtNormal} ${myName}: " # warnPrefix=" ${colYello}W${txtNormal} ${myName}: " # errorPrefix=" ${colRed}E${txtNormal} ${myName}: " # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ~SUGGESTIONS~ to change something, play around! # Remove the hash # to take effect. # If you read this as "tbsm.conf" then was this extracted from the tbsm # documentation. Try "tbsm doc conf" #XserverArg="-quiet -nolisten tcp" #verboseLevel=1 #colSeparator="${colArchBlue}" #menuSeparator="********************************************" #noSeparator="true" #noMenuHeader="true" #tbsmColor="${colArchBlue}" #promptCol="${txtBold}${colRed}" #menuPrompt="Session or command:" #quickPrompt="Choose session: " #quickPrompt="${myName}: " #quickPrompt=" ? " #quickPrompt=" ? ${txtNormal}${myName}:" #txtNormal=$colBlue #theme="austere" #theme="gently" #theme="riddler" ================================================ FILE: doc/80_ReleaseNotes.txt ================================================ Release notes to tbsm version v0.7, Nov 2022 ============================================== c69bc18 - runSession: detect DBUS_SESSION_ADDRESS Thanks to Victor for this DBUS/Wayland fix 4e8ad87 - Drop not needed option when using ln command d6e8982 - Make check if running in tty more fuzzy ... With these two we try to be more BSD compatible 56ec23b - Use colon as path separator in sessionPfads string CAUTION! if you have used that in your config! 9c3a661 - Manual: Add a hint how to add a shell session to the menu ================================================================================ Previous tbsm version was v0.6, Feb 2022 -------------------------------------------------------------------------------- To view older release notes : tbsm doc log The full changelog is online: https://github.com/loh-tar/tbsm/commits/master ================================================ FILE: doc/81_ReleaseLog.txt ================================================ -------------------------------------------------------------------------------- Release Note History of tbsm The full changelog is online: https://github.com/loh-tar/tbsm/commits/master -------------------------------------------------------------------------------- 2022-11-13 v0.7 ======================================= c69bc18 - runSession: detect DBUS_SESSION_ADDRESS Thanks to Victor for this DBUS/Wayland fix 4e8ad87 - Drop not needed option when using ln command d6e8982 - Make check if running in tty more fuzzy ... With these two we try to be more BSD compatible 56ec23b - Use colon as path separator in sessionPfads string CAUTION! if you have used that in your config! 9c3a661 - Manual: Add a hint how to add a shell session to the menu 2022-02-27 v0.6 ======================================= 031c2a3 - Makefile: Add new target 'update' e7cc113 - Manual: Add Tips&Tricks section Thanks to all who gave a hint afdb675 - Allow configuration of session search paths by conf file Thanks to Douglas 54b0c57 - Support XDG Base Directory Specification Thanks to Vladimir 0f04aa3 - Exit on error in config file. Prior was such an error ignored 9f82e2d - Allow custom Xorg startup file 53d339e - Improved user information in verbose mode - 1a4d9f1 a6e7bcb - Add new doc file ReleaseNotes, rename ChangeLog to ReleaseLog 2018-12-12 v0.5 ======================================= 857fca4 - Add support for Wayland sessions Thanks to Ian for this very welcome patch 2017-10-28 v0.4 ======================================= 682e371 - Only start X session from tty This patch is pretty untested and breaks hopefully no use case cfc4134 - New command exit/X to logout from tty 0422ab7 - Fix broken .desktop checking / parameter building Some session didn't start because of this and the too much parameter d7ddd83 - Don't start a session when not running in tty Treat all in /usr/share/xsessions as such and with Type=XSession e9b76ec - Don't 'exec' terminal programs, just start them Bad idea? daf86d9 - Add display name to XserverArg Done by simply use the tty number as X display name c16c915 - Review verbosity You may notice some more chattering but verboseLevel=2/info should be ok for daily work, neverless level 3/verbose is default now to become familar 2017-07-28 v0.3 ======================================= 7d1b43d - Fix not correct working command line parsing 86b1ae1 - Fix: Bring back menu title 2016-01-22 v0.2 ======================================= 4f68a05 - Fix: Suppress 'which' error message if no binary was found f90a348 - Makefile: Remove license quirk, add source quirk 682fe14 - Fix little typo, thanks to Arda 2016-01-19 v0.1 [YANKED] ======================================= Hello World! Aside from all bling-bling there are some things to note about removed code from CDMs original which may make tbsm not yet a replacement of CDM for you: - There is no search for next empty VT - There is no check if X is already running somewhere - There is no SPAWN set or checked - And maybe something else removed which I forgot to mention so you are warned. But it is getting worse: This very first version is not very well tested! Anyway have fun, Lothar ================================================ FILE: doc/90_ToDo.tx ================================================ Priority ========== H: high M: medium L: low The priority of things to do are quite arbitrary chosen and may not guaranty that things are done in this order not even that they ever be done. You are kindly invited to take one of these points to start your contribution. But you could of cause chose any other, not here listed, issue if you like. H: Think about how to add Xserver arguments individual per session/application. Adding a key to the .desktop file would work H: Add support to give arguments to an application by the run command M: Read and display also the description out of .desktop files with respect to the local language M: Add a variable for indent list items. Now there are two spaces fixed, or even better, a mask to fill by printf with number and name L: Add support for command history by readline L: Add command 'edit' to open theme and .desktop files in users preferred editor. To do so copy the theme file, if needed, from system config to user config directory. To edit a .desktop file create a new one, if needed, in the whitelist directory by grepping only the used keys out of the original file L: Add command '-' to remove an entry from current list. One keystroke less than -b or -w and you have not to mention what are you currently see L: Add command '+' if '-' is implemented to be coherent L: Add command 'list themes' and display author/name/description L: Add individual menu seperators top/middle/bottom and show them individual L: Review theme stuff. Actual there is a crude mix of using. Sometimes the color is in the config var sometimes not. L: Add command 'set' to update/create the config file. 'set v 2' or 'set v silent' will write 'verboseLevel=2' and 't austere' 'theme=austere' to config file where as 'set foo=bar' will update/set foo ================================================ FILE: doc/99_License.txt ================================================ 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 ================================================ FILE: readme-install.txt ================================================ To install from source cd into the downloaded tree and simply run: make Read what is offered and run the command of your choice with root-power. However, should you be on a non GNU system, or using a somehow special distribution which only has a BusyBox, please see there: https://github.com/loh-tar/tbsm/issues/9 After successful install it may wise to run: tbsm doc man # Most notably chapter 3 tbsm help And in case of an update: tbsm doc notes ================================================ FILE: src/.shellcheckrc ================================================ # Verify variable is used # We have so much unused vars, like configured colors. So this check is pesky disable=SC2034 ================================================ FILE: src/tbsm ================================================ #!/bin/bash # # tbsm: Terminal Based Session Manager # # Copyright (C) 2016-2019, 2022 loh.tar@googlemail.com # # Thanks to: # CDM: The Console Display Manager # Copyright (C) 2009-2012, Daniel J Griffiths # Essential parts of tbsm are based on CDMs code. # # t-display-manager # Copyright (C) 2012, 2013 Iru Cai # Taken the idea of linking to files to collect a setup # # All contributors of cdm and tdm, # stackexchange.com, bash-hackers.org, google.com and many more. # Last but not least: Mom & Dad and archlinux.org # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # TODO # Consider http://wiki.bash-hackers.org/howto/collapsing_functions declare -r myName="tbsm" declare -r myLongName="Terminal Based Session Manager" declare -r myVersion="0.7" # Nov 2022 declare -r myDescription="A pure bash session and application launcher" # Let's support XDG Base Directory Specification declare -a systemConfigDirs # Line up in reverse order to apply config files declare -a sessionStartDirs # Line up in normal order to find first configured file oldIFS="$IFS"; IFS=":" for DIR in ${XDG_CONFIG_DIRS:-/etc/xdg} ; do systemConfigDirs=("${DIR}/${myName}" "${systemConfigDirs[@]}") sessionStartDirs=("${sessionStartDirs[@]}" "${DIR}/${myName}") done IFS="$oldIFS" declare -r systemConfigDirs=( "${systemConfigDirs[@]}" ) declare -r sessionStartDirs=( "${sessionStartDirs[@]}" ) declare -r configDir="${XDG_CONFIG_HOME:-${HOME}/.config}/${myName}" declare -r installPfad="" declare sessionPfads="/usr/share/xsessions:/usr/share/wayland-sessions" declare runInTTY="yes" # When unset we have no tty, glas is half full declare XserverArg="@Xdisplay@ -nolisten tcp" # @Xdisplay@ will replaced by number of tty declare protectedVerbose declare lastSession declare -i lastSessionIndex declare defaultSession declare -i defaultSessionIndex declare -a binList=() # fooList filled with keys out of .desktop files declare -a nameList=() declare -a flagList=() declare -a linkList=() declare -a blacklist=() declare -a argList=("$@") # haha! declare command # Used by popCommand. tbsm own command to execute declare argument # Used by popArgument declare -a cmdStack=() # Stack of tbsm own commands declare verboseLevel="3" # 0=quiet, 1=silent, 2=info, 3=verbose # More about colors at Arch Wiki # https://wiki.archlinux.org/index.php/Color_Bash_Prompt # # Don't use double quotes here! # Text attributes declare -r txtClean=$'\e[0m' # All attributes off declare -r txtBold=$'\e[1m' # Or bright, depend on your terminal declare -r txtUScore=$'\e[4m' # Underscore declare -r txtBlink=$'\e[5m' declare -r txtRVideo=$'\e[7m' # Reverse video declare -r txtHide=$'\e[8m' # Concealed, very useful here... declare -r txtWipe=$'\ec' # Clear the screen # Foreground colors only. Someone sicko may miss backgroud colors declare -r colBlack=$'\e[30m' declare -r colRed=$'\e[31m' declare -r colGreen=$'\e[32m' declare -r colYello=$'\e[33m' declare -r colBlue=$'\e[34m' declare -r colMagenta=$'\e[35m' declare -r colArchBlue=$'\e[36m' # Well, someone may call it Cyan declare -r colWhite=$'\e[37m' # Other theme related variables see near end of file, search for 'default theme' pushCommand() { [[ -z "${1}" ]] && return # Push all arguments in reverse order on the stack # https://stackoverflow.com/a/32647351 for ((i = $#; i > 0; i--)); do cmdStack+=("${!i}"); # # # echo "push cmd: ${!i}" done } popCommand() { # Return codes are: # 0 You have data # 1 No more data local last=${#cmdStack[@]}-1 command="" [[ "${last}" -lt 0 ]] && return 1 command="${cmdStack[${last}]}"; cmdStack=("${cmdStack[@]:0:last}") # # # echo "pop cmd: $command" } popArgument() { # Return codes are: # 0 You have data # 1 No more data # We could use popCommand here and test if there is "quit" but then we have to # to re-push it. That's why we code all again local last=${#cmdStack[@]}-1 argument="" [[ "${last}" -lt 0 ]] && return 1 [[ "${cmdStack[${last}]}" == "quit" ]] && return 1 argument="${cmdStack[${last}]}"; cmdStack=("${cmdStack[@]:0:last}") # # # echo "popedArg: $argument" } popListIndex() { # Return codes are: # 0 You have data # 1 No more data # 2 Bad data popArgument || return 1 [[ "${#linkList[@]}" -eq "0" ]] && fillLists if [[ ! ("${argument}" =~ ^[1-9]+[0-9]*$) ]]; then # Not a (valid) number, we assume it's the next command pushCommand "$argument" return 1 elif [[ "$argument" -gt "${#linkList[@]}" ]]; then error "${FUNCNAME[-2]:3}: Selection number too big: ${argument}" return 2 fi listIndex=$((argument-1)) } # http://stackoverflow.com/a/229606 hasOption() { [[ "${argList[*]}" == *"--$1"* ]]; } # It's better not to use echo # http://unix.stackexchange.com/a/65819 print() { local vl=${2:-"0"} [[ ${verboseLevel} -gt $vl ]] && printf "%s\n" "$1"; } info() { local prefix=${infoPrefix:-" ${txtBold}i${txtClean} ${myName}: "} local vl=${2:-"2"} [[ ${verboseLevel} -gt $vl ]] && print "${prefix}$1" >&2 } warn() { local prefix=${warnPrefix:-" ${colYello}W${txtClean} ${myName}: "} local vl=${2:-"1"} [[ ${verboseLevel} -gt $vl ]] && print "${prefix}$1" >&2 } error() { local prefix=${errorPrefix:-" ${colRed}E${txtClean} ${myName}: "} print "${prefix}$1" >&2 } exitNormal() { [[ -n "$1" ]] && print "$*"; exit 0; } exitError() { error "$*"; exit 1; } exitCancel() { error "$*"; exit 2; } checkConfigDir() { [[ -d "${configDir}" ]] && return if ! mkdir -p "${configDir}"/{blacklist,themes,whitelist} 2>/dev/null ; then exitError "Can't create my config dir: ${configDir}" fi info "Created config directory: ${configDir}" } readConfigFile() { local cfgFile="${1:-${myName}.conf}" local -a searchPath=("${systemConfigDirs[@]}" "${configDir}" "$PWD") local -a failPath=() local -a usedPath=() local -i ok=0 # Hint: We can't print information about success/fail in this loop and respect # at the same time some verbose level. That why we collect data and print later for path in "${searchPath[@]}" ; do local fullPath="${path}/${cfgFile}" if [[ ! -r "${fullPath}" ]]; then failPath=("${failPath[@]}" "${fullPath}") continue; fi ok=1 usedPath=("${usedPath[@]}" "${fullPath}") # http://stackoverflow.com/a/20815951 local -i lineNo=0 while IFS='= ' read -r lhs rhs do (( ++lineNo )) if [[ ! $lhs =~ ^\ *# && -n $lhs ]]; then rhs="${rhs%%\#*}" # Del in line right comments rhs="${rhs%"${rhs##*[^ ]}"}" # Del trailing spaces # Without eval does it not works as intended. Because we want use # already known variables in config files too. declare -g "$lhs"="$(eval "echo $rhs")" || exitError "Bad config in file: ${fullPath} line: ${lineNo}" fi done < "${fullPath}" done if (( ! ok )); then error "No config file '${cfgFile##*/}' found" error "+-Searched in: ${failPath[0]%/*}/" for path in "${failPath[@]:1}" ; do error "+------------: ${path%/*}/" done return 1 fi # Restore verbose level given on command line, if some [[ -n "${protectedVerbose}" ]] && verboseLevel="${protectedVerbose}" if [[ ${verboseLevel} -gt "2" ]] ; then info "Searched for config file(s) '${cfgFile##*/}' in ..." for path in "${failPath[@]}" ; do info "- Nothing in: ${path%/*}/" done for path in "${usedPath[@]}" ; do info "+ Utilized : ${path}" done fi } clearLists() { binList=() nameList=() flagList=() linkList=() [[ "$1" != "keepBlack" ]] && blacklist=() } setDefaultAndLast() { clearLists readDesktopFiles "${configDir}" defaultSession="${linkList[0]}" lastSession="${linkList[1]}" } fillBlacklist() { clearLists readDesktopFiles "${configDir}/blacklist" blacklist=("${linkList[@]}") } fillLists() { local -i goodPfads=0 local -r warnOnly="true" setDefaultAndLast fillBlacklist clearLists "keepBlack" for pfad in "${sessionPfads[@]}" ; do info "Look at session path: ${pfad}" readDesktopFiles "$pfad" "$warnOnly" && (( ++goodPfads )) done if [[ $goodPfads -eq "0" ]]; then warn "${FUNCNAME[0]}: No session paths found" fi readDesktopFiles "${configDir}/whitelist" # binList+=("-") # nameList+=("Drop to Shell / Exit") # flagList+=("-") # linkList+=("-") } parseDesktopFiles() { local execKey local nameKey local binItem local realLink local flag local val # TODO: allow full quoting and expansion according to desktop entry spec: # http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables for ((count=0; count < ${#desktopFiles[@]}; count++)); do # Filter blacklisted entries realLink="$(readlink -m "${desktopFiles[${count}]}")" # http://stackoverflow.com/a/15394738 # shellcheck disable=SC2076 # Guess we need the quotes to avoid false positive [[ " ${blacklist[*]} " =~ " ${realLink} " ]] && continue # TryExec key is there to determine if executable is present, # but as we are going to test the Exec key anyway, we ignore it. # http://stackoverflow.com/a/22550813 execKey=$(sed -nr '/^\[Desktop Entry\]/,/^\[/{s/^Exec=//p}' "${desktopFiles[${count}]}") nameKey=$(sed -nr '/^\[Desktop Entry\]/,/^\[/{s/^Name=//p}' "${desktopFiles[${count}]}") # Sadly have only the plasma.desktop file an entry with "Type=XSession", all # other (sorry, the few I have seen) says "Type=Application", so work around this flag="X" if [[ $realLink == *"xsessions"* ]]; then flag="S" # Treat all in /usr/share/xsessions as such elif [[ $realLink == *"wayland-sessions"* ]]; then flag="W" # Treat all in /usr/share/wayland-sessions as such else val=$(sed -nr '/^\[Desktop Entry\]/,/^\[/{s/^Type=//p}' "${desktopFiles[${count}]}") if [[ "${val}" == "XSession" ]]; then flag="S" # Takes action when there is a real local file, not a link else val=$(sed -nr '/^\[Desktop Entry\]/,/^\[/{s/^Terminal=//p}' "${desktopFiles[${count}]}") [[ "${val}" == "true" ]] && flag="C" fi fi if [[ -n ${execKey} && -n ${nameKey} ]]; then # The .desktop files allow there Exec keys to use $PATH lookup. if ! binItem="$(which "${execKey%%[ ]*}" 2>/dev/null)" # If which fails to return valid path, skip to next .desktop file. then warn "Skip '$nameKey' Binary found not: ${execKey%%[ ]*}" continue fi binList+=("${binItem} ${execKey#*"${execKey%%[ ]*}"}") flagList+=("${flag}") # shellcheck disable=SC2076 # Guess we need the quotes to avoid false positive if [[ "$flag" == "W" ]] && [[ " ${nameList[*]} " =~ " ${nameKey} " ]]; then nameList+=("${nameKey} (Wayland)") else nameList+=("${nameKey}") fi linkList+=("${realLink}") fi done } readDesktopFiles() { local filePfad="$1" # e.g. /usr/share/xsessions/ local warnOnly="$2" local -a desktopFiles if [[ -d "${filePfad}" ]]; then # Given -maxdepth 1 to fix trouble at storing default/lastSession # links above blacklist/whitelist directorys # Why use -regex and not -name ? mapfile -t desktopFiles < <(find "${filePfad}" -maxdepth 1 -regex .\*.desktop | sort) parseDesktopFiles else if [[ ${warnOnly} ]]; then warn "${FUNCNAME[0]}: Path not found: ${filePfad}" "2" else error "${FUNCNAME[0]}: Path not found: ${filePfad}" fi return 1 fi } cmdSearch() { local filePfad="/usr/share/applications" local -a desktopFiles if ! popArgument; then error "${FUNCNAME[0]:3} needs a pattern" return 1 fi pattern="$argument" clearLists if [[ -d "${filePfad}" ]]; then mapfile -t desktopFiles < <(grep -Ril --include="*.desktop" "$pattern" "$filePfad" | sort) info "Found matches: ${#desktopFiles[@]}" parseDesktopFiles else error "${FUNCNAME[0]:3}: Pfad not found: ${filePfad}" fi print "Applications matching '$pattern'" printMenuList } printMenuSeparator() { if [[ ! "${noSeparator}" ]]; then print "${colSeparator}${menuSeparator}${txtNormal}" fi } printMenuHeader() { if [[ ! "${noMenuHeader}" ]]; then printMenuSeparator print "${menuTitle}" printMenuSeparator fi } printMenuFooter() { printMenuSeparator; } printMenuList() { if [[ "${#nameList[@]}" == "0" ]]; then print " Nothing to list" return fi for ((count = 0; count < ${#nameList[@]}; count++)); do print " $((count+1)) ${nameList[${count}]}"; done } runSession() { local -i listIndex="$1" local bin=${binList[${listIndex}]} local waylandSessionArgs [[ ${listIndex} -lt 0 ]] && error "Session number too small" && return 1 [[ ${listIndex} -gt ${#nameList[@]}-1 ]] && error "Session number too big" && return 1 info "Run session: ${nameList[listIndex]}" # Run $bin according to its flag. case ${flagList[${listIndex}]} in C) # Console programs info "Run command: ${bin}" eval "${bin}" ;; S) # X Sessions if [[ $runInTTY ]]; then runXSession "${bin}" else info "Not running in tty. Will not start X session." "0" return 1 fi ;; W) # Wayland Sessions if [[ $runInTTY ]]; then if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then waylandSessionArgs+=$(which dbus-run-session 2> /dev/null) fi info "Run command: XDG_SESSION_TYPE=wayland ${waylandSessionArgs:+${waylandSessionArgs[*]} }${bin}" # shellcheck disable=SC2086 # Don't work when we quote $bin XDG_SESSION_TYPE=wayland ${waylandSessionArgs:+${waylandSessionArgs[@]} }${bin} else info "Not running in tty. Will not start Wayland session." "0" return 1 fi ;; X) # Applications if [[ $runInTTY ]]; then runXSession "${bin}" else info "Not running in tty, run: ${bin}" eval "${bin}" fi ;; -) # Old cdm/tdm stuff, not used exitNormal "Have a nice day" ;; *) # Old cdm/tdm stuff, should never happens exitError "Unknown flag: ${flagList[${listIndex}]}" ;; esac # Exit or not. Show full menu if no command left if popCommand then pushCommand "${command}" else pushCommand "menu" fi } runXSession() { # Has the user configured some custom start X file? local -a searchPath=("${configDir}" "${sessionStartDirs[@]}") for path in "${searchPath[@]}"; do local startFile="${path}/start-x" if [[ -x "${startFile}" ]]; then info "Start X by ${startFile}" eval "${startFile} ${bin} -- ${XserverArg}" return fi if [[ -f "${startFile}" ]]; then warn "Not executable ${startFile}" else info "No ${startFile}" fi done # No special start file found, use the build in info "Run command: startx ${bin} -- ${XserverArg}" # shellcheck disable=SC2068,SC2086 # We need the splitting here, or(?) startx ${bin} -- ${XserverArg} } showQuickMenu() { setDefaultAndLast if [[ -z "${defaultSession}" ]]; then pushCommand "menu" return fi local promptText promptText="${promptCol}${quickPrompt}${txtNormal} [*]" [[ -n "${defaultSession}" ]] && promptText="${promptText} [!]${nameList[0]}" [[ -n "${lastSession}" && "${lastSession}" != "${defaultSession}" ]] && promptText="${promptText} [ ]${nameList[1]}" promptText="${promptText} " read -er -i "!" -p "${promptText}" userInput; if [[ "${userInput}" == "!" ]]; then runSession "0" elif [[ -z "${userInput}" ]]; then runSession "1" else # Don't quote here or it will not work as intended # shellcheck disable=SC2086 pushCommand ${userInput#!} fi } showMenu() { printMenuHeader printMenuList printMenuFooter } addToList() { local list="$1" local -i ok=0 local -i err=0 while : do popListIndex case $? in 1) break; ;; # No more left 2) err=1; continue; ;; # Error but we ignore it esac if [[ "${linkList[${listIndex}]}" == "-" ]]; then warn "Session can't ${list}ed: ${nameList[${listIndex}]}" continue fi ln -sf "${linkList[${listIndex}]}" "${configDir}/${list}/" info "Session ${list}ed: ${nameList[${listIndex}]}" "1" ok=1 done if (( ! ok )) ; then # FUNCNAME is a build in bash array. We cut leading 3 char "cmd" error "${FUNCNAME[1]:3} need a valid session number" return 1 fi } removeFromList() { local list="$1" local file local link local -i ok=0 local -i err=0 # Ensure popListIndex works as desired clearLists readDesktopFiles "${configDir}/${list}" while : do popListIndex case $? in 1) break; ;; # No more left 2) err=1; continue; ;; # Error but we ignore it esac for file in "${configDir}/${list}/"* ; do link="$(readlink -m "${file}")" if [[ "${link}" == "${linkList[${listIndex}]}" ]] ; then unlink "$file" info "Session removed from ${list}: ${nameList[${listIndex}]}" "1" fi done ok=1 done if (( ! ok || err )) ; then # FUNCNAME is a build in bash array. We cut leading 3 char "cmd" error "${FUNCNAME[1]:3} needs a valid session number" return 1 fi } cmdBlacklist() { case "$1" in add) addToList "blacklist"; ;; remove) removeFromList "blacklist"; ;; *) exitError "FATAL: Not handeld by cmdBlacklist: $1"; ;; esac } cmdDefault() { popListIndex case $? in 1) error "Command default needs a number"; return 1; ;; 2) return 1; ;; esac if [[ "${linkList[${listIndex}]}" == "-" ]]; then warn "Session can't saved as default: ${nameList[${listIndex}]}" return fi ln -sf "${linkList[${listIndex}]}" "${configDir}/000-default-session.desktop" info "New default session: ${nameList[${listIndex}]}" "1" sleep 1 } cmdDoc() { local doc local docMatch local docPath="/usr/share/doc/tbsm" popArgument mapfile -t docMatch < <(find "${docPath}" -not -type d -iname \*"${argument}"\* | sort) if [[ ${#docMatch[@]} -gt 1 ]]; then print "Available documentation:" for doc in "${docMatch[@]}" ; do doc="${doc#*/??_}" print " ${doc%.*}" done elif (( ${#docMatch[@]} == 0 )) ; then print "No manual match '${argument}'" else less "${docMatch[0]}" fi } cmdHelp() { local tab=$'\t' local ind=" " # indent local showFullHelp="false" popArgument if [[ -z "${argument}" ]]; then showFullHelp="true"; fi if [[ "${showFullHelp}" == "true" ]]; then print "This is ${myName} (v${myVersion}) - ${myDescription}" print "Usage:" print "${ind}${myName} [COMMAND [argument..]] [OPTION [argument..] ..]" print print "COMMANDs are:" else print "Available commands:" fi print "${ind}blacklist, b ${tab}Add/remove entries on the Blacklist. Use -b to remove" print "${ind}default, d ${tab}Save session as the default session" print "${ind}doc [pattern]${tab}${tab}List or show documentation files" print "${ind}exit, X${tab}${tab}Logout from tty or quit" if [[ "${showFullHelp}" == "true" ]]; then print "${ind}help, h${tab}${tab}You read it now" else print "${ind}help, h${tab}${tab}You used '${argument}' so try it for some more info" fi print "${ind}list, l [b|c|w]${tab}List entries. Current, config or black-/whitelist" print "${ind}menu, m${tab}${tab}Show the full session menu" print "${ind}quick-menu, qm${tab}Show the quick menu to select default or last session" print "${ind}quit, q${tab}${tab}Hasta la vista, Baby!" print "${ind}run, r ${tab}${tab}Run the given session number" print "${ind}search, s ${tab}Search an application of your interest" print "${ind}whitelist, w ${tab}Add/remove entries on the Whitelist. Use -w to remove" print "For your convenience there are some short hands: lb lc lw ?" if [[ "${showFullHelp}" == "true" ]]; then print print "OPTIONs are prefixed by a double hyphen, they are:" print "${ind}info ${tab}${tab}Give some feedback what is going on" print "${ind}quiet ${tab}${tab}Shut up!" print "${ind}silent${tab}${tab}Be reticent" print "${ind}theme ${tab}${tab}Use given extra configuration file in /theme/" print "${ind}verbose${tab}${tab}Tell me more" print print "Examples:" print "Run a session whose number you know" print "${ind}${myName} run 3" print "Run with main menu and give a comprehensive feed back" print "${ind}${myName} --verbose" print "Run with qick start menu" print "${ind}${myName} quick-menu" # print "${ind}${myName}" # print "${ind}${tab}" fi } cmdList() { local title popArgument case "${argument}" in b) fillBlacklist title="Black listed entries:" ;; c) fillLists title="Current configuration:" ;; w) clearLists readDesktopFiles "${configDir}/whitelist" title="White listed entries:" ;; *) if [[ "${#nameList[@]}" == "0" ]]; then pushCommand "list" "c" return else title="Currently loaded list:" fi ;; esac # No showMenu here, keep the theme away and list it simple print "${title}" printMenuList } cmdLogout() { if [[ $runInTTY ]]; then # Logout is not easy from inside a script. Can you do it better? myPid=$$ kill -SIGHUP "$(ps -ef | awk '($2=="'$myPid'"){print $3}')" else exitNormal fi } cmdMenu() { clearCommands fillLists showMenu } cmdQMenu() { clearCommands showQuickMenu } cmdRun() { popListIndex case $? in 1) error "Command run needs a number"; return 1; ;; 2) return 1; ;; esac # When user choose 'Drop to Shell' there is no .desktop file to link if [[ "${linkList[${listIndex}]}" != "-" ]]; then # Don't save default as last session if [[ "${defaultSession}" != "${linkList[${listIndex}]}" ]]; then ln -sf "${linkList[${listIndex}]}" "${configDir}/001-last-session.desktop" fi if [[ -z "${defaultSession}" ]]; then pushCommand "$((listIndex+1))" cmdDefault fi fi runSession "${listIndex}" } cmdWhitelist() { case "$1" in add) addToList "whitelist"; ;; remove) removeFromList "whitelist"; ;; *) exitError "FATAL: Not handeld by cmdWhitelist: $1"; ;; esac } parmOfOption() { local -i i=0 while [[ $i < ${#argList[@]} ]] do [[ "${argList[$i]}" == "--$1" ]] && break (( ++i )) done if (( ++i < ${#argList[@]} )); then echo "${argList[$i]}" fi } noMoreCommand() { [[ "${#cmdStack[@]}" == "0" ]]; } clearCommands() { cmdStack=(); } prompt() { local promptText="${promptCol}${menuPrompt}${txtNormal}" while noMoreCommand do # We want to give a hint after a while, but because in meanwhile # the user may write down his command the input would discard. # That's why we wait for the first key stroke # FIXME: With -e is a new line printed after key stroke read -rs -n 1 -t 12 -p "${promptText} " userInput; if [[ $? -gt 128 ]]; then print $'\r'"${menuHint} 1-${#nameList[@]} b d l m qm q r s w X ?" elif [[ -z "${userInput}" ]]; then pushCommand "quick-menu" echo continue elif [[ ! ("$userInput" =~ ^[-a-zX0-9?]) ]]; then userInput="" # Flush keyboard if e.q. cursor key was pressed # http://superuser.com/a/364421 read -rt 0.01 -n 100 printf $'\r' continue fi printf $'\r' # http://stackoverflow.com/a/25000195 read -erp "${promptText} " -i "$userInput" -a userInput ; if (( ${#userInput[@]} == 0 )) ; then pushCommand "quick-menu" elif [[ "${userInput[0]}" =~ ^[1-9]*[0-9]+$ ]]; then pushCommand "run" "${userInput[0]}" # Ignore any input not matched a normal char, hyphen or the ? # Why? Why not? elif [[ "${userInput[0]}" =~ ^(\?|-*|[a-z]+$) ]]; then pushCommand "${userInput[@]}" # FIXME: Find a solution not to print a newline with 'read -e' # and stay on the line. Could be done in a loop reading only one char # like above in our first read step to show the hint at time out #else # printf $'\r' fi done } # # Let's get ready to rumble! # hasOption "verbose" && protectedVerbose=("3") hasOption "info" && protectedVerbose=("2") hasOption "silent" && protectedVerbose=("1") hasOption "quiet" && protectedVerbose=("0") declare -r protectedVerbose # Now he fit his name [[ -n "${protectedVerbose}" ]] && verboseLevel=("${protectedVerbose}") checkConfigDir # Remove silently dead links in case something was deinstalled # https://unix.stackexchange.com/a/38691 find "$configDir" -type l -exec test ! -e {} \; -exec unlink {} \; readConfigFile hasOption "theme" && theme=$(parmOfOption "theme") if [[ -n "${theme}" ]]; then info "Use theme: ${theme}" readConfigFile "themes/${theme}" fi # Convert our path string into an array for nicer handling later IFS=':' read -ra sessionPfads <<< "$sessionPfads" # FIXME: Do you know a way to "re-eval" strings with variables in it so we can # simple 'declare' all these on top of file but have effect if a config # file change e.g. a color? # Our default theme has to be modest, but should showcase the possibilities [[ -z "$txtNormal" ]] && txtNormal="${txtClean}" # The reset key to show ordinary text [[ -z "$tbsmColor" ]] && tbsmColor="${txtBold}" [[ -z "$promptCol" ]] && promptCol="${tbsmColor}" [[ -z "$menuTitle" ]] && menuTitle="${tbsmColor}T${txtNormal}erminal ${tbsmColor}B${txtNormal}ased ${tbsmColor}S${txtNormal}ession ${tbsmColor}M${txtNormal}anager (${tbsmColor}${myName}${txtNormal} v${myVersion})" [[ -z "$colSeparator" ]] && colSeparator="${txtClean}" [[ -z "$menuSeparator" ]] && menuSeparator="--------------------------------------------" [[ -z "$menuPrompt" ]] && menuPrompt="${myName}:" [[ -z "$menuHint" ]] && menuHint="${tbsmColor}Hint:${txtNormal}" [[ -z "$quickPrompt" ]] && quickPrompt="${myName}:${txtNormal} What's next?" [[ -z "$infoPrefix" ]] && infoPrefix=" ${tbsmColor}i${txtNormal} ${myName}: " [[ -z "$warnPrefix" ]] && warnPrefix=" ${colYello}W${txtNormal} ${myName}: " [[ -z "$errorPrefix" ]] && errorPrefix=" ${colRed}E${txtNormal} ${myName}: " # Check if running in tty and set X displaynumber runInTTY=$(tty) if [[ ! "$runInTTY" =~ /dev/tty[a-z]*([0-9]) ]]; then unset runInTTY else # Replace @Xdisplay@ with e.g. :1 XserverArg=${XserverArg/@Xdisplay@/:${BASH_REMATCH[1]}} fi # Special handling to support GNU style help if [[ "${1}" == "--help" ]]; then pushCommand "quit" pushCommand "--help" # If no command given (-z) or starts with "--" show menu elif [[ -z "${1}" || "${1}" == --* ]]; then pushCommand "menu" else pushCommand "quit" # Remove every option from the command line # FIXME: Did you got it without to copy in a help variable? IamToStupid="$*" IamToStupid="${IamToStupid%%--*}" # Don't quote here or it will not work as desired # shellcheck disable=SC2068 pushCommand ${IamToStupid[@]} fi # # Hey! Ho! Let's Go! # while popCommand do case "${command}" in blacklist|b) cmdBlacklist "add" ; ;; -blacklist|-b) cmdBlacklist "remove" ; ;; doc) cmdDoc ; ;; default|d) cmdDefault ; ;; help|--help|h) cmdHelp ; ;; list|l) cmdList ; ;; menu|m) cmdMenu ; ;; quick-menu|qm) cmdQMenu ; ;; quit|q) exitNormal ; ;; run|r) cmdRun ; ;; search|s) cmdSearch ; ;; whitelist|w) cmdWhitelist "add" ; ;; -whitelist|-w) cmdWhitelist "remove" ; ;; exit|X) cmdLogout ; ;; # Short hands \?|-\?|-h) pushCommand "help" "${command}" ; ;; lb) pushCommand "list" "b" ; ;; lw) pushCommand "list" "w" ; ;; lc) pushCommand "list" "c" ; ;; [1-9]|[1-9][0-9]) pushCommand "run" "$command" ; ;; # Try to catch lazy written b w commands [bw][1-9]) pushCommand "${command:0:1}" "${command:1}" ; ;; -[bw][1-9]) pushCommand "${command:0:2}" "${command:2}" ; ;; *) error "Unknown command: ${command}" ; ;; esac prompt done ================================================ FILE: themes/austere ================================================ # CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE # => rename/copy it when you modify something # Name: Austere # Author: loh.tar # Description: Anything but fun noSeparator="true" tbsmColor="${txtClean}" quickPrompt="Choose:" verboseLevel="2" ================================================ FILE: themes/gently ================================================ # CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE # => rename/copy it when you modify something # Name: Gently # Author: loh.tar # Description: Unobtrusive and reticent but not austere noSeparator="true" verboseLevel="2" ================================================ FILE: themes/riddler ================================================ # CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE # => rename/copy it when you modify something # Name: Riddler # Author: loh.tar # Description: From Gotham with love tbsmColor="${colGreen}" txtNormal="${txtClean}" infoPrefix=" ${tbsmColor}i${txtNormal} " warnPrefix=" ${colYello}W${txtNormal} " errorPrefix=" ${colRed}E${txtNormal} " menuPrompt=" ?" menuHint=" ${tbsmColor}!${txtNormal}" quickPrompt=" ?" #quickPrompt=" ? ${myName}:" noSeparator="true" noMenuHeader="true" verboseLevel="2"