Full Code of loh-tar/tbsm for AI

master f5f6ab3a901b cached
16 files
66.3 KB
18.3k tokens
1 requests
Download .txt
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 <enter> 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 <enter> 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 <ian@brunelli.me>,
  not only for adding Wayland support

Jan-Luca <build.opensuse.org/users/jlkDE>,
  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 <color><text><color>
   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 <dgriffiths@ghost1227.com>
#     Essential parts of tbsm are based on CDMs code.
#
#     t-display-manager
#     Copyright (C) 2012, 2013 Iru Cai <mytbk920423@gmail.com>
#     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 <n..>${tab}Add/remove entries on the Blacklist. Use -b to remove"
    print "${ind}default, d <n>${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 <n>${tab}${tab}Run the given session number"
    print "${ind}search, s <pattern>${tab}Search an application of your interest"
    print "${ind}whitelist, w <n..>${tab}Add/remove entries on the Whitelist. Use -w to remove"
    print "For your convenience there are some short hands: lb lc lw <n> ?"
  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 <name>${tab}${tab}Use given extra configuration file in <configDir>/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"
Download .txt
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
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (72K chars).
[
  {
    "path": ".gitignore",
    "chars": 152,
    "preview": "# 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"
  },
  {
    "path": ".kateconfig",
    "chars": 511,
    "preview": "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html\n\nkate-wildcard(Makefile): replace-tabs off; "
  },
  {
    "path": "Makefile",
    "chars": 1314,
    "preview": "DESTDIR=\nMyName=tbsm\n\n# Thanks to https://stackoverflow.com/a/27132934\nTHIS_FILE := $(lastword $(MAKEFILE_LIST))\n\nall: n"
  },
  {
    "path": "README.md",
    "chars": 445,
    "preview": "## Terminal Based Session Manager\n\n**tbsm** is an application or session launcher, written in pure bash with no\nncurses "
  },
  {
    "path": "doc/01_Manual.txt",
    "chars": 8668,
    "preview": "                                                             tbsm v0.7, Nov 2022\nIndex\n=======\n1- Introduction\n2- Things"
  },
  {
    "path": "doc/60_DefaultConfig.txt",
    "chars": 3939,
    "preview": "                                                             tbsm v0.6, Feb 2022\n\nDefault Configuration\n================"
  },
  {
    "path": "doc/80_ReleaseNotes.txt",
    "chars": 871,
    "preview": "Release notes to tbsm version v0.7, Nov 2022\n==============================================\nc69bc18 - runSession: detect"
  },
  {
    "path": "doc/81_ReleaseLog.txt",
    "chars": 3382,
    "preview": "--------------------------------------------------------------------------------\n                         Release Note H"
  },
  {
    "path": "doc/90_ToDo.tx",
    "chars": 1839,
    "preview": "Priority\n==========\nH: high\nM: medium\nL: low\n\nThe priority of things to do are quite arbitrary chosen and may not guaran"
  },
  {
    "path": "doc/99_License.txt",
    "chars": 15220,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "readme-install.txt",
    "chars": 465,
    "preview": "To install from source cd into the downloaded tree and simply run:\n  make\n\nRead what is offered and run the command of y"
  },
  {
    "path": "src/.shellcheckrc",
    "chars": 121,
    "preview": "# Verify variable is used\n#   We have so much unused vars, like configured colors. So this check is pesky\ndisable=SC2034"
  },
  {
    "path": "src/tbsm",
    "chars": 29908,
    "preview": "#!/bin/bash\n#\n#   tbsm: Terminal Based Session Manager\n#\n#   Copyright (C) 2016-2019, 2022 loh.tar@googlemail.com\n#\n#   "
  },
  {
    "path": "themes/austere",
    "chars": 265,
    "preview": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Na"
  },
  {
    "path": "themes/gently",
    "chars": 242,
    "preview": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Na"
  },
  {
    "path": "themes/riddler",
    "chars": 519,
    "preview": "# CAUTION! THIS FILE MAY BE OVERRIDDEN IN CASE OF AN UPDATE\n#          => rename/copy it when you modify something\n\n# Na"
  }
]

About this extraction

This page contains the full source code of the loh-tar/tbsm GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (66.3 KB), approximately 18.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!