#!/usr/bin/env python3 # GIMP 3.0 plug-in for scanning via XSane # Copyright (C) 2024 Lee Yingtong Li (RunasSudo) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import gi gi.require_version('Gimp', '3.0') from gi.repository import Gimp from gi.repository import GObject from gi.repository import Gio import os import subprocess import sys import tempfile DEVICE_NAME = None # e.g. DEVICE_NAME = 'escl:http://10.0.0.1:80' def xsanecli_run(procedure, config, run_data, *args): # Get temporary directory tempdir = tempfile.mkdtemp('gimp-plugin-xsanecli') png_out = os.path.join(tempdir, 'out.png') # Open XSane args = ['xsane', '--save', '--no-mode-selection', '--force-filename', png_out, '--print-filenames'] + ([DEVICE_NAME] if DEVICE_NAME else []) proc = subprocess.Popen(args, stdout=subprocess.PIPE, encoding='utf-8') # 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 result = proc.stdout.readline().strip() proc.terminate() if result == '': # XSane was closed return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.CANCEL)]) if result != 'XSANE_IMAGE_FILENAME: ' + png_out: Gimp.message('Unexpected XSane 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) os.rmdir(tempdir) return Gimp.ValueArray.new_from_values([GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS), GObject.Value(Gimp.Image.__gtype__, image)]) class XSaneCLI(Gimp.PlugIn): ## GimpPlugIn virtual methods ## def do_set_i18n(self, procname): return False def do_query_procedures(self): return ['plug-in-xsanecli'] def do_create_procedure(self, name): if name == 'plug-in-xsanecli': procedure = Gimp.Procedure.new(self, name, Gimp.PDBProcType.PLUGIN, xsanecli_run, None, None) procedure.set_menu_label('_XSane via CLI...') procedure.add_menu_path('/File/Create') procedure.add_enum_argument('run-mode', 'Run mode', 'The run mode', Gimp.RunMode.__gtype__, Gimp.RunMode.NONINTERACTIVE, GObject.ParamFlags.READWRITE) procedure.add_image_return_value('image', 'Image', 'Output image', False, GObject.ParamFlags.READWRITE) else: raise Exception('Unknown procedure') procedure.set_attribution( 'Lee Yingtong Li (RunasSudo)', # Author 'Lee Yingtong Li (RunasSudo)', # Copyright 'Lee Yingtong Li (RunasSudo)' # Year ) return procedure Gimp.main(XSaneCLI.__gtype__, sys.argv)