nunnally/nunnally.py
2025-09-04 10:08:47 -04:00

139 lines
4.1 KiB
Python
Executable File

#!/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()