I solved the problem.
The first part of the solution was the information by Gilles that xdotool is just a binary, so there's no need to install it. Just copying the executable to the remote machine (on which I have permission to run executables stored in my own directories) was sufficient.
Note that when below I mention any command, I assume that the DISPLAY
variable is set to the display where Mathematica runs (which in my case was :0.0
). This is what I did first after logging into the machine using ssh, before doing any of the commands below.
The next step was recognizing that when the screen is locked, the Gnome screensaver grabs the keyboard events, so any keypresses sent to the Mathematica window ended up at the screensaver instead. Fortunately the Gnome screensaver is easy to unlock from the command line. The command is
gnome-screensaver-command -d
which I found here.
After that, I identified the Mathematica window using the command
xwininfo -root -tree | grep Mathematica
which I found here. The correct Mathematica window was easy to identify because it contained in the title the notebook's file name ("notebook" is the Mathematica name for a specific type of Mathematica document, the one you usually use for calculations) and a star to indicate that it was unsaved. Each line starts with the corresponding window ID (a hex number like 0x13371d)
After having identified the window, I then set focus to it using
xdotool windowactivate 0x13371d
(where the hex number was of course the window ID obtained previously) and finally sent it the Control-S using
xdotool key ctrl+s
Then I used xwininfo again to check that the star indeed disappeared in the window title, indicating that the notebook had really been saved.
If switching to xterm
is an option, you could use the hack below. There are a few caveats though. Once you address most of them, the solution ends up quite complicated, see the final script at the end.
xterm -e 'trap "" HUP; your-application'
Upon receiving the instruction to close from the window manager, xterm
will send a SIGHUP to the process group of your-application, and only exit itself when the process returns.
That assumes your-application doesn't reset the handler for SIGHUP and could have unwanted side effects for the children of your-application.
Both of which seem to be a problem if your-application is tmux
.
To work around those, you could do:
xterm -e sh -c 'bash -mc tmux <&1 & trap "" HUP; wait'
That way, tmux
would be started in a different process group, so only the sh
would receive the SIGHUP (and ignore it).
Now, that doesn't apply to tmux
which resets the handler for those signals anyway, but in the general case, depending on your implementation of sh
, the SIGINT, SIGQUIT signals and generally both will be ignored for your-application as that bash
is started as an asynchronous command from a non-interactive sh
. That means you couldn't interrupt your-application with Ctrl+C or Ctrl+\.
That's a POSIX requirement. Some shells like mksh
don't honour it (at least not the current versions), or only in part like dash
that does it for for SIGINT but not SIGQUIT. So, if mksh
is available, you could do:
xterm -e mksh -c 'bash -mc your-application <&1 & trap "" HUP; wait'
(though that may not work in future versions of mksh
if they decide to fix that non-conformance).
Or if you can't guarantee that mksh
or bash
will be available or would rather not rely on behaviour that may change in the future, you can do their work by hand with perl
and for instance write an unclosable-xterm
wrapper script like:
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -MPOSIX -e '
$pid = fork;
if ($pid == 0) {
setpgrp or die "setpgrp: $!";
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!";
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE";
waitpid(-1,WUNTRACED)' "$@"
(to be called as unclosable-xterm your-application and its args
).
Now, another side effect is that that new process group we're creating and putting in foreground (with bash -m
or setpgrp
+tcsetpgrp
above) is no longer the session leader process group, so no longer an orphaned process group (there's a parent supposedly caring for it now (sh
or perl
)).
What that means is that upon pressing Ctrl+Z, that process will be suspended. Here, our careless parent will just exit, which means the process group will get a SIGHUP (and hopefully die).
To avoid it, we could just ignore the SIGTSTP in the child process, but then if your-application is an interactive shell, for some implementations like mksh
, yash
or rc
, Ctrl-Z won't work either for the jobs they run.
Or we could implement a more careful parent that resumes the child each time it's stopped, like:
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -MPOSIX -e '
$pid = fork;
if ($pid == 0) {
setpgrp or die "setpgrp: $!";
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!";
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE";
while (waitpid(-1,WUNTRACED) > 0 && WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
kill "CONT", -$pid;
}' "$@"
Another issue is that if xterm
is gone for another reason than the close from the window manager, for example if xterm
is killed or loses the connection to the X server (because of xkill
, the destroy action of you Window manager, or the X server crashes for instance), then those processes won't die as SIGHUP would also be used in those cases to terminate them. To work around that, you could use poll()
on the terminal device (which would be torn down when xterm
goes):
#! /bin/sh -
[ "$#" -gt 0 ] || set -- "${SHELL:-/bin/sh}"
exec xterm -e perl -w -MPOSIX -MIO::Poll -e '
$pid = fork; # start the command in a child process
if ($pid == 0) {
setpgrp or die "setpgrp: $!"; # new process group
tcsetpgrp(0,getpid) or die "tcsetpgrp: $!"; # in foreground
exec @ARGV;
die "exec: $!";
}
die "fork: $!" if $pid < 0;
$SIG{HUP} = "IGNORE"; # ignore SIGHUP in the parent
$SIG{CHLD} = sub {
if (waitpid(-1,WUNTRACED) == $pid) {
if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
# resume the process when stopped
# we may want to do that only for SIGTSTP though
kill "CONT", -$pid;
} else {
# exit when the process dies
exit;
}
}
};
# watch for terminal hang-up
$p = IO::Poll->new;
$p->mask(STDIN, POLLERR);
while ($p->poll <= 0 || $p->events(STDIN) & POLLHUP == 0) {};
kill "HUP", -$pid;
' "$@"
Best Answer
I've had a similar problem with
awesome
window manager as well asurxvt
, when ImageMagick was used to set the background. It got quickly resolved with feedback from the author ofawesome
- you can see the archive of this conversation on gmane archives - here and further on here.The solution was to change the background setter and I chose to use
habak
because it was the lightest one. You can also use other, likefeh
orEsetroot
(belongs to Enlightenment WM). I think I would recommend you to tryfeh
first, since it seems to be packaged for many distros.Side note: In case someone wanted to try out many different bg-setters, here's a list of those that
awsetbg
(bg-setting wrapper script fromawesome
) tries to use:Esetroot habak feh hsetroot chbg fvwm-root imlibsetroot display qiv xv xsri xli xsetbg wmsetbg xsetroot
Note that some of those only come shipped with bigger packages.
Edit: Looking at xsri manpage, I think it might provide best flexibility for your needs.