![如何创建一个 X 窗口,该窗口不会通过终端命令以外的任何方式关闭](https://linux22.com/image/85803/%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%20X%20%E7%AA%97%E5%8F%A3%EF%BC%8C%E8%AF%A5%E7%AA%97%E5%8F%A3%E4%B8%8D%E4%BC%9A%E9%80%9A%E8%BF%87%E7%BB%88%E7%AB%AF%E5%91%BD%E4%BB%A4%E4%BB%A5%E5%A4%96%E7%9A%84%E4%BB%BB%E4%BD%95%E6%96%B9%E5%BC%8F%E5%85%B3%E9%97%AD.png)
我正在尝试创建一个从 X11 开始的进程,该进程将是图形化的并且无法被我自己的用户杀死。
更具体地说,我有一个 urxvt 下拉终端,在启动时启动并立即运行 tmux 会话。让我烦恼的是,当我忘记它是下拉终端而我应该隐藏它时,有时我会用Alt+杀死它。F4
当然,我不会丢失 tmux 会话,因为我可以从另一个终端重新连接到它,但我必须重新启动下拉终端(使用确切的配置)并重新连接,这很乏味。
我想做的是无法Alt杀死F4 这个特定进程。
读完这篇文章后: 使 Linux 上的进程不可终止
我知道我应该以属于另一个用户的身份启动该进程,但即使我设法在我自己的 X 服务器中执行此操作:
sudo xhost +local:
sudo runuser -l my-secondary-user -c urxvt
我面临两种不良行为:
- 该进程可由我的主用户(拥有 X 会话的用户)终止。
- 虚拟终端开始以 my-secondary-user 身份登录(当然)。
(2) 可能是可以纠正的 - 只需运行命令以我的主用户身份登录 - 但我不知道如何修复 (1)。
有没有办法让这项工作正常进行,或者有其他方法来做我想做的事?如果可能的话,我宁愿避免创建内核模块。
使用 Openbox 运行 Arch Linux。
编辑
我想我也许可以让Alt+F4不适用于这个特定的窗口。发现了这个: Openbox:根据每个应用程序禁用 Alt-F4
并修改了我的rc.xml
:
<keybind key="A-F4">
<action name="If">
<application class="URxvt"
title="tmux-session">
</application>
<then><!-- Do nothing for urxvt-tmux-session --></then>
<else>
<action name="Close" />
</else>
</action>
</keybind>
这比以前稍微好一些:我不能Alt+F4那个终端,但现在也不能Alt+F4任何 urxvt 窗口。 (猜猜这是因为新的 urxvt 窗口都在相同的类名下运行?)
除此之外,我可以从菜单栏中删除我需要的所有内容,或者右键单击我的色调面板,或者当然,通过命令。但这种方法意味着:
- 我仍然可以通过右键单击 Tint2 面板来终止我的下拉终端(如果我使下拉终端不作为图标出现在面板上,也许可以修复 - 通过一些研究应该可以实现)。
- 我不能Alt+F4其他终端窗口(不太喜欢它,但也许可以接受)。
有任何想法吗?
答案1
如果xterm
可以选择切换到,您可以使用下面的技巧。但有一些注意事项。一旦解决了其中的大多数问题,解决方案就会变得相当复杂,请参阅最后的最终脚本。
xterm -e 'trap "" HUP; your-application'
在收到窗口管理器的关闭指令后,xterm
将向您的应用程序的进程组发送 SIGHUP,并且仅在进程返回时自行退出。
假设您的应用程序不会重置 SIGHUP 的处理程序,并且可能会对您的应用程序的子应用程序产生不必要的副作用。
如果您的应用程序是tmux
.
要解决这些问题,您可以这样做:
xterm -e sh -c 'bash -mc tmux <&1 & trap "" HUP; wait'
这样,tmux
将在不同的进程组中启动,因此只有sh
会收到 SIGHUP (并忽略它)。
现在,这并不适用于tmux
重置这些信号的处理程序,但在一般情况下,根据您的实现sh
,SIGINT、SIGQUIT 信号通常都会被您的应用程序忽略,因为它bash
是作为来自非交互式sh
.这意味着您无法使用Ctrl+C或中断您的应用程序Ctrl+\。
这是 POSIX 的要求。有些 shellmksh
不支持它(至少当前版本不支持),或者仅部分dash
支持 SIGINT 但不支持 SIGQUIT。因此,如果mksh
可用,您可以这样做:
xterm -e mksh -c 'bash -mc your-application <&1 & trap "" HUP; wait'
mksh
(尽管如果他们决定修复该不符合项,这可能在未来版本中不起作用)。
或者,如果您不能保证mksh
或bash
将可用,或者不想依赖将来可能改变的行为,您可以手动完成他们的工作,perl
例如编写一个unclosable-xterm
包装脚本,如下所示:
#! /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)' "$@"
(称为unclosable-xterm your-application and its args
)。
现在,另一个副作用是我们正在创建并放入前台的新进程组(上面带有bash -m
或setpgrp
+ tcsetpgrp
)不再是会话领导者进程组,因此不再是成为孤儿的进程组(据说现在有一个父进程在照顾它(sh
或perl
))。
这意味着按下 后Ctrl+Z,该过程将被暂停。在这里,我们粗心的父进程将退出,这意味着进程组将收到 SIGHUP (并希望死亡)。
为了避免这种情况,我们可以忽略子进程中的 SIGTSTP,但是如果您的应用程序是交互式 shell,则对于某些实现(例如mksh
、yash
或rc
),Ctrl-Z将不适用于它们运行的作业。
或者我们可以实现一个更仔细的父级,每次停止时都会恢复子级,例如:
#! /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;
}' "$@"
另一个问题是,如果xterm
由于其他原因而消失关闭从窗口管理器,例如,如果xterm
被终止或失去与 X 服务器的连接(因为xkill
,破坏例如,您的窗口管理器的操作,或者 X 服务器崩溃),那么这些进程不会终止,因为在这些情况下也会使用 SIGHUP 来终止它们。要解决这个问题,您可以poll()
在终端设备上使用(终端设备运行时会被拆除xterm
):
#! /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;
' "$@"
答案2
你的问题有一个有点误导性的标题,导致你的研究走向错误的方向。当您关闭窗口时X
,服务器不会终止你的进程。相反,它WM_DELETE_WINDOW
向进程发送一条消息,进程随后终止。这个事实注定了你的第一种方法:即使你设置了标志SIGNAL_UNKILLABLE
,你仍然能够关闭它。
您的第二种方法似乎可用,您所要做的就是区分单个终端窗口和所有其他窗口。实现此目的的一种方法是urxvt
从特定标题(例如 )开始,urxvt -name urxvt-iddqd
并为其名称创建规则:
<keybind key="A-F4">
<action name="If">
<application title="urxvt-iddqd"
title="tmux-session">
</application>
<then><!-- Do nothing for urxvt-tmux-session --></then>
<else>
<action name="Close" />
</else>
</action>
</keybind>
答案3
问题的作者提供了以下解决方案:
我找到了一个适合我的解决方案德米特里的回答和我上面的编辑:
- 我为我的设置了一个自定义标题
urxvt
:urxvt -title urxvt-drop-down
我没有使用自定义名称,因为看起来
urxvt -name urxvt-drop-down
命令忽略
.Xresources
我当前使用的文件,而-title
使用它。
- 我使用Alt+F4键绑定来忽略标题为 的终端
urxvt-drop-down
。 rc.xml:<keybind key="A-F4"> <action name="If"> <title>urxvt-drop-down</title> <then><!-- Do nothing for urxvt-drop-down --></then> <else> <action name="Close" /> </else> </action> </keybind>
- 我让罪魁祸首永远不会出现在任务栏中,并且由于它
skip_taskbar
也使窗口失去焦点,因此重新聚焦它。rc.xml
:<application title="urxvt-drop-down"> <skip_taskbar>yes</skip_taskbar> <focus>yes</focus> </application>
尽管这为我解决了问题,但这本质上是一种解决方法。因此我还不确定是否斯蒂芬的回答是这个问题的正确答案。
需要对所提供的命令进行一些研究以了解正在执行的操作。
不过,我不会使用
xterm
,因为我想要假透明度和下拉功能(我认为 xterm 也可能有某种下拉功能,但如果没有合成器,透明度肯定是不可能的 - 而且我不会使用合成器)。