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"
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.