A small daemon that makes the Scan key on networked Brother multi-function printers/scanners work on Linux. It registers your PC with the scanner over SNMP; when you press Scan → Scan to File / Image / OCR / E-mail on the device and pick a menu entry, the scanner notifies the PC and the daemon runs the script you configured for that entry (e.g. scan to a multi-page PDF or a JPG).
This is a modernised version of Frank Abelbeck's original brotherscankeyd:
- No third-party Python modules. The old
net-snmp(snmpset) andlinuxfddependencies are gone — the SNMP request is built with the standard library, and timers/signals useepolltimeouts andsignal.set_wakeup_fd. - Runs straight from this directory — it finds its own
.iniand.shfiles without a system-wide install. - Single-instance lock,
Ctrl+Cto stop, scan folder configurable in the ini, and the bundled example scripts produce a multi-page PDF and a JPG.
- Python 3 (3.4+). No extra Python packages.
- sane-backends — provides
scanimageand the scanner backend.- For Brother network scanning, install Brother's brscan4 driver and
register the scanner (see below). Many Brother models also work via
sane-airscan(eSCL/WSD), but the bundled scripts use Brother-style option names, sobrscan4is recommended.
- For Brother network scanning, install Brother's brscan4 driver and
register the scanner (see below). Many Brother models also work via
- ImageMagick (
convert) — used by both scan scripts. - pdftk — used by
scan2pdf.shonly (for the odd/even merge feature). - xdg-user-dir (usually preinstalled) — used to locate your Pictures
folder; optional, there is a
~/Picturesfallback.
On Debian/Ubuntu/Zorin:
sudo apt install sane-utils imagemagick pdftk
# brscan4: download the .deb from Brother's support site and install it, then:scan2pdf.sh/scan2image.sh talk to the scanner through SANE. After installing
brscan4, register your device (one-time, needs root):
sudo brsaneconfig4 -a name=MyScanner model=MFC-1910W ip=192.168.1.31model=must match a brscan4-supported model exactly.ip=is your scanner's address.
Verify it is found — you should see a brother4: device:
scanimage -L
# device `brother4:net1;dev0' is a Brother MFC-1910W ...The brother4:net1;dev0 string is what goes in the ini's dev = field.
Edit brscankeyd.ini (see Configuration below).
The daemon looks for its config in this order:
- the file given with
--config, /etc/brscankeyd.ini,brscankeyd.ininext tobrscankeyd.py(i.e. this directory).
Script paths in the ini may be absolute or relative to the daemon's
directory (so the bundled scan2pdf.sh / scan2image.sh work as-is).
Run it from this directory:
./brscankeyd.py --verbose start # run in the foreground (Ctrl+C to stop)
./brscankeyd.py start # same, but quiet (INFO level)
./brscankeyd.py stop # stop a running instance (sends SIGTERM)
./brscankeyd.py daemon # detach and run in the background
./brscankeyd.py config # print help on the ini format
./brscankeyd.py license # show license informationOptions: --config CFG, --address HOST, --port PORT (default 54925),
--syslog, --verbose.
Once started, press Scan on the device, choose Scan to File (or Image), and select an entry — a file appears in your scan folder. With multiple pages the scanner asks "scan another page?"; all pages are merged into one PDF.
Only one instance can run at a time (guarded by an exclusive file lock); a second start exits with "another brscankeyd process is already running".
A unit is bundled as brscankeyd.service. Symlink
it into your user units and enable it:
mkdir -p ~/.config/systemd/user
ln -s "$PWD/brscankeyd.service" ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now brscankeyd.serviceAlternatively, let systemd create the symlink for you with link (give it the
absolute path to the unit in this folder):
systemctl --user link "$PWD/brscankeyd.service"
systemctl --user enable --now brscankeyd.serviceManage / inspect it:
systemctl --user status brscankeyd
systemctl --user restart brscankeyd # after editing the ini
journalctl --user -u brscankeyd -f # live logs
systemctl --user disable --now brscankeyd # turn it offNotes:
- The service runs as you, so scans land in your home folder and it stops at
logout. To keep it running while logged out:
loginctl enable-linger $USER. ExecStartuses an absolute path — update it (anddaemon-reload) if you move this folder.- Don't run the service and a manual
./brscankeyd.py startat once; the second is refused by the single-instance lock.
brscankeyd.ini is an INI-style file.
[General]
first cycle = 3 ; seconds before the first SNMP registration
cycle = 300 ; seconds between registration refreshes
buffer size = 4096 ; UDP read buffer size
hostname = 192.168.1.111 ; this PC's IP the scanner should notify
; (omit to auto-detect)
port = 54925 ; UDP port the scanner notifies (Brother default)
scan dir = ~/Afbeeldingen/Scans ; where scans are saved (see below)scan dir is optional. If set, the daemon exports it to the scan scripts as the
SCANDIR environment variable. If unset, the scripts default to a Scans
folder inside your XDG Pictures directory (e.g. ~/Afbeeldingen/Scans),
falling back to ~/Pictures/Scans.
One section per scanner (any name that is not General and not a menu section):
[MFC-1910W]
ip = 192.168.1.31
dev = brother4:net1;dev0 ; the SANE device from `scanimage -L`Sections ending in FILE, IMAGE, OCR or EMAIL define the entries shown
under the scanner's matching Scan to … menu. Each line is
Entry name = /path/to/script [args…]. The daemon calls the script with the
device's dev value as the first argument, followed by any args you list.
Prefix with DeviceName : to scope entries to one device; without a prefix the
entries apply to every device.
[MFC-1910W : FILE]
PDF = scan2pdf.sh
[ MFC-1910W : IMAGE]
JPG = scan2image.sh 600 ; passed to the script as the resolution(Leading/trailing spaces around section and entry names are ignored.)
Both write into $SCANDIR (created automatically) with a dated, auto-numbered
filename, and accept the SANE device address as $1.
| Script | Output | Notes |
|---|---|---|
scan2pdf.sh |
multi-page PDF | Uses scanimage --batch (ADF or glass). Source defaults to the ADF; override with SCANSOURCE=FlatBed. The optional odd/even/reversed args support manual double-sided scanning. Needs convert + pdftk. |
scan2image.sh |
JPG | Flatbed, color, quality 92. Optional 2nd arg = resolution (default 300 dpi). Needs convert. |
Useful environment variables (set in the ini via scan dir, exported manually,
or via Environment= in the systemd unit):
SCANDIR— output directory.SCANSOURCE— scan source forscan2pdf.sh(e.g.FlatBedorAutomatic Document Feeder(left aligned)).
You can point menu entries at your own scripts too — they just receive the
device address as $1 plus whatever extra arguments you configure.
This is a modernised fork of brotherscankeyd by Frank Abelbeck frank.abelbeck@googlemail.com, originally released in 2016.
Frank stopped developing the original on 2020-12-07 and pointed users to its
successor, brotherscankeyd2.
All credit for the design and the original implementation goes to him; this fork
only removes the third-party dependencies (net-snmp/linuxfd) and adapts the
daemon to run from a local directory under systemd.
2016-07-05: initial release as "works for me" version (except for the scan
scripts scan2image.sh and scan2pdf.sh; they are still untested)
2016-07-10: switched to a unified output system (Python module logging and
custom ConsoleHandler for coloured output); moved CONFIGFILE and
PIDFILE to standard locations /etc and /var/run respectively
(program tries both paths); scan2* scripts revisited, now seem to
work; various bug fixes
2016-07-17: debugged program after first server trials; removed SEVERE scan
script quoting errors, altered script calling procedure, introduced
OpenRC init script
2016-07-25: moved SNMP requests into subprocess to avoid blocking the main loop
2016-08-06: fixed a script calling race condition (scanner not immediately ready
for connects of scanimage/scanadt after issuing a notification) by
introducing a two second delay
2017-06-24: removed pysnmp dependencies; SNMP requests are now sent with
/usr/bin/snmpset (http://net-snmp.sourceforge.net/).
2017-07-02: fixed process management: replaced proc.kill() with proc.terminate()
2026-06-10: removed the net-snmp and linuxfd dependencies. SNMP SET requests are
now encoded (BER/ASN.1) and sent over UDP with the standard library;
periodic SNMP refresh is driven by epoll timeouts instead of
linuxfd.timerfd; SIGTERM/SIGINT are caught via signal.set_wakeup_fd
and a socketpair instead of linuxfd.signalfd.
GPLv3. Original program © 2016 Frank Abelbeck. See the COPYING file and run
./brscankeyd.py license.