#!/usr/bin/env python3 # Image to text service # Copyright (C) 2024 Matt "piusbird" Arnold # Licensed under GNU AGPL version 3 import dbus import dbus.service from dbus.mainloop.glib import DBusGMainLoop import os import sys import signal from PIL import Image import tesserocr import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk # noqa class NullDevice: def write(self, s): pass def hup_handle(sig, fr): sys.exit() class NunnallyService(dbus.service.Object): def __init__(self): bus_name = dbus.service.BusName( "club.breadpunk.nunnally", bus=dbus.SessionBus() ) dbus.service.Object.__init__(self, bus_name, "/club/breadpunk/nunnally") self.boardxs = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.work_img = Gtk.Image.new_from_icon_name("process-stop", Gtk.IconSize.MENU) @dbus.service.method("club.breadpunk.nunnally") def ocrImage(self): if not self.image_targets(): return False pixbuf = self.boardxs.wait_for_image() if pixbuf is not None: self.work_img.set_from_pixbuf(pixbuf) ocrtarget = self.pixbuf2image(pixbuf) final_text = tesserocr.image_to_text(ocrtarget) self.boardxs.set_text(final_text, -1) return final_text else: self.boardxs.set_text("No Image in clipboard") return str(pixbuf) @dbus.service.method("club.breadpunk.nunnally") def getPid(self): pidstr = str(os.getpid()) return pidstr def pixbuf2image(self, pix): """Convert gdkpixbuf to PIL image""" data = pix.get_pixels() w = pix.props.width h = pix.props.height stride = pix.props.rowstride mode = "RGB" if pix.props.has_alpha: mode = "RGBA" im = Image.frombytes(mode, (w, h), data, "raw", mode, stride) return im def image_targets(self): # my instincts are telling me to optimize this targets = self.boardxs.wait_for_targets() image_targets = [ "image/png", "image/jpeg", "image/jpg", "image/gif", "image/bmp", "image/tiff", "image/webp", ] target_strings = [str(target) for target in targets[1]] # for image_target in image_targets: if image_target in target_strings: return True return False pid = os.fork() # Hmmm this looks an awful lot like... C # Yes it does per C fork(3) creates a nearly identical copy of the # calling process as a child of the calling process # returning it's pid to the caller. Asexual reproduction at it's finest if pid: os._exit(0) # exit without running exit handlers, that might cause race condition # in the child # Per the fork manual the child begins execution at the point where fork # is called, as if the child had called it. The only difference being # is the child process gets a zero as return value, and so the else branch # of this if is followed. else: # It turns out my CS prof lied about the purpose of these two calls # the child process needs to be the process group leader, when parent # exits or it gets reaped by the init system os.setpgrp() os.umask(0) print(os.getpid()) # to aid in stopping the server # We want to close our connection to the controlling terminal # to avoid accedentially spamming the use. And causing interactive processes # to be SIGSTOP'ed. I do this with a Null Device class. # You could just as easily do some sort of logging thing. sys.stdin.close() sys.stdout = NullDevice() sys.stderr = NullDevice() # The last thing we do before handing things off to the daemon's main # function is set up the daemon's signal table how we want it # fork, may have initialized it with the default handlers # depending on implementation signal.signal(signal.SIGHUP, hup_handle) signal.signal(signal.SIGTERM, hup_handle) # And the main loop begins DBusGMainLoop(set_as_default=True) myservice = NunnallyService() Gtk.main()