The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

GIMP 3.0 vs. XSane vs. gimp-xsanecli

For reasons I do not profess to understand, GIMP 3.0 does not work with plugins written for GIMP 2.0, including the XSane plugin that handles scanning. This seems like an obvious oversight, but after three months it also seems to be one of those things that’s like that and that’s the way it is.

Protracted searching turned up gimp-xsanecli, a GIMP 3.0 plugin invoking XSane through its command-line interface to scan an image into a temporary file, then stuff the file into GIMP. Unfortunately, it didn’t work over the network with the Epson ET-3830 printer / scanner in the basement.

It turns out gimp-xsanecli tells XSane to output the filename it’s using, then expects to find the identifying XSANE_IMAGE_FILENAME string followed by the filename on the first line of whatever it gets back:

if result != 'XSANE_IMAGE_FILENAME: ' + png_out:
  Gimp.message('Unexpected XSane result: ' + result)
  return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])

The font ligature that may or may not mash != into is not under my control.

Protracted poking showed the scanner fires a glob of HTML through proc/stdout into gimp-xsanecli before XSane produces its output, but after the scan completes:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN "
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
… snippage …
</head>
<body><noscript>Enable your browser's JavaScript setting.</noscript></body></HTML>XSANE_IMAGE_FILENAME: /tmp/out.png

Complicating the process:

  • The HTML glob only appears on the first scan, after which XSane produces exactly what gimp-xsanecli expects
  • There is no newline separating the glob from the expected output on the last line

So …

Insert a while loop into the main loop to strip off the HTML glob line by line by line:

        while True:
                # Wait until XSane prints the name of the scanned file, indicating scanning is finished
                # This blocks Python but that is ok because GIMP UI is not affected

                # discard HTML header added by scanner to first scan
                while True :

                        result = proc.stdout.readline().strip()

                        if r'</body>' in result :
                                result = result.partition(r'</HTML>')[-1]
                        #        Gimp.message('Found end of HTML: ' + result)
                                break

                        elif 'XSANE_IMAGE_FILENAME:' in result :
                        #        Gimp.message('Found filename: ' + result)
                                break

                        else :
                        #        Gimp.message('Discarding: ' + result)
                                continue

                if result == '':
                        # XSane was closed
                        break

                if result != 'XSANE_IMAGE_FILENAME: ' + png_out:
                        Gimp.message('Unexpected XSane result: ' + result)
                        return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.EXECUTION_ERROR)])

                # Open image
                image = Gimp.file_load(Gimp.RunMode.NONINTERACTIVE, Gio.File.new_for_path(png_out))
                Gimp.Display.new(image)

                # Remove temporary files
                os.unlink(png_out)

                if not SCAN_MULTIPLE:
                        proc.terminate()
                        break

        os.rmdir(tempdir)

        return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS), GObject.Value(Gimp.Image.__gtype__, image)])

While it’s tempting to absorb the whole thing in one gulp with proc.stdout.read().strip(), that doesn’t work because nothing arrives until the XSane subprocess terminates, which is not what you want.

A scan to show It Just Works™ :

I expect it doesn’t work under a variety of common conditions, but … so far so good.

Comments

7 responses to “GIMP 3.0 vs. XSane vs. gimp-xsanecli”

  1. david Avatar
    david

    ah, yes, the “year of linux on the desktop” I keep hearing about.

    1. Ed Avatar

      The key advantage being when something breaks, I have all the pieces … and generally learn a little more while putting them back together again. As with bike problems, though, things rarely break when I have all my tools in hand. :sigh:

  2. scruss2 Avatar

    The HTML suggests that your scanner thinks it’s connecting to a Windows host via WSD (Web Services for Devices). You might have more luck connecting to it as an eSCL device.

    I’m using xsane-cli with an ET-3850, and haven’t needed to edit anything. I don’t recommend setting DEVICE_NAME in the source: the URLs I’ve tried seem to crash the plugin.

    1. Ed Avatar

      Dunno what’s different, but that’s the ESCL device. It behaves the same way when I select it from the Xsane device dialog; all I did was copy the URL (or whatever it is) from the dialog. Omitting the :443 port produces the same result.

      The other choice is a PID device killing Xsane with a Failed to start scanner: Error during device I/O message.

      Perhaps the broken pieces should fit together differently?

  3. RCPete Avatar
    RCPete

    Hmm, more fun with new software. I purchased a used Latitude 5500 for reasons, and part of those reasons disappeared, so now it’s the crash test dummy for the bleeding edge version of Slackware. Emphasis on crash. A few new things to learn, among others using a generic kernel. No more -huge-, so scripts have to change.

    Part of the fun (for values thereof) is learning the quirks of eLILO. Haven’t gotten to Grub yet, but it’s mightily tempting. OTOH, the FSF info package seems deliberately annoying. Gimme examples!

    I did discover that if you have a kernel panic and the panic timeouts are set to 0, it’s a challenge. No external battery, so I unplugged it and went to bed. Rebooted in the morning on the usb stick, and the battery was at 4%, but it recovered. All my other machines are on Lilo, which has fewer ways to brick the machine for hours. Usually. [grin] Relevant timeouts now set to 60.

    The third-party bootloaders might be OK; perhaps rEFInd, but to start with, I’m going to Grub.

    1. Ed Avatar

      It’s been a long long time since I’ve seen a kernel panic …

  4. RCPete Avatar
    RCPete

    It’s not too hard, especially when I’m trying to do a bootloader with two or more kernels called. vmlinuz and initrd.gz doesn’t help when you need more than one, and a) getting the names and permissions typo-proofed, and b) understanding the peculiar quirks of elilo (the nominal default bootloader for uefi systems in Slackware) entails a fair amount of dragons.

    A USB boot stick has been handy, and if I catch the panic quickly, I can force a reboot.

    The grub info document lacks examples, but they claim that the config tool will be good enough. The crash-test dummy is the only one using UEFI boot right now, and it’s in the midst of downloading the new software. Something that is supported will be much better. Elilo has been more-or-less dead after 2013. The joys of a distribution run by/with a small team.

    OTOH, I think I have the automated kernel installation script rewritten. Doing it by hand found a number of errors. (The default kernel is 6.12.xx right now, but there’s testing for 6.14 and 6.15.) The last time I did a slackware-current machine, kernel updates were fast and furious.