自动重新分配您的默认打印机

我运行 Linux,……还是它运行我? 一些计算范式是如此无处不在,如此根深蒂固,我们很少停下来认为事情可以以另一种方式工作。 当这样的认识来临时,我们可以行使我们的自由——其中之一 Fedora的 四个基础 – 改善 用户体验. 为了让这种情绪不仅仅是陈词滥调,我需要重新想象默认打印机的想法以及它是如何设置的。 本文介绍了该实现。

动机

我的打印需求很简单。 我偶尔会打印几张来自 lilypond 的乐谱、来自网站的优惠券,或者出于税收目的我应该保留在档案中的帐户信息页面。 仅当我从头开始安装操作系统或更换打印机时,才会考虑选择打印机。 否则,我将使用我的默认 – 也是唯一的 – 打印机。

我儿子的打印需求完全不同。 他带回了一台专业的标签打印机,邻居把它当作垃圾扔在路边。 修复这个深奥的设备并让它与 Linux 一起工作的挑战是不可抗拒的。 他成功了! 那台打印机很快又加入了另一台,然后是另一台。 来自 eBay 的二手标签打印机、零件和大卷标签库存开始到货。 很快,他就接受了付费客户的新奇和定制标签订单,所有这些都是由 CUPS 驱动的,提供免费字体、GIMP、Inkscape 和本土脚本。

专业标签打印机控制包括我们大多数“普通纸”用户从未处理过的大量功能:刀预设、温度设置、颜色通道之间的库存倒带等。任何项目都需要在最终打印之前进行多次设置和测试打印作业。 有大约十几种不同的打印机可供选择,每一种都有自己独特的功能组合,一件事几乎总是正确的:默认打印机不是他下一个项目所需的打印机。

如果不是默认设置,命令行打印程序通常会被告知要使用的打印机。 大多数图形应用程序会在您选择打印时显示打印机列表,并预先选择默认打印机。 在不首先更改指定的默认打印机的情况下在多个命令行和图形打印应用程序之间切换最终会导致错误——要么选择错误的打印机,要么根本忽略选择一个。

值得指出的是,更改默认打印机并不难。 在“打印设置”对话框中单击“设置为默认值”即可。 但这也是另一件要做的事情,一件非常容易推迟或完全忽视的事情。 显然,一旦您开始打印到特定打印机,即使您没有更改计算机的默认打印机,您也已经在心里更改了默认打印机。 有一天,我儿子在讨论这个问题时说:“如果我每次打印时,系统都会将我刚使用的任何打印机设为默认打印机,那将很有帮助。” 我被说服了,但系统上没有这样的复选框。

确定最后使用的打印机

尽管大多数用户通过应用程序与 CUPS(通用 Unix 打印系统)进行交互,但它也具有强大的命令行界面。 lpstat 命令列出打印机或打印作业的状态。 添加参数 -W all 将显示当前用户的所有打印作业,无论它们是否已完成。 后面没有用户名的 -u 将对所有用户的打印作业执行相同的操作。 为了我们的目的,lpstat -W all -u 按时间顺序列出作业。 最后一行是我们唯一感兴趣的。 每行的第一个字段是作业名称。 这由打印机名称(您想要的部分)后跟连字符和作业编号组成。

$ lpstat -W all -u
HP-LaserJet-4250-5         utoddl   393216   Tue 21 Nov 2017 09:26:09 PM EST
HP-LaserJet-4250-6         root     393216   Tue 21 Nov 2017 09:27:07 PM EST
HP-Deskjet-952c-Printer-7  utoddl   171008   Sat 25 Nov 2017 05:00:42 PM EST
[...]
HP-LaserJet-4250-51        utoddl   132096   Wed 23 Jun 2021 08:43:53 AM EDT
HP-Deskjet-952c-Printer-52 root      28457   Sun 27 Jun 2021 11:24:50 AM EDT

一点 bash 脚本可以将最后使用的打印机的名称与作业名称中的连字符和数字隔离开来。 lpadmin 命令可以设置默认打印机:

while read -a line; do
  printer=${line[0]%-*}
done < <(lpstat -W all -u)
lpadmin -d $printer

检测和处理打印作业

现在你有 bash 设置默认打印机的代码,您需要知道何时运行它。 您可以将其置于无限循环中,在默认打印机重置之间休眠 30 秒。 或者,您可以监控系统日志以了解打印活动并采取相应措施。 这些都是非常浪费的方法。 更好的方法是让系统在 /var/spool/cups 目录更改时通知您,它对每个打印作业都会这样做。 现代 Linux 内核通过 inotify 接口提供该功能。 用户空间程序可以使用 inotify 接口要求 Linux 内核在特定文件或目录发生某些更改时通知它们。

Internet 上提供的许多实用程序有助于从脚本访问 inotify 界面。 幸运的是,您不需要它们中的任何一个。 Fedora 已经有了通过inotify激活服务的机制:systemd。

系统

Systemd 根据系统事件和条件管理服务。 所有这些都在 systemd 单元文件中定义。 除了在系统启动或关闭时简单地启动和停止服务外,systemd 还将某些事件单元与类似名称的服务单元配对。 为了 examplelogrotate.timer 单元定义了一个计时器,当它关闭时,激活 logrotate.service。

路径单元

路径单元使用内核的 inotify 接口来监视指定文件或目录中的更改。 您将需要创建一个名为 default-printer-update.path 的路径单元文件,该文件将在 /var/spool/cups 目录更改时触发。 您还将创建一个名为 default-printer-update.service 的相应服务单元文件,其中包含 bash 设置默认打印机的代码。 只要路径单元触发,该服务单元就会激活。 我选择的名称以符合其他单位的约定 Fedora — 它在做什么,然后是它正在采取什么行动,所有小写的 ASCII 字母用连字符分隔。 路径和服务单元文件都位于 /etc/systemd/system 目录中。 这由 root 用户拥有并且只能由 root 用户写入。 因此,您需要使用 sudo 获得提升的权限和您最喜欢的文本编辑器来创建和编辑这些文件。 这是路径单元文件。 (参见 man systemd.path。)

# /etc/systemd/system/default-printer-update.path
[Unit]
Description=Default printer update

[Path]
PathChanged=/var/spool/cups

[Install]
WantedBy=multi-user.target

服务单位

服务单元文件如下。 请参阅 man systemd.service 以获得明确的解释,但我会指出一些事情。 Type=oneshot 表示这个单元只运行一个命令; 它不会创建任何需要稍后监视和关闭的后台进程。 第二个 ExecStart 行中的命令比我们的更复杂 bash 上面的代码,而第一个注释掉的 ExecStart 行中的替代方案要简单一些。 要么会工作。 较长的第二个仅在与当前默认打印机不同时才尝试设置默认打印机,并且它始终输出一些将显示在系统日志中的文本,以帮助简化调试。

这两个示例一起显示了在服务单元文件的 Exec 行中包含重要命令时必须如何处理某些字符。 反斜杠和引号必须用反斜杠转义,美元符号必须加倍,换行符完全丢失,因此必须插入分隔分号。 尽管对某些人来说很棘手而且可能很有趣,但更多细节超出了本文的范围。

# /etc/systemd/system/default-printer-update.service
[Unit]
Description=Default printer update

[Service]
Type=oneshot

# ExecStart=/bin/bash -c 'lpadmin -d $( lpstat -W all -u | tail -n 1 | cut -f1 -d  | sed -E -e 's/-[0-9]+$//' )'

ExecStart=/bin/bash -c 'lpstat_d=$$( lpstat -d ) ;
                        default_printer=$${lpstat_d##* } ;
                        last_job_id=$$( lpstat -W all -u | tail -n 1 | cut -f1 -d" " ) ;
                        last_printer=$${last_job_id%%-*} ;
                        if [ "$$last_printer" != "$$default_printer" ] ; then 
                          echo "Updating default printer to $$last_printer." ;
                          lpadmin -d "$$last_printer" ;
                        else 
                          echo "Leaving default printer as $$default_printer." ;
                        fi'

[Install]
WantedBy=multi-user.target

重新加载系统

在 /etc/systemd/system 中拥有两个单元文件(模式 0644、所有者 root 和组 root 设置)后,您必须使用以下命令使更改生效:

sudo systemctl daemon-reload

每当您对任一单元文件进行更改时,都必须执行此重新加载步骤。

要开始测试,您需要启用并启动路径单元。 不需要启用服务单元; 它将在路径单元触发时启动。

sudo systemctl enable default-printer-update.path
sudo systemctl start default-printer-update.path

最后你准备好测试了。

测试

让我指出两件事情,一旦陈述起来似乎很明显,但只是设置它的人可能不会想到。 首先,这在没有人打印过、不再定义上次使用的打印机或已刷新打印作业历史的系统上不起作用。 幸运的是,它也不会伤害任何东西。 其次,您实际上不必打印任何东西来测试它。 就足够了 sudo touch /var/spool/cups 触发路径单元,然后激活服务。 这可以节省大量时间、纸张和真正昂贵的东西:打印机墨水。

设置

您至少需要定义两台打印机。 这些不必实际存在或附加到您的机器上。 只要用不同的名称定义它们,它们都可以指向同一个物理打印机。 如果需要,请继续启动 system-config-printer 并定义您想要的任何打印机。

这些命令在您处理测试时的问题时很有用。 命令 lpstat -a 将列出所有定义的打印机, lpstat -d 将显示当前的默认打印机,并且 sudo lpadmin -d printer_name 将设置默认打印机。

过程

打开两个终端,在其中一个终端输入命令 journalctl -f。 这将显示系统日志消息,包括来自 systemd 的关于激活 default-printer-update.service 的消息以及来自 default-printer-update.service 本身的消息。

在另一个终端中,输入 sudo 触摸 /var/spool/cups,您应该会在第一个终端中看到如下消息:

Oct 02 15:12:04 lappy systemd[1]: Starting Default printer update…
Oct 02 15:12:04 lappy bash[35263]: Leaving default printer as Generic-CUPS-BRF-Printer0.
Oct 02 15:12:04 lappy systemd[1]: default-printer-update.service: Deactivated successfully.
Oct 02 15:12:04 lappy systemd[1]: Finished Default printer update.

这些消息完全出现表明路径单元正在工作并激活了服务单元。 在这种情况下,默认打印机已正确设置。 否则,消息会显示“将默认打印机更新为 。”

我们在这里的工作完成了!

Selinux

在这一点上,我预计必须解释如何说服 selinux 允许我们的路径单元监视 /var/spool/cups 目录。 这是我儿子和我一年多前第一次设置它时的一个问题。 我惊喜地发现,正如 man init_selinux 所示,init_t 类型的进程(比如 systemd 本身代表你的路径单元)现在可以管理 print_spool_t 类型的文件和目录(比如 /var/spool/cups)。

selinux 增强是隐藏在 Fedora 以及其他使使用 Linux 在快速变化的技术环境中成为可行选择的发行版。 它建立在很多人的大量工作和很多乐趣的基础上。 我很感激他们让我能够参与并分享像这样的小项目。 我希望你喜欢它。