You are not logged in.
Pages: 1
As I've already mentioned over there, I've been working on a Bash script that will display a warning dialog in case the user tries to exit the window manager when there are remaining open windows.
The whole thing is sort of finished for now, though it surely requires some additional tweaks and fixes. The source can be found at https://github.com/msiism/desktop-scripts.
Testing results (though there's not really much to test there) and comments on the code would be much appreciated. I've been using it without a problem up until now.
Two additional remarks: While the README file on GitHub states the script can currently be used with Openbox or JWM only, it should be able work with any window manager that can be queried by wmctrl and offers an exit switch as a command line option. It would simply have to be added. Also, I deliberately chose xmessage for the GUI for two reasons: One, it's part of X and doesn't depend on anything else. Two, my plan is to expand and re-write the whole script in Python and use Tk for the GUI eventually. So it doesn't have to look totally beautiful yet.
Last edited by msi (2018-03-28 02:26:27)
Offline
I'm not understanding the behavior of cut. Why are the field counts not the same for each line? I see the pid in the third field on all lines, and awk sees it the same way I do.
user@refracta:~$ wmctrl -l -p
0x0080001c -1 1866 refracta panel
0x00a00004 0 1865 refracta user@refracta: ~
0x00e00008 -1 1868 refracta spacefm
user@refracta:~$ wmctrl -l -p | cut -d " " -f 4
1865
user@refracta:~$ wmctrl -l -p | cut -d " " -f 1
0x0080001c
0x00a00004
0x00e00008
user@refracta:~$ wmctrl -l -p | cut -d " " -f 2
-1
-1
user@refracta:~$ wmctrl -l -p | cut -d " " -f 3
1866
0
1868
user@refracta:~$ wmctrl -l -p | cut -d " " -f 4
1865
user@refracta:~$ wmctrl -l -p |awk '{print $3}'
1866
1865
1868
Offline
It looks like it takes every space character as a delimiter, whereas awk sees space sequences as delimiting units.
Online
I'm not understanding the behavior of cut.
It is a bit strange indeed. Thanks for spotting that.
The problem seems to have two causes: First, the second column of wmctrl's output, which displays the number of the virtual workspace a window is on, has a different width for sticky windows (those that are visible across all workspaces), since they are identified by a value of -1. (The same goes for windows with a workspace number above 10.)
Then, as ralph.ronnquist has already pointed out, cut -d " " doesn't seem to collapse multiple spaces into one. What it seems to do instead is a bit weird: If there is only one space between two printable character strings, it obviously takes that space as a delimiter, e.g.:
$ var1="one two"
$ echo "$var1" | cut -d " " -f 1
one
$ echo "$var1" | cut -d " " -f 2
two
But if there's more than one space, cut seems to count the first one as a delimiter only, but the following ones each as both a delimiter and a field, so:
$ var1="one two" # 3 spaces inbetween
$ echo "$var1" | cut -d " " -f 1
one
$ echo "$var1" | cut -d " " -f 2 # will print the 2nd space
$ echo "$var1" | cut -d " " -f 3 # will print the 3rd space
$ echo "$var1" | cut -d " " -f 4
two
There seems to be no way to have wmctrl not show the second column, so the script should rather use sed with a regular expression instead of cut there. I'll change that.
Last edited by msi (2018-03-28 23:09:14)
Offline
Trying to use sed, I realized that it's probably not the right tool for what I want to do here. cut is actually fine. You just have to use it properly, which would mean using cut -c 15-18 in this case.
But then I thought: This is nothing but a simple substring extraction, so there should be a way to do it using parameter expansion. And there is: Once a line printed by wmctrl has been assigned to a variable, you can just use "${line:14:4}", where the first number is the starting position and the second the substring's character count.
Offline
Well, since process ids may have anything between one and five digits, you could as well let the interpreter do the tokenization, making it be like the following:
wmctrl -l -p | while read a b c d ; do echo $c ; done
Online
Right. I already noticed this is causing problems when, at one point, the script would not exit the window manager even though I had put all remaining windows on the ignore list. The reason was that one text editor window I was running had a 5-digit process id.
After searching the web for a bit, I found a really neat solution, allowing me to go back to using the initial cut command. As mentioned above, the problem with cut is that it cannot collapse multiple spaces into single ones. But you can have tr do that before you pipe the output to cut:
wmctrl -l -p | tr -s ' ' | cut -d ' ' -f 3
tr -s, as the manual page states, "replace(s) each input sequence of a repeated character (...) with a single occurrence of that character."
Last edited by msi (2018-03-29 22:59:40)
Offline
Another problem I've been facing with that script is that executing it involves killing its parent process. When running the script directly (meaning not from a terminal window), the process tree will look like this:
PID PPID COMMAND
2379 1 login
3678 2379 bash
3687 3678 startx
3709 3687 xinit
3710 3709 Xorg
3715 3709 openbox
4000 3715 wex
4015 4000 xmessage
Now, when Openbox is shut down by wex, wex will be killed immediately in return, meaning the end of the script will not be reached. To prevent this, I would probably have to do two things:
have the exit command for the window manager executed as a process independent of the running script
have the system wait for wex to finish before doing that
Btw, one thing that I don't understand about the process tree above is that, apparently, no new instance of bash is being created when wex is run, which is a bit strange given that it's a bash script.
Last edited by msi (2018-04-11 12:22:09)
Offline
After some more thinking, tinkering and reading around, I found a solution to this problem that's pretty workable. Let me get there step by step.
The idea was to somehow have the window manager's exit command invoked as independent from the currently running script but also put it on hold as long as that script is running. First, this implies that the hold action and the exit action need to be implemented as one command. Second, to run that command as independent from the current script, it has to be put in the background on invocation, which is done by appending an ampersand to the end of the line.
A very primitive and generally unreliable way to achieve this is using the sleep command and the && operator:
#!/bin/bash
#
# procman1
cmd_name=${0##*/} # Get basename of the entered command
cmd_pid=$$ # Get PID of this script
sleep 5 && openbox --exit & # Create a process in the background that will wait
# for 5 seconds and then exit Openbox.
printf "${cmd_name}($cmd_pid): I've given order to kill Openbox. This will \
happen shortly after I'm done.\n"
exit
(On a side note: I found that making the wait-and-exit routine a compound command by enclosing it in {} is not necessary because && already ties both commands together.)
Running this script and asking the shell for its exit status after it has finished shows that it is able to exit cleanly before the window manager is closed down:
$ ./devel/shell/procman/procman1 && echo $?
procman1(4742): I've given order to kill Openbox. This will happen shortly after I'm done.
0
The problem here is that allowing the current script to finish is achieved simply by waiting 5 seconds before shutting down Openbox. And while there's a very good chance this script is never going to take longer than 5 seconds to complete, scheduling a command execution based on the estimated execution time of another – instead of actually checking if that process is still alive – is fundamentally risky and should not be done.
The wait-and-exit routine should rather stay awake and explicitly wait for the script to finish. Now, waiting for processes to complete before executing anything else can be achieved by issuing the wait command. But that has its limitations: It can only handle child processes of the current shell. If sleep is simply replaced by wait (plus PID of the running script), the shell will complain about that.
#!/bin/bash
#
# procman2
cmd_name=${0##*/} # Get basename of the entered command
cmd_pid=$$ # Get PID of this script
wait $cmd_pid && openbox --exit & # Create a process in the background that
# will wait for this script to finish and
# then exit Openbox.
printf "${cmd_name}($cmd_pid): I've given order to kill Openbox. This will \
happen shortly after I'm done.\n"
exit
Running the script results in an error message from wait:
$ ./devel/shell/procman/procman2 && echo $?
procman2(5529): I've given order to kill Openbox. This will happen shortly after I'm done.
./devel/shell/procman/procman2: line 8: wait: pid 5529 is not a child of this shell
0
I'm still unsure why this really happens. I assume that #!/bin/bash at the beginning of a script creates a subshell in which the script is then executed. However, this is not reflected by the output of ps, as far as I can see.
Anyway, the question is: How is it possible to wait for any process to complete before executing a particular command? My solution has been inspired by some answers to this question on Stackoverflow. And it goes like this:
#!/bin/bash
#
# procman3
cmd_name=${0##*/} # Get basename of the entered command
cmd_pid=$$ # Get PID of this script
function f_wait_exit {
while ps -p $cmd_pid > /dev/null # Check if this script is still running, but
# redirect stdout of the ps command to
# /dev/null
do
printf "Waiting for $cmd_pid to finish...\n"
sleep 1 # Don't go crazy on CPU usage
done
sleep 3 # A bit of time to read the exit code of this script after it's done
openbox --exit
}
f_wait_exit & # Execute the wait-and-exit routine in the background
printf "${cmd_name}($cmd_pid): I've given order to kill Openbox. This will \
happen shortly after I'm done.\n"
sleep 3 # Keep this script alive for 3 seconds for demonstration purposes
exit
Running this script will give the following results:
$ ./devel/shell/procman/procman3 && echo $?
procman3(4805): I've given order to kill Openbox. This will happen shortly after I'm done.
Waiting for 4805 to finish...
Waiting for 4805 to finish...
Waiting for 4805 to finish...
0
So, problem solved? I guess so, apart from the fact that using sleep in the while loop creates a race condition. But, when the script is not kept alive for demonstration, the sleep time could be brought down to half a second or less there to reduce the risk of any new process taking $cmd_pid in the meantime.
Last edited by msi (2018-04-22 20:10:21)
Offline
Pages: 1