How to detect if i3-wm is being run or GNOME is being run in bash - linux

I've been using i3-wm for about six months now, and I had to switch to GNOME because Discord was crashing a lot in i3. I had previously used the i3-msg command in my bashrc to make sure the borders of the terminal wouldn't be visible, as to use the entire screen space for the terminal. The specific command I run is:
i3-msg -q border toggle
The problem is, when I use GNOME and I open up a terminal, the i3-msg command runs, and causes an error message evidently caused by the fact that i3 isn't running. The ideal scenario would be to add an if statement that checks if i3 is running, and if it is, then run the i3-msg command.
My question: What is the most convenient way to determine which window manager / Desktop Environment is currently running in my system?

When i3 is active, there should be a proces called "i3". You could check that with pgrep.
if pgrep -x "i3" > /dev/null
then
echo "i3 is running"
fi
-x is short for --exact – without it the if clause would still work, as long as no non-i3 process' name contains i3.
Omitting > /dev/null would print out the pid(s) found by pgrep.
Instead of pgrep you could also use pidof or ps -C. Instead of idiomatic if-then-fi you could also just use && like pidof i3 > /dev/null && echo "i3 is running" || echo "i3 is not running"

Related

How to check in If-Statement if a screen is already running

I want to write a bash script where I check If my Screen (I gave this screen the name a3_altis) is already running or not, just like this:
if (screen a3_altis is running)
then
./stop.sh
sleep 5
./start.sh
else
./start.sh
fi
I'm new in Bash, so I don't really know how to check If a screen is running.
screen may provide a more robust mechanism, but it should suffice to just use grep:
if screen -ls | grep -q a3_altis; then
./stop.sh
sleep 5
fi
./start.sh

What is the cleanest way to exit/kill/logout linux session?

I am using tiling window managers and from time to time I want to go back to a normal desktop environment or switch to another tiling window manager.
Usually I use a rofi script with loginctl terminate-user $USER.
Before this I used pkill -9 -u $USER.
I also found other options such as loginctl kill-user $USER and pkill dwm or pkill i3.
With so many options, I started to wonder which is the best and cleanest way to exit a session?
To kill a window manager, you only need to kill the X server, leaving Linux and your login shell running.
For a general command, you can use:
pkill -x X
In the specific case of i3, from i3's documentation:
To exit i3 properly, you can use the exit command, however you don’t need to (simply killing your X session is fine as well).
Example:
bindsym $mod+Shift+e exit

How to detect when a bash script is triggered from keybinding

Background
I have a Bash script that requires user input. It can be run by calling it in a terminal or by pressing a keyboard shortcut registered in the i3 (or Sway) config file as follows:
bindsym --release $mod+Shift+t exec /usr/local/bin/myscript
The Problem
I know I can use read -p to prompt in a terminal, but that obviously won't work when the script is triggered through the keybinding. In that case I can use something like Yad to create a GUI, but I'm unable to detect when it's not "in a terminal". Essentially I want to be able to do something like this:
if [ $isInTerminal ]; then
read -rp "Enter your username: " username
else
username=$(yad --entry --text "Enter your username:")
fi
How can I (automatically) check if my script was called from the keybinding, or is running in a terminal? Ideally this should be without user-specified command-line arguments. Others may use the script and as such, I'd like to avoid introducing any possibility of user error via "forgotten flags".
Attempted "Solution"
This question suggests checking if stdout is going to a terminal, so I created this test script:
#!/usr/bin/env bash
rm -f /tmp/detect.log
logpass() {
echo "$1 IS opened on a terminal" >> /tmp/detect.log
}
logfail() {
echo "$1 IS NOT opened on a terminal" >> /tmp/detect.log
}
if [ -t 0 ]; then logpass stdin; else logfail stdin; fi
if [ -t 1 ]; then logpass stdout; else logfail stdout; fi
if [ -t 2 ]; then logpass stderr; else logfail stderr; fi
However, this doesn't solve my problem. Regardless of whether I run ./detect.sh in a terminal or trigger it via keybind, output is always the same:
$ cat /tmp/detect.log
stdin IS opened on a terminal
stdout IS opened on a terminal
stderr IS opened on a terminal
It seems like the easiest way to solve your actual problem would be to change the i3 bind to
bindsym --release $mod+Shift+t exec /usr/local/bin/myscript fromI3
and do
if [[ -n "$1" ]]; then
echo "this was from a keybind"
else
echo "this wasn't from a keybind"
fi
in your script.
False Positive
As most results on Google would suggest, you could use tty which would normally return "not a tty" when it's not running in a terminal. However, this seems to differ with scripts called via bindsym exec in i3/Sway:
/dev/tty1 # From a keybind
/dev/pts/6 # In a terminal
/dev/tty2 # In a console
While tty | grep pts would go part way to answering the question, it is unable to distinguish between running in a console vs from a keybinding which you won't want if you're trying to show a GUI.
"Sort of" Solution
Triggering via keybinding seems to always have systemd as the parent process. With that in mind, something like this could work:
{
[ "$PPID" = "1" ] && echo "keybind" || echo "terminal"
} > /tmp/detect.log
It is likely a safe assumption that the systemd process will always have 1 as its PID, but there's no guarantee that every system using i3 will also be using systemd so this is probably best avoided.
Better Solution
A more robust way would be using ps. According to PROCESS STATE CODES in the man page:
For BSD formats and when the stat keyword is used, additional characters may be displayed:
< high-priority (not nice to other users)
N low-priority (nice to other users)
L has pages locked into memory (for real-time and custom IO)
s is a session leader
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
+ is in the foreground process group
The key here is + on the last line. To check that in a terminal you can call ps -Sp <PID> which will have a STAT value of Ss when running "interactively", or S+ if it's triggered via keybinding.
Since you only need the STATE column, you can further clean that up with -o stat= which will also remove the headers, then pipe through grep to give you the following:
is_interactive() {
ps -o stat= -p $$ | grep -q '+'
}
if is_interactive; then
read -rp "Enter your username: " username
else
username=$(yad --entry --text "Enter your username:")
fi
This will work not only in a terminal emulator and via an i3/Sway keybinding, but even in a raw console window, making it a much more reliable option than tty above.

start process in background, get PID, and write into stdin in Shell

I need to do a buffer overflow for my system security course. Therefore I do have a program(called canary) I need start which asks for a input string (read()).
I need to calculate a canary(random canary built with PID and time) for a successful buffer overflow. I already wrote a program(getcanary) which gets me the right canary. The problem:
I try to start canary in a extra terminal, then get the PID of it, then calculate the program followed by a write to canary's STDIN. The last thing is where I have a Problem.
#!/bin/bash
echo "start canary"
x-terminal-emulator -e ./canary &
sleep 1
PID=$(pgrep canary)
CANARY=$(./getcanary $PID)
How can I write the command to the extra terminal? I already tried several solutions,
echo "cmd" > /proc/$PID/fd/0
is one of it
I also tried
mkfifo fifo
cat > fifo &
./canary < fifo
echo "cmd" > fifo
some other solutions are not allowed by my environment, as the script must run on a clean install of xubuntu, so I can't use screen or tmux
I hope you can help me,
Thank you! :)
PS.: I'm sorry if I misunderstood any of these solutions I tried, I'm not very familiar with shell scripting.
Write to the terminal, not to the running process!
#!/bin/bash
echo "start canary"
x-terminal-emulator -e ./canary &
termpid=$!
sleep 1
xvkbd -window $(xdotool search --sync --pid $termpid) -text "echo Hello world!\n"

How to reset tty after exec-ed program crashes?

I am writing a Ruby wrapper around Docker and nsenter. One of the command my tool provides is to start a Bash shell within a container. Currently, I am doing it like this:
payload = "sudo nsenter --target #{pid(container_name)} --mount --uts --ipc --net --pid -- env #{env} /bin/bash -i -l;"
Kernel.exec(payload)
In Ruby, Kernel#exec relies on the exec(2) syscall, hence there is no fork.
One issue is that the container sometime dies prematurely which effectively kills my newly created Bash prompt. I then get back the prompt originally used to run my Ruby tool, but I cannot see what I am typing anymore, the tty seems broken and running reset effectively solves the issue.
I'd like to conditionally run reset if the program I exec-ed crashes. I found that the following works well:
$ ./myrubytool || reset
Except I'd like to avoid forcing people using my tool to append || reset every time.
I have tried the following:
payload = "(sudo nsenter --target #{pid(container_name)} --mount --uts --ipc --net --pid -- env #{env} /bin/bash -i -l) || reset;"
But this surprisingly puts reset in the background (i.e. I can run reset by entering fg). One benefit is that the tty is working properly, but it's not really ideal.
Would you have any idea to solve this issue?
If terminal echo has been disabled in a terminal, then you can run the command stty echo to re-enable the terminal echo. (Conversely, stty -echo disables terminal echo, and stty -a displays all terminal settings.)
This is safe to run even if terminal echo is already enabled, so if you want to play it safe, you can do something like ./myrubytool ; stty echo which will re-enable terminal echo if it is disabled regardless of the exit status of your Ruby program. You can put this in a shell script if you want to.
It might be that there is a way to execute a command when the Ruby program exits (often referred to as a "trap"), but I'm not familiar enough with Ruby to know whether such capabilities exist.
However, if you are creating a script for general use, you probably should look into more robust techniques and not rely on workarounds.
How about this? It should do exactly what you want.
It runs the command in a separate process, waits on it, and if, when it finishes, the return value is not 0, it runs the command reset.
payload = "sudo nsenter --target #{pid(container_name)} --mount --uts --ipc --net --pid -- env #{env} /bin/bash -i -l;"
fork { Kernel.exec(payload) }
pid, status = Process.wait2
unless status.exitstatus == 0
system("reset")
end
EDIT
If all you want to do is turn echo back on, change the system("reset") line to system("stty echo").

Resources