[
  {
    "path": ".gitignore",
    "content": "# I suggest you use a global ignore file where you keep a list\n# of typical files to ignore.\n# Add here only pretty tbsm specific files or directories.\n"
  },
  {
    "path": ".kateconfig",
    "content": "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html\n\nkate-wildcard(Makefile): replace-tabs off; remove-trailing-spaces modified;\nkate-wildcard(*.sgml): replace-tabs on; indent-width 2; remove-trailing-spaces modified;\nkate-wildcard(*.md;*.txt): replace-tabs on; indent-width 2; remove-trailing-spaces modified;\nkate-mimetype(text/plain): replace-tabs on; indent-width 2; remove-trailing-spaces modified;\n\nkate: remove-trailing-spaces modified; default-dictionary en_US; replace-tabs on;\n"
  },
  {
    "path": "Makefile",
    "content": "DESTDIR=\nMyName=tbsm\n\n# Thanks to https://stackoverflow.com/a/27132934\nTHIS_FILE := $(lastword $(MAKEFILE_LIST))\n\nall: none\n\nnone:\n\t@echo \"To update of a new version of ${MyName} run 'make update'\"\n\t@echo \"  => That will keep user created files in /etc untouched\"\n\t@echo \"To install, run 'make install'\"\n\t@echo \"To remove every thing, REALLY!?...well, run 'make uninstall' \"\n\ninstall:\n\tinstall -pDm755 -t ${DESTDIR}/usr/bin src/${MyName}\n\tinstall -pDm644 -t ${DESTDIR}/etc/xdg/${MyName}/themes themes/*\n\tinstall -pDm644 -t ${DESTDIR}/usr/share/doc/${MyName} doc/*\n\tln -srf -T ${DESTDIR}/usr/bin/${MyName} ${DESTDIR}/usr/share/doc/${MyName}/\"70_SourceCode\"\n\nupdate:\n\trm -f  ${DESTDIR}/usr/bin/${MyName}\n\trm -rf ${DESTDIR}/usr/share/doc/${MyName}\n\t@# Only remove what we had installed, don't touch user created files\n\t-rm -f ${DESTDIR}/etc/xdg/${MyName}/themes/{austere,gently,riddler}\n\t@# Remove careful none empty directories\n\t-rm -d ${DESTDIR}/etc/xdg/${MyName}/themes\n\t-rm -d ${DESTDIR}/etc/xdg/${MyName}\n\t@echo\n\t@echo \"Hint: If there is some 'error' printed, don't care! Ignore it as done by make\"\n\t@echo\n\t@$(MAKE) -f $(THIS_FILE) install\n\nuninstall:\n\trm -f  ${DESTDIR}/usr/bin/${MyName}\n\trm -rf ${DESTDIR}/etc/xdg/${MyName}\n\trm -rf ${DESTDIR}/usr/share/doc/${MyName}\n\n.PHONY: all none install uninstall update\n"
  },
  {
    "path": "README.md",
    "content": "## Terminal Based Session Manager\n\n**tbsm** is an application or session launcher, written in pure bash with no\nncurses or dialog dependencies. It is inspired by cdm, tdm, in some way by\nkrunner and related.\n\n**Last version is 0.7, Nov 2022**\n\nDetails about **tbsm** can be found in the doc directory and on\nthe [tbsm home page](https://loh-tar.github.io/tbsm/) with some\nscreenshots.\n\n### License\n\nGNU General Public License (GPL), Version 2.0\n"
  },
  {
    "path": "doc/01_Manual.txt",
    "content": "                                                             tbsm v0.7, Nov 2022\nIndex\n=======\n1- Introduction\n2- Things Of Interest\n3- Post Install Tasks\n4- Contact\n5- Thanks To\n6- Copying\n\n\n1- Introduction\n=================\ntbsm is an application or session launcher, written in pure bash with no\nncurses or dialog dependencies. It is inspired by cdm, tdm, in some way by\nkrunner and related.\n\nTo became an overview of available options run:\n  tbsm help\n\nIt was attempted to design the behavior of tbsm to be as less pesky as possible,\nand to start daily tasks with as less key strokes.\n\nTo do so, tbsm saves the last run session and a default session. These both are\navailable by the quick menu where you can start the default session by simply\nhit <enter> or the last session when you delete the bang ! previous. You can run\nthere any command tbsm knows without to remove the bang.\n\nThe last run session will not changed when you choose the default session.\n\nYou reach the quick menu from the full session menu by hitting <enter> on an\nempty prompt. To go back you have to give the needed command which is 'm'.\n\nYou should know that there is not really any special at the 'full session menu'.\nIt is only a pimped out listing of your tbsm configuration where as the commands\n'search' and 'list' show a poor list. In any way you have to use the listed\nindex numbers as argument for any command you like.\n\nThe quick menu on the other hand is a little special but still works with the\nsame intern list, of two entries only, default and last session. You can verify\nthis when you give 'l' at the quick menu prompt.\n\nMay you have a nice day, I run now 'q'\n\n\n2- Things Of Interest\n=======================\nHe only works with .desktop files to start something, there is no other\npossibility. See 4-6- for a custom example.\n\nThe following statements assume a usual configuration. However there will\nXDG_CONFIG_HOME and XDG_CONFIG_DIRS respected.\n\nWhen running tbsm without any argument he is scanning for available .desktop\nfiles in /usr/share/xsessions/ , /usr/share/wayland-sessions and\n$configDir/whitelist but skip any file referenced in $configDir/blacklist.\nWhereas $configDir is ~/.config/tbsm.\n\nThe latter will created if not exist but not filled with default files, do it\nby yourself if needed. The former will searched for themes and the config file\nbut not for white or black listed files.\n\nIn the simplest form you can create a config file like so:\n    echo \"verboseLevel=2\" > ~/.config/tbsm/tbsm.conf\n\nOr perhaps:\n    echo \"theme=austere\" > ~/.config/tbsm/tbsm.conf\n\n\nWhen searching for any pattern he run a grep with the pattern on all .desktop\nfiles in /usr/share/applications/\n\nThe first session ever started became the default session.\n\nThere may some option or command variant available which is not documented. They\nare there to be more comfortable or due to the laziness of the coder.\n\n\n3- Post Install Tasks\n=======================\nTo auto start tbsm after login edit your ~/.bash_profile, or similar, and append\nto the end something like:\n\n  # Auto start tbsm after login on first two VTs\n  [[ $XDG_VTNR -le 2 ]] && tbsm\n\nThis is only an suggestion, there may better ways. Look at section 4- how others\nconfigured their system.\n\nNOTE: Ensure you start no other display manager. Check your distribution's\n      documentation how to disable system services or uninstall packages.\n\n\n3-1- Customizing\n------------------\nTake a look at /etc/xdg/tbsm and subfolders.\nCreate your own theme files in ~/.config/tbsm/themes.\n\nBy default is tbsm a little chattering (--verbose) with the intend you become\nfamiliar with it. But quickly you will be annoyed and therefore have all themes\na setting verboseLevel=\"2\" (--info) which should be good for most cases.\n\nRun the command 'tbsm doc conf' to become an idea what else you can customize.\n\n\n3-2- Special Start X Configuration\n------------------------------------\nBy default will X started this way:\n\n  startx ${bin[@]} -- ${XserverArg[@]}\n\nIf that doesn't full fill your needs, you can create an own startup file in one\nof the config directories. It must be named \"start-x\" and will be called with\nthe three shown arguments, but quoted. As the file is called, it must be\nexecutable. Here the simplest possible example:\n\n  $ cat ~/.config/tbsm/start-x\n  #!/bin/bash\n  # $@ contains: \"${bin} -- ${XserverArg}\" but all splitted, so $2 may \"--\"\n  # but not for sure. $3 is typically \":1\" (display) $4 typically \"-nolisten\"\n  # and so on. Depending on your config settings.\n  #\n  # do something before\n  startx $@\n  # do something after\n\nFor a little more complex example see 4-5-\n\n\n4- Tips & Tricks\n==================\nHere are some hints collected in order as they was reported. Should that list\ngrow in the future I should better enable the Wiki feature on GitHub.\n\n\n4-1- tbsm together with tmux\n------------------------------\nWhen using tmux, tbsm will run with the above mentioned setup in each terminal\nsession. To solve this try the following:\n\n  # Auto start tbsm only on tty1\n  if [[ ! ${DISPLAY} && ${XDG_VTNR} == 1 ]]; then\n      exec tbsm\n  fi\n\nThanks to Anton, https://github.com/loh-tar/tbsm/issues/10\n\n\n4-2- tbsm together with fish shell\n------------------------------------\nAdd to .config/fish/config.fish\n\n  if test \"$DISPLAY\" = \"\" -a \"$XDG_VTNR\" = 1\n        tbsm\n  end\n\nIf $DISPLAY is empty and XDG_VTR equals 1 then execute tbsm.\nWorks with tmux. Test in sway, alacritty.\n\nThanks to Anton, https://github.com/loh-tar/tbsm/issues/10\n\n\n4-3- Prevent exiting out of twm for example\n---------------------------------------------\nInstead of just calling tbsm, \"exec\" it similar to 4-1-\n\n  # Auto start tbsm after login on first two VTs\n  [[ $XDG_VTNR -le 2 ]] && exec tbsm\n\nThanks to toke, https://github.com/loh-tar/tbsm/pull/12\n\n\n4-4- To avoid trouble when also SSH access is needed\n------------------------------------------------------\n\n  [[ -n \"$XDG_VTNR\" && $XDG_VTNR -le 2 ]] && tbsm\n\nThanks to 0BAD-C0DE, https://aur.archlinux.org/packages/tbsm#comment-841043\n\n\n4-5- Redirect Xorg output into custom logfile\n-----------------------------------------------\nWith this redirection will the terminal not fluted with unreadable chatter from\nthe X server or the session. Interesting read! Of cause you can also dump it by\nredirect to /dev/null\n\n  $ cat ~/.config/tbsm/start-x\n  #!/bin/bash\n  ownLogfile=\"/tmp/$USER/tbsm/xorg-chatter.log\"\n  mkdir -p $(dirname $ownLogfile)\n  # We also set an option to improve readability on high resolution displays in\n  # a slightly strange way. Typical we would set XserverArg in the conf file\n  # Just remember: $@ is like \"${bin} -- ${XserverArg}\"\n  startx $@ -dpi 120 2> ${ownLogfile}\n\n\n4-6- Add a shell session to the menu\n--------------------------------------\nIn this example you will be dropped to a bash shell, other shells will have\nsimilar options. When your are done type 'exit' and you came back to tbsm.\n\n  $ cat ~/.config/tbsm/whitelist/bash-session.desktop\n  [Desktop Entry]\n  Name=Bash Session\n  Exec=bash -i\n  Terminal=true\n\nAsked by bee-keeper, https://github.com/loh-tar/tbsm/issues/18\n\n\n5- Contact\n============\nThe home of tbsm\n  https://github.com/loh-tar/tbsm\n\nArch forum talk\n  https://bbs.archlinux.org/viewtopic.php?id=207817\n\nThe author of that all\n  loh.tar@googlemail.com\n\n\n6- Thanks To\n==============\nIan Brunelli <ian@brunelli.me>,\n  not only for adding Wayland support\n\nJan-Luca <build.opensuse.org/users/jlkDE>,\n  for providing an openSUSE package\n\nAll contributors of cdm and tdm,\n  without which I probably would not have started tbsm in the first place\n\nstackexchange.com, bash-hackers.org,\n  for their great knowledge base and tutorials\n\ndict.cc, google.com (till v0.5) and deepl.com (since v0.6),\n  who make my english much more understandable\n\nMany more I have miss to note (sorry!), but not github.com for kindly hosting.\nLast but not least: Mom & Dad and archlinux.org\n\n\n7- Copying\n============\nCopyright (C) 2016-2019, 2022 loh.tar@googlemail.com\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\nMA 02110-1301, USA.\n"
  },
  {
    "path": "doc/60_DefaultConfig.txt",
    "content": "                                                             tbsm v0.6, Feb 2022\n\nDefault Configuration\n=======================\nThis file is written with blood, sweat and tears only for your convenience and\nlists the hard coded defaults with some comments.\n\nYou can use this file as inspiration or starting point for your customization.\nCreate a tbsm.conf file with portions out of this file or extract all of it as\nblue print e.g. by running one of these commands:\n\nFor your personal config:\n  $ tbsm doc conf|sed -n \"/^# ~COMPLETE~/,/^*$/p\" >~/.config/tbsm/tbsm.conf\n\nTo only extract some SUGGESTIONS use it instead of COMPLETE in the command.\n  $ tbsm doc conf|sed -n \"/^# ~SUGGESTIONS~/,/^*$/p\" >~/.config/tbsm/tbsm.conf\n\nAfter that, edit the new file as you like to gain some effect. If you think it\nshould fit as system wide file, move it to /etc/xdg/tbsm/tbsm.conf.\n\nAs ever, these are the usual paths, adjust them to fit your system.\n\nHINT: We don't ship a config file in the system config directory to avoid\ntrouble in case of an update.\n--------------------------------------------------------------------------------\n\n# ~COMPLETE~ Default Configuration\n# If you read this as \"tbsm.conf\" then was this extracted from the tbsm\n# documentation. Try \"tbsm doc conf\"\n# Nothing in this file is enabled by default.\n#\n#\n# None theme related settings:\n#   XserverArg=\"@Xdisplay@ -nolisten tcp\"\n                      # @Xdisplay@ will replaced by number of tty\n#   verboseLevel=\"3\"  # 0=quiet, 1=silent, 2=info, 3=verbose\n#   theme=\"\"          # Ok, it is theme related\n#   sessionPfads=\"/usr/share/xsessions:/usr/share/wayland-sessions\"\n                      # Yeah, looks like a typo, but use it as written :-)\n\n# Available text (foreground) colors and attributes, protected not to change.\n# Use them to modify the look as you like.\n#   txtClean          # All attributes off\n#   txtBold           # Or bright, depend on your terminal\n#   txtUScore         # Underscore\n#   txtBlink\n#   txtRVideo         # Reverse video\n#   txtHide           # Concealed, very useful here...\n#   txtWipe           # Clear the screen\n#   colBlack\n#   colRed\n#   colGreen\n#   colYello\n#   colBlue\n#   colMagenta\n#   colArchBlue       # Well, someone may call it Cyan\n#   colWhite\n\n# These are the variables used to influence the look and feel. You can change\n# them as you like but not use without to set them before. What???\n# Well, they are assigned (or not) after any config file is processed.\n#   txtNormal=\"${txtClean}\" # The reset key to show ordinary text\n#   tbsmColor=\"${txtBold}\"\n#   promptCol=\"${tbsmColor}\"\n#   menuTitle=\"${tbsmColor}T${txtNormal}erminal ${tbsmColor}B${txtNormal}ased ${tbsmColor}S${txtNormal}ession ${tbsmColor}M${txtNormal}anager (${tbsmColor}${myName}${txtNormal} v${myVersion})\"\n#   colSeparator=\"${txtClean}\"\n#   menuSeparator=\"--------------------------------------------\"\n#   menuPrompt=\"${myName}:\"\n#   menuHint=\"${tbsmColor}Hint:${txtNormal}\"\n#   quickPrompt=\"${myName}:${txtNormal} What's next?\"\n\n#   infoPrefix=\" ${tbsmColor}i${txtNormal} ${myName}: \"\n#   warnPrefix=\" ${colYello}W${txtNormal} ${myName}: \"\n#   errorPrefix=\" ${colRed}E${txtNormal} ${myName}: \"\n\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n\n# ~SUGGESTIONS~ to change something, play around!\n# Remove the hash # to take effect.\n# If you read this as \"tbsm.conf\" then was this extracted from the tbsm\n# documentation. Try \"tbsm doc conf\"\n\n#XserverArg=\"-quiet -nolisten tcp\"\n#verboseLevel=1\n\n#colSeparator=\"${colArchBlue}\"\n#menuSeparator=\"********************************************\"\n#noSeparator=\"true\"\n#noMenuHeader=\"true\"\n\n#tbsmColor=\"${colArchBlue}\"\n#promptCol=\"${txtBold}${colRed}\"\n\n#menuPrompt=\"Session or command:\"\n\n#quickPrompt=\"Choose session: \"\n#quickPrompt=\"${myName}: \"\n#quickPrompt=\" ? \"\n#quickPrompt=\" ? ${txtNormal}${myName}:\"\n\n#txtNormal=$colBlue\n\n#theme=\"austere\"\n#theme=\"gently\"\n#theme=\"riddler\"\n"
  },
  {
    "path": "doc/80_ReleaseNotes.txt",
    "content": "Release notes to tbsm version v0.7, Nov 2022\n==============================================\nc69bc18 - runSession: detect DBUS_SESSION_ADDRESS\n          Thanks to Victor for this DBUS/Wayland fix\n4e8ad87 - Drop not needed option when using ln command\nd6e8982 - Make check if running in tty more fuzzy ...\n          With these two we try to be more BSD compatible\n56ec23b - Use colon as path separator in sessionPfads string\n          CAUTION! if you have used that in your config!\n9c3a661 - Manual: Add a hint how to add a shell session to the menu\n================================================================================\nPrevious tbsm version was v0.6, Feb 2022\n--------------------------------------------------------------------------------\nTo view older release notes : tbsm doc log\nThe full changelog is online: https://github.com/loh-tar/tbsm/commits/master\n"
  },
  {
    "path": "doc/81_ReleaseLog.txt",
    "content": "--------------------------------------------------------------------------------\n                         Release Note History of tbsm\n\nThe full changelog is online: https://github.com/loh-tar/tbsm/commits/master\n--------------------------------------------------------------------------------\n\n\n2022-11-13  v0.7\n=======================================\nc69bc18 - runSession: detect DBUS_SESSION_ADDRESS\n          Thanks to Victor for this DBUS/Wayland fix\n4e8ad87 - Drop not needed option when using ln command\nd6e8982 - Make check if running in tty more fuzzy ...\n          With these two we try to be more BSD compatible\n56ec23b - Use colon as path separator in sessionPfads string\n          CAUTION! if you have used that in your config!\n9c3a661 - Manual: Add a hint how to add a shell session to the menu\n\n\n2022-02-27  v0.6\n=======================================\n031c2a3 - Makefile: Add new target 'update'\ne7cc113 - Manual: Add Tips&Tricks section\n          Thanks to all who gave a hint\nafdb675 - Allow configuration of session search paths by conf file\n          Thanks to Douglas\n54b0c57 - Support XDG Base Directory Specification\n          Thanks to Vladimir\n0f04aa3 - Exit on error in config file. Prior was such an error ignored\n9f82e2d - Allow custom Xorg startup file\n53d339e - Improved user information in verbose mode - 1a4d9f1\na6e7bcb - Add new doc file ReleaseNotes, rename ChangeLog to ReleaseLog\n\n\n2018-12-12  v0.5\n=======================================\n857fca4 - Add support for Wayland sessions\n          Thanks to Ian for this very welcome patch\n\n\n2017-10-28  v0.4\n=======================================\n682e371 - Only start X session from tty\n          This patch is pretty untested and breaks hopefully no use case\ncfc4134 - New command exit/X to logout from tty\n0422ab7 - Fix broken .desktop checking / parameter building\n          Some session didn't start because of this and the too much parameter\nd7ddd83 - Don't start a session when not running in tty\n          Treat all in /usr/share/xsessions as such and with Type=XSession\ne9b76ec - Don't 'exec' terminal programs, just start them\n          Bad idea?\ndaf86d9 - Add display name to XserverArg\n          Done by simply use the tty number as X display name\nc16c915 - Review verbosity\n          You may notice some more chattering but verboseLevel=2/info should be\n          ok for daily work, neverless level 3/verbose is default now to become\n          familar\n\n\n2017-07-28  v0.3\n=======================================\n7d1b43d - Fix not correct working command line parsing\n86b1ae1 - Fix: Bring back menu title\n\n\n2016-01-22  v0.2\n=======================================\n4f68a05 - Fix: Suppress 'which' error message if no binary was found\nf90a348 - Makefile: Remove license quirk, add source quirk\n682fe14 - Fix little typo, thanks to Arda\n\n\n2016-01-19  v0.1  [YANKED]\n=======================================\n\n          Hello World!\n\nAside from all bling-bling there are some things to note about removed code from\nCDMs original which may make tbsm not yet a replacement of CDM for you:\n\n- There is no search for next empty VT\n- There is no check if X is already running somewhere\n- There is no SPAWN set or checked\n- And maybe something else removed which I forgot to mention\n\nso you are warned.\nBut it is getting worse: This very first version is not very well tested!\n\nAnyway have fun,\nLothar\n"
  },
  {
    "path": "doc/90_ToDo.tx",
    "content": "Priority\n==========\nH: high\nM: medium\nL: low\n\nThe priority of things to do are quite arbitrary chosen and may not guaranty\nthat things are done in this order not even that they ever be done.\n\nYou are kindly invited to take one of these points to start your contribution.\nBut you could of cause chose any other, not here listed, issue if you like.\n\nH: Think about how to add Xserver arguments individual per session/application.\n   Adding a key to the .desktop file would work\nH: Add support to give arguments to an application by the run command\nM: Read and display also the description out of .desktop files with respect to\n   the local language\nM: Add a variable for indent list items. Now there are two spaces fixed, or even\n   better, a mask to fill by printf with number and name\nL: Add support for command history by readline\nL: Add command 'edit' to open theme and .desktop files in users preferred\n   editor. To do so copy the theme file, if needed, from system config to user\n   config directory. To edit a .desktop file create a new one, if needed, in the\n   whitelist directory by grepping only the used keys out of the original file\nL: Add command '-' to remove an entry from current list. One keystroke less than\n   -b or -w and you have not to mention what are you currently see\nL: Add command '+' if '-' is implemented to be coherent\nL: Add command 'list themes' and display author/name/description\nL: Add individual menu seperators top/middle/bottom and show them individual\nL: Review theme stuff. Actual there is a crude mix of <color><text><color>\n   using. Sometimes the color is in the config var sometimes not.\nL: Add command 'set' to update/create the config file.\n   'set v 2' or 'set v silent' will write 'verboseLevel=2' and 't austere'\n   'theme=austere' to config file where as 'set foo=bar' will update/set foo\n"
  },
  {
    "path": "doc/99_License.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "readme-install.txt",
    "content": "To install from source cd into the downloaded tree and simply run:\n  make\n\nRead what is offered and run the command of your choice with root-power.\n\nHowever, should you be on a non GNU system, or using a somehow special\ndistribution which only has a BusyBox, please see there:\n  https://github.com/loh-tar/tbsm/issues/9\n\nAfter successful install it may wise to run:\n  tbsm doc man    # Most notably chapter 3\n  tbsm help\n\nAnd in case of an update:\n  tbsm doc notes\n"
  },
  {
    "path": "src/.shellcheckrc",
    "content": "# Verify variable is used\n#   We have so much unused vars, like configured colors. So this check is pesky\ndisable=SC2034\n"
  },
  {
    "path": "src/tbsm",
    "content": "#!/bin/bash\n#\n#   tbsm: Terminal Based Session Manager\n#\n#   Copyright (C) 2016-2019, 2022 loh.tar@googlemail.com\n#\n#   Thanks to:\n#     CDM: The Console Display Manager\n#     Copyright (C) 2009-2012, Daniel J Griffiths <dgriffiths@ghost1227.com>\n#     Essential parts of tbsm are based on CDMs code.\n#\n#     t-display-manager\n#     Copyright (C) 2012, 2013 Iru Cai <mytbk920423@gmail.com>\n#     Taken the idea of linking to files to collect a setup\n#\n#     All contributors of cdm and tdm,\n#     stackexchange.com, bash-hackers.org, google.com and many more.\n#     Last but not least: Mom & Dad and archlinux.org\n#\n#   This program is free software; you can redistribute it and/or modify\n#   it under the terms of the GNU General Public License as published by\n#   the Free Software Foundation; either version 2 of the License, or\n#   (at your option) any later version.\n#\n#   This program is distributed in the hope that it will be useful,\n#   but WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#   GNU General Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License\n#   along with this program; if not, write to the Free Software\n#   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,\n#   MA 02110-1301, USA.\n\n# TODO\n#   Consider http://wiki.bash-hackers.org/howto/collapsing_functions\n\ndeclare -r myName=\"tbsm\"\ndeclare -r myLongName=\"Terminal Based Session Manager\"\ndeclare -r myVersion=\"0.7\" # Nov 2022\ndeclare -r myDescription=\"A pure bash session and application launcher\"\n\n# Let's support XDG Base Directory Specification\ndeclare -a systemConfigDirs # Line up in reverse order to apply config files\ndeclare -a sessionStartDirs # Line up in normal order to find first configured file\noldIFS=\"$IFS\"; IFS=\":\"\nfor DIR in ${XDG_CONFIG_DIRS:-/etc/xdg} ; do\n  systemConfigDirs=(\"${DIR}/${myName}\" \"${systemConfigDirs[@]}\")\n  sessionStartDirs=(\"${sessionStartDirs[@]}\" \"${DIR}/${myName}\")\ndone\nIFS=\"$oldIFS\"\ndeclare -r systemConfigDirs=( \"${systemConfigDirs[@]}\" )\ndeclare -r sessionStartDirs=( \"${sessionStartDirs[@]}\" )\ndeclare -r configDir=\"${XDG_CONFIG_HOME:-${HOME}/.config}/${myName}\"\n\ndeclare -r installPfad=\"\"\ndeclare    sessionPfads=\"/usr/share/xsessions:/usr/share/wayland-sessions\"\ndeclare    runInTTY=\"yes\"         # When unset we have no tty, glas is half full\ndeclare    XserverArg=\"@Xdisplay@ -nolisten tcp\"  # @Xdisplay@ will replaced by number of tty\ndeclare    protectedVerbose\ndeclare    lastSession\ndeclare -i lastSessionIndex\ndeclare    defaultSession\ndeclare -i defaultSessionIndex\ndeclare -a binList=()             # fooList filled with keys out of .desktop files\ndeclare -a nameList=()\ndeclare -a flagList=()\ndeclare -a linkList=()\ndeclare -a blacklist=()\ndeclare -a argList=(\"$@\")         # haha!\ndeclare    command                # Used by popCommand. tbsm own command to execute\ndeclare    argument               # Used by popArgument\ndeclare -a cmdStack=()            # Stack of tbsm own commands\ndeclare    verboseLevel=\"3\"       # 0=quiet, 1=silent, 2=info, 3=verbose\n\n# More about colors at Arch Wiki\n# https://wiki.archlinux.org/index.php/Color_Bash_Prompt\n#\n# Don't use double quotes here!\n# Text attributes\ndeclare -r txtClean=$'\\e[0m'      # All attributes off\ndeclare -r txtBold=$'\\e[1m'       # Or bright, depend on your terminal\ndeclare -r txtUScore=$'\\e[4m'     # Underscore\ndeclare -r txtBlink=$'\\e[5m'\ndeclare -r txtRVideo=$'\\e[7m'     # Reverse video\ndeclare -r txtHide=$'\\e[8m'       # Concealed, very useful here...\ndeclare -r txtWipe=$'\\ec'         # Clear the screen\n\n# Foreground colors only. Someone sicko may miss backgroud colors\ndeclare -r colBlack=$'\\e[30m'\ndeclare -r colRed=$'\\e[31m'\ndeclare -r colGreen=$'\\e[32m'\ndeclare -r colYello=$'\\e[33m'\ndeclare -r colBlue=$'\\e[34m'\ndeclare -r colMagenta=$'\\e[35m'\ndeclare -r colArchBlue=$'\\e[36m'  # Well, someone may call it Cyan\ndeclare -r colWhite=$'\\e[37m'\n# Other theme related variables see near end of file, search for 'default theme'\n\npushCommand() {\n  [[ -z \"${1}\" ]] && return\n\n  # Push all arguments in reverse order on the stack\n  # https://stackoverflow.com/a/32647351\n  for ((i = $#; i > 0; i--)); do\n    cmdStack+=(\"${!i}\");\n# # #     echo \"push cmd: ${!i}\"\n  done\n}\n\npopCommand() {\n  # Return codes are:\n  #   0 You have data\n  #   1 No more data\n\n  local last=${#cmdStack[@]}-1\n\n  command=\"\"\n\n  [[ \"${last}\" -lt 0 ]] && return 1\n\n  command=\"${cmdStack[${last}]}\";\n  cmdStack=(\"${cmdStack[@]:0:last}\")\n# # #   echo \"pop  cmd: $command\"\n}\n\npopArgument() {\n  # Return codes are:\n  #   0 You have data\n  #   1 No more data\n\n  # We could use popCommand here and test if there is \"quit\" but then we have to\n  # to re-push it. That's why we code all again\n\n  local last=${#cmdStack[@]}-1\n\n  argument=\"\"\n\n  [[ \"${last}\" -lt 0 ]] && return 1\n  [[ \"${cmdStack[${last}]}\" == \"quit\" ]] && return 1\n\n  argument=\"${cmdStack[${last}]}\";\n  cmdStack=(\"${cmdStack[@]:0:last}\")\n# # #   echo \"popedArg: $argument\"\n}\n\npopListIndex() {\n  # Return codes are:\n  #   0 You have data\n  #   1 No more data\n  #   2 Bad data\n\n  popArgument || return 1\n\n  [[ \"${#linkList[@]}\" -eq \"0\" ]] && fillLists\n\n  if [[ ! (\"${argument}\" =~ ^[1-9]+[0-9]*$) ]]; then\n    # Not a (valid) number, we assume it's the next command\n    pushCommand \"$argument\"\n    return 1\n  elif [[ \"$argument\" -gt \"${#linkList[@]}\" ]]; then\n    error \"${FUNCNAME[-2]:3}: Selection number too big: ${argument}\"\n    return 2\n  fi\n\n  listIndex=$((argument-1))\n}\n\n# http://stackoverflow.com/a/229606\nhasOption() { [[ \"${argList[*]}\" == *\"--$1\"* ]]; }\n\n# It's better not to use echo\n# http://unix.stackexchange.com/a/65819\nprint() {\n  local vl=${2:-\"0\"}\n  [[ ${verboseLevel} -gt $vl ]] && printf \"%s\\n\" \"$1\";\n}\n\ninfo() {\n  local prefix=${infoPrefix:-\" ${txtBold}i${txtClean} ${myName}: \"}\n  local vl=${2:-\"2\"}\n  [[ ${verboseLevel} -gt $vl ]] && print \"${prefix}$1\" >&2\n}\n\nwarn() {\n  local prefix=${warnPrefix:-\" ${colYello}W${txtClean} ${myName}: \"}\n  local vl=${2:-\"1\"}\n  [[ ${verboseLevel} -gt $vl ]] && print \"${prefix}$1\" >&2\n}\n\nerror() {\n  local prefix=${errorPrefix:-\" ${colRed}E${txtClean} ${myName}: \"}\n  print \"${prefix}$1\" >&2\n}\n\nexitNormal() { [[ -n \"$1\" ]] && print \"$*\"; exit 0; }\nexitError()  { error \"$*\"; exit 1; }\nexitCancel() { error \"$*\"; exit 2; }\n\ncheckConfigDir() {\n  [[ -d \"${configDir}\" ]] && return\n\n  if ! mkdir -p \"${configDir}\"/{blacklist,themes,whitelist} 2>/dev/null ; then\n    exitError \"Can't create my config dir: ${configDir}\"\n  fi\n\n  info \"Created config directory: ${configDir}\"\n}\n\nreadConfigFile() {\n  local    cfgFile=\"${1:-${myName}.conf}\"\n  local -a searchPath=(\"${systemConfigDirs[@]}\" \"${configDir}\" \"$PWD\")\n  local -a failPath=()\n  local -a usedPath=()\n  local -i ok=0\n\n  # Hint: We can't print information about success/fail in this loop and respect\n  # at the same time some verbose level. That why we collect data and print later\n  for path in \"${searchPath[@]}\" ; do\n    local fullPath=\"${path}/${cfgFile}\"\n    if [[ ! -r \"${fullPath}\" ]]; then\n      failPath=(\"${failPath[@]}\" \"${fullPath}\")\n      continue;\n    fi\n    ok=1\n    usedPath=(\"${usedPath[@]}\" \"${fullPath}\")\n\n    # http://stackoverflow.com/a/20815951\n    local -i lineNo=0\n    while IFS='= ' read -r lhs rhs\n    do\n      (( ++lineNo ))\n      if [[ ! $lhs =~ ^\\ *# && -n $lhs ]]; then\n        rhs=\"${rhs%%\\#*}\"             # Del in line right comments\n        rhs=\"${rhs%\"${rhs##*[^ ]}\"}\"  # Del trailing spaces\n\n        # Without eval does it not works as intended. Because we want use\n        # already known variables in config files too.\n        declare -g \"$lhs\"=\"$(eval \"echo $rhs\")\" || exitError \"Bad config in file: ${fullPath} line: ${lineNo}\"\n      fi\n    done < \"${fullPath}\"\n\n  done\n\n  if (( ! ok )); then\n    error \"No config file '${cfgFile##*/}' found\"\n    error \"+-Searched in: ${failPath[0]%/*}/\"\n    for path in \"${failPath[@]:1}\" ; do\n      error \"+------------: ${path%/*}/\"\n    done\n    return 1\n  fi\n\n  # Restore verbose level given on command line, if some\n  [[ -n \"${protectedVerbose}\" ]] && verboseLevel=\"${protectedVerbose}\"\n\n  if [[ ${verboseLevel} -gt \"2\" ]] ; then\n    info \"Searched for config file(s) '${cfgFile##*/}' in ...\"\n    for path in \"${failPath[@]}\" ; do\n      info \"- Nothing in: ${path%/*}/\"\n    done\n    for path in \"${usedPath[@]}\" ; do\n      info \"+ Utilized  : ${path}\"\n    done\n  fi\n}\n\nclearLists() {\n  binList=()\n  nameList=()\n  flagList=()\n  linkList=()\n  [[ \"$1\" != \"keepBlack\" ]] && blacklist=()\n}\n\nsetDefaultAndLast() {\n  clearLists\n  readDesktopFiles \"${configDir}\"\n  defaultSession=\"${linkList[0]}\"\n  lastSession=\"${linkList[1]}\"\n}\n\nfillBlacklist() {\n  clearLists\n  readDesktopFiles \"${configDir}/blacklist\"\n  blacklist=(\"${linkList[@]}\")\n}\n\nfillLists() {\n  local -i goodPfads=0\n  local -r warnOnly=\"true\"\n\n  setDefaultAndLast\n  fillBlacklist\n  clearLists \"keepBlack\"\n\n  for pfad in \"${sessionPfads[@]}\" ; do\n    info \"Look at session path: ${pfad}\"\n    readDesktopFiles \"$pfad\" \"$warnOnly\" && (( ++goodPfads ))\n  done\n  if [[ $goodPfads -eq  \"0\" ]]; then\n    warn \"${FUNCNAME[0]}: No session paths found\"\n  fi\n\n  readDesktopFiles \"${configDir}/whitelist\"\n\n#   binList+=(\"-\")\n#   nameList+=(\"Drop to Shell / Exit\")\n#   flagList+=(\"-\")\n#   linkList+=(\"-\")\n}\n\nparseDesktopFiles() {\n  local execKey\n  local nameKey\n  local binItem\n  local realLink\n  local flag\n  local val\n\n  # TODO: allow full quoting and expansion according to desktop entry spec:\n  # http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables\n\n  for ((count=0; count < ${#desktopFiles[@]}; count++)); do\n    # Filter blacklisted entries\n    realLink=\"$(readlink -m \"${desktopFiles[${count}]}\")\"\n    # http://stackoverflow.com/a/15394738\n    # shellcheck disable=SC2076 # Guess we need the quotes to avoid false positive\n    [[ \" ${blacklist[*]} \" =~ \" ${realLink} \" ]] && continue\n\n    # TryExec key is there to determine if executable is present,\n    # but as we are going to test the Exec key anyway, we ignore it.\n    # http://stackoverflow.com/a/22550813\n    execKey=$(sed -nr '/^\\[Desktop Entry\\]/,/^\\[/{s/^Exec=//p}' \"${desktopFiles[${count}]}\")\n    nameKey=$(sed -nr '/^\\[Desktop Entry\\]/,/^\\[/{s/^Name=//p}' \"${desktopFiles[${count}]}\")\n    # Sadly have only the plasma.desktop file an entry with \"Type=XSession\", all\n    # other (sorry, the few I have seen) says \"Type=Application\", so work around this\n    flag=\"X\"\n    if [[ $realLink == *\"xsessions\"* ]]; then\n      flag=\"S\"    # Treat all in /usr/share/xsessions as such\n    elif [[ $realLink == *\"wayland-sessions\"* ]]; then\n      flag=\"W\"    # Treat all in /usr/share/wayland-sessions as such\n    else\n      val=$(sed -nr '/^\\[Desktop Entry\\]/,/^\\[/{s/^Type=//p}' \"${desktopFiles[${count}]}\")\n      if [[ \"${val}\" == \"XSession\" ]]; then\n        flag=\"S\"  # Takes action when there is a real local file, not a link\n      else\n        val=$(sed -nr '/^\\[Desktop Entry\\]/,/^\\[/{s/^Terminal=//p}' \"${desktopFiles[${count}]}\")\n        [[ \"${val}\" == \"true\" ]] && flag=\"C\"\n      fi\n    fi\n    if [[ -n ${execKey} && -n ${nameKey} ]]; then\n      # The .desktop files allow there Exec keys to use $PATH lookup.\n      if ! binItem=\"$(which \"${execKey%%[ ]*}\" 2>/dev/null)\"\n      # If which fails to return valid path, skip to next .desktop file.\n        then\n        warn \"Skip '$nameKey' Binary found not: ${execKey%%[ ]*}\"\n        continue\n      fi\n      binList+=(\"${binItem} ${execKey#*\"${execKey%%[ ]*}\"}\")\n      flagList+=(\"${flag}\")\n      # shellcheck disable=SC2076 # Guess we need the quotes to avoid false positive\n      if [[ \"$flag\" == \"W\" ]] && [[ \" ${nameList[*]} \" =~ \" ${nameKey} \" ]]; then\n          nameList+=(\"${nameKey} (Wayland)\")\n      else\n          nameList+=(\"${nameKey}\")\n      fi\n      linkList+=(\"${realLink}\")\n    fi\n  done\n}\n\nreadDesktopFiles() {\n  local    filePfad=\"$1\"  # e.g. /usr/share/xsessions/\n  local    warnOnly=\"$2\"\n  local -a desktopFiles\n\n  if [[ -d \"${filePfad}\" ]]; then\n    # Given -maxdepth 1 to fix trouble at storing default/lastSession\n    # links above blacklist/whitelist directorys\n    # Why use -regex and not -name ?\n    mapfile -t desktopFiles < <(find \"${filePfad}\" -maxdepth 1 -regex .\\*.desktop | sort)\n    parseDesktopFiles\n  else\n    if [[ ${warnOnly} ]]; then\n      warn \"${FUNCNAME[0]}: Path not found: ${filePfad}\" \"2\"\n    else\n      error \"${FUNCNAME[0]}: Path not found: ${filePfad}\"\n    fi\n    return 1\n  fi\n}\n\ncmdSearch() {\n  local    filePfad=\"/usr/share/applications\"\n  local -a desktopFiles\n\n  if ! popArgument; then\n    error \"${FUNCNAME[0]:3} needs a pattern\"\n    return 1\n  fi\n  pattern=\"$argument\"\n\n  clearLists\n\n  if [[ -d \"${filePfad}\" ]]; then\n    mapfile -t desktopFiles < <(grep -Ril --include=\"*.desktop\" \"$pattern\" \"$filePfad\" | sort)\n    info \"Found matches: ${#desktopFiles[@]}\"\n    parseDesktopFiles\n  else\n    error \"${FUNCNAME[0]:3}: Pfad not found: ${filePfad}\"\n  fi\n\n  print \"Applications matching '$pattern'\"\n  printMenuList\n}\n\nprintMenuSeparator() {\n  if [[ ! \"${noSeparator}\" ]]; then\n    print \"${colSeparator}${menuSeparator}${txtNormal}\"\n  fi\n}\n\nprintMenuHeader() {\n  if [[ ! \"${noMenuHeader}\" ]]; then\n    printMenuSeparator\n    print \"${menuTitle}\"\n    printMenuSeparator\n  fi\n}\n\nprintMenuFooter() { printMenuSeparator; }\n\nprintMenuList() {\n  if [[ \"${#nameList[@]}\" == \"0\" ]]; then\n    print \"  Nothing to list\"\n    return\n  fi\n\n  for ((count = 0; count < ${#nameList[@]}; count++)); do\n    print \"  $((count+1)) ${nameList[${count}]}\";\n  done\n}\n\nrunSession() {\n  local -i listIndex=\"$1\"\n  local    bin=${binList[${listIndex}]}\n  local    waylandSessionArgs\n\n  [[ ${listIndex} -lt 0 ]] && error \"Session number too small\" && return 1\n  [[ ${listIndex} -gt ${#nameList[@]}-1 ]] && error \"Session number too big\" && return 1\n\n  info \"Run session: ${nameList[listIndex]}\"\n\n  # Run $bin according to its flag.\n  case ${flagList[${listIndex}]} in\n    C)  # Console programs\n      info \"Run command: ${bin}\"\n      eval \"${bin}\"\n      ;;\n    S)  # X Sessions\n      if [[ $runInTTY ]]; then\n        runXSession \"${bin}\"\n      else\n        info \"Not running in tty. Will not start X session.\" \"0\"\n        return 1\n      fi\n      ;;\n    W) # Wayland Sessions\n      if [[ $runInTTY ]]; then\n        if [ -z \"$DBUS_SESSION_BUS_ADDRESS\" ]; then\n            waylandSessionArgs+=$(which dbus-run-session 2> /dev/null)\n        fi\n        info \"Run command: XDG_SESSION_TYPE=wayland ${waylandSessionArgs:+${waylandSessionArgs[*]} }${bin}\"\n        # shellcheck disable=SC2086 # Don't work when we quote $bin\n        XDG_SESSION_TYPE=wayland ${waylandSessionArgs:+${waylandSessionArgs[@]} }${bin}\n      else\n        info \"Not running in tty. Will not start Wayland session.\" \"0\"\n        return 1\n      fi\n      ;;\n    X)  # Applications\n      if [[ $runInTTY ]]; then\n        runXSession \"${bin}\"\n      else\n        info \"Not running in tty, run: ${bin}\"\n        eval \"${bin}\"\n      fi\n      ;;\n    -)  # Old cdm/tdm stuff, not used\n      exitNormal \"Have a nice day\"\n      ;;\n    *)  # Old cdm/tdm stuff, should never happens\n      exitError \"Unknown flag: ${flagList[${listIndex}]}\"\n      ;;\n  esac\n\n  # Exit or not. Show full menu if no command left\n  if popCommand\n    then pushCommand \"${command}\"\n    else pushCommand \"menu\"\n  fi\n}\n\nrunXSession() {\n  # Has the user configured some custom start X file?\n  local -a searchPath=(\"${configDir}\" \"${sessionStartDirs[@]}\")\n  for path in \"${searchPath[@]}\"; do\n    local startFile=\"${path}/start-x\"\n    if [[ -x \"${startFile}\" ]]; then\n      info \"Start X by ${startFile}\"\n      eval \"${startFile} ${bin} -- ${XserverArg}\"\n      return\n    fi\n    if [[ -f \"${startFile}\" ]]; then\n      warn \"Not executable ${startFile}\"\n    else\n      info \"No ${startFile}\"\n    fi\n  done\n\n  # No special start file found, use the build in\n  info \"Run command: startx ${bin} -- ${XserverArg}\"\n  # shellcheck disable=SC2068,SC2086 # We need the splitting here, or(?)\n  startx ${bin} -- ${XserverArg}\n}\n\nshowQuickMenu() {\n  setDefaultAndLast\n\n  if [[ -z \"${defaultSession}\" ]]; then\n    pushCommand \"menu\"\n    return\n  fi\n\n  local promptText\n  promptText=\"${promptCol}${quickPrompt}${txtNormal} [*]\"\n  [[ -n \"${defaultSession}\" ]] && promptText=\"${promptText}  [!]${nameList[0]}\"\n  [[ -n \"${lastSession}\" && \"${lastSession}\" != \"${defaultSession}\" ]] && promptText=\"${promptText} [ ]${nameList[1]}\"\n\n  promptText=\"${promptText} \"\n\n  read -er -i \"!\" -p \"${promptText}\" userInput;\n\n  if [[ \"${userInput}\" == \"!\" ]]; then runSession \"0\"\n  elif [[ -z \"${userInput}\" ]]; then runSession \"1\"\n  else\n    # Don't quote here or it will not work as intended\n    # shellcheck disable=SC2086\n    pushCommand ${userInput#!}\n  fi\n}\n\nshowMenu() {\n  printMenuHeader\n  printMenuList\n  printMenuFooter\n}\n\naddToList() {\n  local    list=\"$1\"\n  local -i ok=0\n  local -i err=0\n\n  while :\n  do\n    popListIndex\n    case $? in\n      1) break; ;;           # No more left\n      2) err=1; continue; ;; # Error but we ignore it\n    esac\n\n    if [[ \"${linkList[${listIndex}]}\" == \"-\" ]]; then\n      warn \"Session can't ${list}ed: ${nameList[${listIndex}]}\"\n      continue\n    fi\n\n    ln -sf \"${linkList[${listIndex}]}\" \"${configDir}/${list}/\"\n    info \"Session ${list}ed: ${nameList[${listIndex}]}\" \"1\"\n    ok=1\n  done\n\n  if (( ! ok )) ; then\n    # FUNCNAME is a build in bash array. We cut leading 3 char \"cmd\"\n    error \"${FUNCNAME[1]:3} need a valid session number\"\n    return 1\n  fi\n}\n\nremoveFromList() {\n  local    list=\"$1\"\n  local    file\n  local    link\n  local -i ok=0\n  local -i err=0\n\n  # Ensure popListIndex works as desired\n  clearLists\n  readDesktopFiles \"${configDir}/${list}\"\n\n  while :\n  do\n    popListIndex\n    case $? in\n      1) break; ;;           # No more left\n      2) err=1; continue; ;; # Error but we ignore it\n    esac\n\n    for file in \"${configDir}/${list}/\"* ; do\n      link=\"$(readlink -m \"${file}\")\"\n      if [[ \"${link}\" == \"${linkList[${listIndex}]}\" ]] ; then\n        unlink \"$file\"\n        info \"Session removed from ${list}: ${nameList[${listIndex}]}\" \"1\"\n      fi\n    done\n    ok=1\n\n  done\n\n  if (( ! ok ||  err )) ; then\n    # FUNCNAME is a build in bash array. We cut leading 3 char \"cmd\"\n    error \"${FUNCNAME[1]:3} needs a valid session number\"\n    return 1\n  fi\n}\n\ncmdBlacklist() {\n  case \"$1\" in\n    add)    addToList \"blacklist\"; ;;\n    remove) removeFromList \"blacklist\"; ;;\n    *)      exitError \"FATAL: Not handeld by cmdBlacklist: $1\"; ;;\n  esac\n}\n\ncmdDefault() {\n  popListIndex\n  case $? in\n    1) error \"Command default needs a number\"; return 1; ;;\n    2) return 1; ;;\n  esac\n\n  if [[ \"${linkList[${listIndex}]}\" == \"-\" ]]; then\n    warn \"Session can't saved as default: ${nameList[${listIndex}]}\"\n    return\n  fi\n\n  ln -sf \"${linkList[${listIndex}]}\" \"${configDir}/000-default-session.desktop\"\n  info \"New default session: ${nameList[${listIndex}]}\" \"1\"\n  sleep 1\n}\n\ncmdDoc() {\n  local doc\n  local docMatch\n  local docPath=\"/usr/share/doc/tbsm\"\n\n  popArgument\n  mapfile -t docMatch < <(find \"${docPath}\" -not -type d -iname \\*\"${argument}\"\\* | sort)\n\n  if [[ ${#docMatch[@]} -gt 1 ]]; then\n    print \"Available documentation:\"\n    for doc in \"${docMatch[@]}\" ; do\n      doc=\"${doc#*/??_}\"\n      print \"  ${doc%.*}\"\n    done\n  elif (( ${#docMatch[@]} == 0 )) ; then\n      print \"No manual match '${argument}'\"\n  else\n   less \"${docMatch[0]}\"\n  fi\n}\n\ncmdHelp() {\n  local tab=$'\\t'\n  local ind=\"  \" # indent\n  local showFullHelp=\"false\"\n\n  popArgument\n\n  if [[ -z \"${argument}\" ]]; then\n    showFullHelp=\"true\";\n  fi\n\n  if [[ \"${showFullHelp}\" == \"true\" ]]; then\n    print \"This is ${myName} (v${myVersion}) - ${myDescription}\"\n    print \"Usage:\"\n    print \"${ind}${myName} [COMMAND [argument..]] [OPTION [argument..] ..]\"\n    print\n    print \"COMMANDs are:\"\n  else\n    print \"Available commands:\"\n  fi\n    print \"${ind}blacklist, b <n..>${tab}Add/remove entries on the Blacklist. Use -b to remove\"\n    print \"${ind}default, d <n>${tab}Save session as the default session\"\n    print \"${ind}doc [pattern]${tab}${tab}List or show documentation files\"\n    print \"${ind}exit, X${tab}${tab}Logout from tty or quit\"\n  if [[ \"${showFullHelp}\" == \"true\" ]]; then\n    print \"${ind}help, h${tab}${tab}You read it now\"\n  else\n    print \"${ind}help, h${tab}${tab}You used '${argument}' so try it for some more info\"\n  fi\n    print \"${ind}list, l [b|c|w]${tab}List entries. Current, config or black-/whitelist\"\n    print \"${ind}menu, m${tab}${tab}Show the full session menu\"\n    print \"${ind}quick-menu, qm${tab}Show the quick menu to select default or last session\"\n    print \"${ind}quit, q${tab}${tab}Hasta la vista, Baby!\"\n    print \"${ind}run, r <n>${tab}${tab}Run the given session number\"\n    print \"${ind}search, s <pattern>${tab}Search an application of your interest\"\n    print \"${ind}whitelist, w <n..>${tab}Add/remove entries on the Whitelist. Use -w to remove\"\n    print \"For your convenience there are some short hands: lb lc lw <n> ?\"\n  if [[ \"${showFullHelp}\" == \"true\" ]]; then\n    print\n    print \"OPTIONs are prefixed by a double hyphen, they are:\"\n    print \"${ind}info  ${tab}${tab}Give some feedback what is going on\"\n    print \"${ind}quiet ${tab}${tab}Shut up!\"\n    print \"${ind}silent${tab}${tab}Be reticent\"\n    print \"${ind}theme <name>${tab}${tab}Use given extra configuration file in <configDir>/theme/\"\n    print \"${ind}verbose${tab}${tab}Tell me more\"\n    print\n    print \"Examples:\"\n    print \"Run a session whose number you know\"\n    print \"${ind}${myName} run 3\"\n    print \"Run with main menu and give a comprehensive feed back\"\n    print \"${ind}${myName} --verbose\"\n    print \"Run with qick start menu\"\n    print \"${ind}${myName} quick-menu\"\n    #       print \"${ind}${myName}\"\n    #       print \"${ind}${tab}\"\n  fi\n}\n\ncmdList() {\n  local title\n\n  popArgument\n  case \"${argument}\" in\n    b)\n      fillBlacklist\n      title=\"Black listed entries:\"\n    ;;\n    c)\n      fillLists\n      title=\"Current configuration:\"\n    ;;\n    w)\n      clearLists\n      readDesktopFiles \"${configDir}/whitelist\"\n      title=\"White listed entries:\"\n    ;;\n    *)\n      if [[ \"${#nameList[@]}\" == \"0\" ]]; then\n        pushCommand \"list\" \"c\"\n        return\n      else\n        title=\"Currently loaded list:\"\n      fi\n    ;;\n  esac\n\n  # No showMenu here, keep the theme away and list it simple\n  print \"${title}\"\n  printMenuList\n}\n\ncmdLogout() {\n  if [[ $runInTTY ]]; then\n    # Logout is not easy from inside a script. Can you do it better?\n    myPid=$$\n    kill -SIGHUP \"$(ps -ef | awk '($2==\"'$myPid'\"){print $3}')\"\n  else\n    exitNormal\n  fi\n}\n\ncmdMenu() {\n  clearCommands\n  fillLists\n  showMenu\n}\n\ncmdQMenu() {\n  clearCommands\n  showQuickMenu\n}\n\ncmdRun() {\n  popListIndex\n  case $? in\n    1) error \"Command run needs a number\"; return 1; ;;\n    2) return 1; ;;\n  esac\n\n  # When user choose 'Drop to Shell' there is no .desktop file to link\n  if [[ \"${linkList[${listIndex}]}\" != \"-\" ]]; then\n    # Don't save default as last session\n    if [[ \"${defaultSession}\" !=  \"${linkList[${listIndex}]}\" ]]; then\n      ln -sf \"${linkList[${listIndex}]}\" \"${configDir}/001-last-session.desktop\"\n    fi\n\n    if [[ -z \"${defaultSession}\" ]]; then\n      pushCommand \"$((listIndex+1))\"\n      cmdDefault\n    fi\n  fi\n\n  runSession \"${listIndex}\"\n}\n\ncmdWhitelist() {\n  case \"$1\" in\n    add)    addToList \"whitelist\"; ;;\n    remove) removeFromList \"whitelist\"; ;;\n    *)      exitError \"FATAL: Not handeld by cmdWhitelist: $1\"; ;;\n  esac\n}\n\nparmOfOption() {\n  local -i i=0\n\n  while\n    [[ $i < ${#argList[@]} ]]\n  do\n    [[ \"${argList[$i]}\" == \"--$1\" ]] && break\n    (( ++i ))\n  done\n\n  if (( ++i < ${#argList[@]} )); then\n    echo \"${argList[$i]}\"\n  fi\n}\n\nnoMoreCommand() { [[ \"${#cmdStack[@]}\" == \"0\" ]]; }\nclearCommands() { cmdStack=(); }\n\nprompt() {\n  local promptText=\"${promptCol}${menuPrompt}${txtNormal}\"\n\n  while noMoreCommand\n  do\n    # We want to give a hint after a while, but because in meanwhile\n    # the user may write down his command the input would discard.\n    # That's why we wait for the first key stroke\n    # FIXME: With -e is a new line printed after key stroke\n    read -rs -n 1 -t 12 -p \"${promptText} \" userInput;\n    if [[ $? -gt 128 ]]; then\n      print $'\\r'\"${menuHint} 1-${#nameList[@]} b d l m qm q r s w X ?\"\n    elif [[ -z \"${userInput}\" ]]; then\n      pushCommand \"quick-menu\"\n      echo\n      continue\n    elif [[ ! (\"$userInput\" =~ ^[-a-zX0-9?]) ]]; then\n      userInput=\"\"\n      # Flush keyboard if e.q. cursor key was pressed\n      # http://superuser.com/a/364421\n      read -rt 0.01 -n 100\n      printf $'\\r'\n      continue\n    fi\n\n    printf $'\\r'\n    # http://stackoverflow.com/a/25000195\n    read -erp \"${promptText} \" -i \"$userInput\" -a userInput ;\n\n    if (( ${#userInput[@]} == 0 )) ; then\n      pushCommand \"quick-menu\"\n    elif [[ \"${userInput[0]}\" =~ ^[1-9]*[0-9]+$ ]]; then\n      pushCommand \"run\" \"${userInput[0]}\"\n\n    # Ignore any input not matched a normal char, hyphen or the ?\n    # Why? Why not?\n    elif [[ \"${userInput[0]}\" =~ ^(\\?|-*|[a-z]+$) ]]; then\n      pushCommand \"${userInput[@]}\"\n\n    # FIXME: Find a solution not to print a newline with 'read -e'\n    #        and stay on the line. Could be done in a loop reading only one char\n    # like above in our first read step to show the hint at time out\n    #else\n    #  printf $'\\r'\n    fi\n  done\n}\n\n#\n# Let's get ready to rumble!\n#\n\nhasOption \"verbose\"  && protectedVerbose=(\"3\")\nhasOption \"info\"     && protectedVerbose=(\"2\")\nhasOption \"silent\"   && protectedVerbose=(\"1\")\nhasOption \"quiet\"    && protectedVerbose=(\"0\")\ndeclare -r protectedVerbose  # Now he fit his name\n[[ -n \"${protectedVerbose}\" ]] && verboseLevel=(\"${protectedVerbose}\")\n\ncheckConfigDir\n# Remove silently dead links in case something was deinstalled\n# https://unix.stackexchange.com/a/38691\nfind \"$configDir\" -type l -exec test ! -e {} \\; -exec unlink {} \\;\nreadConfigFile\nhasOption \"theme\" && theme=$(parmOfOption \"theme\")\nif [[ -n \"${theme}\" ]]; then\n  info \"Use theme: ${theme}\"\n  readConfigFile \"themes/${theme}\"\nfi\n\n# Convert our path string into an array for nicer handling later\nIFS=':' read -ra sessionPfads <<< \"$sessionPfads\"\n\n# FIXME: Do you know a way to \"re-eval\" strings with variables in it so we can\n#        simple 'declare' all these on top of file but have effect if a config\n#        file change e.g. a color?\n# Our default theme has to be modest, but should showcase the possibilities\n[[ -z \"$txtNormal\" ]]     && txtNormal=\"${txtClean}\"  # The reset key to show ordinary text\n[[ -z \"$tbsmColor\" ]]     && tbsmColor=\"${txtBold}\"\n[[ -z \"$promptCol\" ]]     && promptCol=\"${tbsmColor}\"\n[[ -z \"$menuTitle\" ]]     && menuTitle=\"${tbsmColor}T${txtNormal}erminal ${tbsmColor}B${txtNormal}ased ${tbsmColor}S${txtNormal}ession ${tbsmColor}M${txtNormal}anager (${tbsmColor}${myName}${txtNormal} v${myVersion})\"\n[[ -z \"$colSeparator\" ]]  && colSeparator=\"${txtClean}\"\n[[ -z \"$menuSeparator\" ]] && menuSeparator=\"--------------------------------------------\"\n[[ -z \"$menuPrompt\" ]]    && menuPrompt=\"${myName}:\"\n[[ -z \"$menuHint\" ]]      && menuHint=\"${tbsmColor}Hint:${txtNormal}\"\n[[ -z \"$quickPrompt\" ]]   && quickPrompt=\"${myName}:${txtNormal} What's next?\"\n[[ -z \"$infoPrefix\" ]]    && infoPrefix=\" ${tbsmColor}i${txtNormal} ${myName}: \"\n[[ -z \"$warnPrefix\" ]]    && warnPrefix=\" ${colYello}W${txtNormal} ${myName}: \"\n[[ -z \"$errorPrefix\" ]]   && errorPrefix=\" ${colRed}E${txtNormal} ${myName}: \"\n\n# Check if running in tty and set X displaynumber\nrunInTTY=$(tty)\nif [[ ! \"$runInTTY\" =~ /dev/tty[a-z]*([0-9]) ]]; then\n  unset runInTTY\nelse\n  # Replace @Xdisplay@ with e.g. :1\n  XserverArg=${XserverArg/@Xdisplay@/:${BASH_REMATCH[1]}}\nfi\n\n# Special handling to support GNU style help\nif [[ \"${1}\" == \"--help\" ]]; then\n  pushCommand \"quit\"\n  pushCommand \"--help\"\n# If no command given (-z) or starts with \"--\" show menu\nelif [[ -z \"${1}\" || \"${1}\" == --* ]]; then\n  pushCommand \"menu\"\nelse\n  pushCommand \"quit\"\n\n  # Remove every option from the command line\n  # FIXME: Did you got it without to copy in a help variable?\n  IamToStupid=\"$*\"\n  IamToStupid=\"${IamToStupid%%--*}\"\n  # Don't quote here or it will not work as desired\n  # shellcheck disable=SC2068\n  pushCommand ${IamToStupid[@]}\nfi\n\n#\n# Hey! Ho! Let's Go!\n#\nwhile popCommand\ndo\n  case \"${command}\" in\n    blacklist|b)    cmdBlacklist \"add\"                          ; ;;\n   -blacklist|-b)   cmdBlacklist \"remove\"                       ; ;;\n    doc)            cmdDoc                                      ; ;;\n    default|d)      cmdDefault                                  ; ;;\n    help|--help|h)  cmdHelp                                     ; ;;\n    list|l)         cmdList                                     ; ;;\n    menu|m)         cmdMenu                                     ; ;;\n    quick-menu|qm)  cmdQMenu                                    ; ;;\n    quit|q)         exitNormal                                  ; ;;\n    run|r)          cmdRun                                      ; ;;\n    search|s)       cmdSearch                                   ; ;;\n    whitelist|w)    cmdWhitelist \"add\"                          ; ;;\n   -whitelist|-w)   cmdWhitelist \"remove\"                       ; ;;\n    exit|X)         cmdLogout                                   ; ;;\n\n    # Short hands\n    \\?|-\\?|-h)      pushCommand \"help\" \"${command}\"             ; ;;\n    lb)             pushCommand \"list\" \"b\"                      ; ;;\n    lw)             pushCommand \"list\" \"w\"                      ; ;;\n    lc)             pushCommand \"list\" \"c\"                      ; ;;\n    [1-9]|[1-9][0-9])\n                    pushCommand \"run\" \"$command\"                ; ;;\n\n    # Try to catch lazy written b w commands\n    [bw][1-9])      pushCommand \"${command:0:1}\" \"${command:1}\" ; ;;\n   -[bw][1-9])      pushCommand \"${command:0:2}\" \"${command:2}\" ; ;;\n\n    *)              error \"Unknown command: ${command}\"         ; ;;\n  esac\n\n  prompt\n\ndone\n"
  },
  {
    "path": "themes/austere",
    "content": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Name: Austere\n# Author: loh.tar\n# Description: Anything but fun\n\nnoSeparator=\"true\"\ntbsmColor=\"${txtClean}\"\nquickPrompt=\"Choose:\"\nverboseLevel=\"2\"\n"
  },
  {
    "path": "themes/gently",
    "content": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Name: Gently\n# Author: loh.tar\n# Description: Unobtrusive and reticent but not austere\n\nnoSeparator=\"true\"\nverboseLevel=\"2\"\n"
  },
  {
    "path": "themes/riddler",
    "content": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Name: Riddler\n# Author: loh.tar\n# Description: From Gotham with love\n\ntbsmColor=\"${colGreen}\"\ntxtNormal=\"${txtClean}\"\ninfoPrefix=\"  ${tbsmColor}i${txtNormal} \"\nwarnPrefix=\"  ${colYello}W${txtNormal} \"\nerrorPrefix=\"  ${colRed}E${txtNormal} \"\nmenuPrompt=\"  ?\"\nmenuHint=\"  ${tbsmColor}!${txtNormal}\"\nquickPrompt=\"  ?\"\n#quickPrompt=\"  ? ${myName}:\"\nnoSeparator=\"true\"\nnoMenuHeader=\"true\"\nverboseLevel=\"2\"\n"
  }
]