python script blocks mouse events

Hi,

I’m trying to write a macro in python that opens a scanner application and waits for the scanner to produce an image at a specific location in the file system.

This is to replace a similar macro I wrote once in basic, but the python environment is more suitable for integration in this case.

For the basic macro the scanner application was invoked using the “Shell” command. That command would wait for the scanner application to finish and then continue the macro script for further post-processing.

To to something similar in python I tried to use “subprocess.run” instead of basic’s “Shell”. It also waits for the command to complete before it continues.

However when the python macro is executed it seems to block all mouse events until the macro has completely finished. That means the scanner application opens, but I can’t click anything. Not in the scanner interface and even anywhere on screen. The computer stops responding completely to mouse clicks. On the other hand keyboard input is still possible. I can even switch windows using the proper modifier keys. This is however not a reasonable solution.

Next I also tried using libreoffice’s “SystemShellExecute.execute” instead of “subprocess.run”. That command however is asynchronous so it continues the macro code right after the scanner application is launched, not after it has finished. So I added a loop after it to wait for the scanner application to finish. But then again mouse events get ignored.

It seems the issue is not really with the external command being executed. If I replace that part with a simple “time.sleep(10)”, mouse events are ignored for the next 10 seconds.

Is there any way I can have normal mouse events while waiting for a subprocess to finish when running a python macro ?

Some extra details that may be helpful:

  • The document I’m creating the macro for is a libreoffice Writer document
  • The macro is executed when selecting an embedded image element, but the same happens if I add a form with a button and set the macro to fire when the button is clicked
  • LibreOffice version 6.4.7.2 on Fedora 32

I continued to tinker with this and have managed to make it work correctly with a button and running a macro for the mouse-click event. I have no idea why it didn’t work before with the button.

For documentation purposes I’ll outline below what I did exactly:

  1. Create a libreoffice writer document

  2. Add a placeholder image to this document via “Insert->Media->Image…” and name that image “Placeholder” via the document navigator.

  3. Using the Form related toolbars add a form to the document and in the form add a button

  4. Next create a python macro to be run when the button is clicked. I have stored it in "$HOME/.config/libreoffice/4/user/Scripts/python and the gist of the macro is this:

    import uno, unohelper
    import subprocess
    import os
    
    from com.sun.star.beans import PropertyValue
    def createProp(name, value):
        prop = PropertyValue()
        prop.Name = name
        prop.Value = value
        return prop
    
    def bScanButtonclick(oEvent=None):
        ThisDoc = XSCRIPTCONTEXT.getDocument()
    
        # Create Url from system path
        sPath = "<path/to/temporary/image/file.png>"
    
        result = subprocess.run(["<path/to/scan/command>", sPath ])
        if  result.returncode != 0:
            return
    
        # Create graphic properties based on Url
        sUrl = uno.systemPathToFileUrl(sPath)
        props = (createProp("URL", sUrl), createProp("AsLink", "false"),)
        # Load graphic using these properties
        gprov = createUnoService('com.sun.star.graphic.GraphicProvider')
        oImg = gprov.queryGraphic(props)
    
        # Find base image to replace - my document has a placeholder image named "Placeholder"
        oImage = ThisDoc.getGraphicObjects().getByName("Placeholder")
        # Replace
        oImage.Graphic = oImg
        # And resize
        oImage.setSize(oImg.Size100thMM)
        # Remove itermediate scan result
        os.remove(sPath)
    
  5. Link this macro to the button’s “Mouse button released” event.

That’s it. If I now click the button it will open my scanner tool (which is a wrapper script around xsane to automate a few settings). Once the scan is made, the macro will locate the scanned image and use it instead of the placeholder image.

There exists a third party python-interface to xsane, python-sane propably you can avoid the use of subprocess?!

Thanks for the tip. However from my interpretation of the module’s documentation that is a python interface to ‘sane’, not ‘xsane’. I’m using xsane because each scan needs to be manually adjusted by the user (for example position and size on the scannerbed). xsane is a graphical frontend that allows this.

I’ll note there’s also a scan interface in libreoffice itself but it’s very unwieldy. For example it’s very difficult to select a precise scan area based on its preview. And I haven’t found ways to set an ICC color profile for scanning in the libreoffice scaninterface either. So while not perfect, the callout to xsane currently matches our requirements the best.

I ran into identical observations, though from a different starting point. There is a difference in expected behaviour between the BASIC wait command and the python time.sleep(). My conclusion is that the whole LibO program execution is halted when the python sleep command is executed, whereas in BASIC is looks like LibO is still executing tasks in the background.
The (unwanted) python behaviour can be circumvented by invoking a BASIC script with a wait command. Not elegant, but working. When running the next script I can edit for 12 seconds cells in an empty calc document, this is not possible when replacing oDoc.invoke with the time.sleep command
I plan to ask the community about this behaviour in general in a separate post


def study():
sheet = XSCRIPTCONTEXT.getDocument().getSheets().getByIndex(0)
oDoc = getBasicScript(“waitPython”)
for i in range(12):
oDoc.invoke((), (), ())
cell = sheet.getCellByPosition(0,0) # = A1
cell.setString(“overhere”)

def getBasicScript(macro=‘Main’, module=‘Module1’, library=‘Standard’, isEmbedded=False) ->XScript:

if isEmbedded:
    desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)
    scriptPro = desktop.CurrentComponent.getScriptProvider()
    location = "document"
else:
    mspf = smgr.createInstanceWithContext(
        "com.sun.star.script.provider.MasterScriptProviderFactory", ctx)
    scriptPro = mspf.createScriptProvider("")
    location = "application"
scriptName = "vnd.sun.star.script:"+library+"."+module+"."+macro+ \
             "?language=Basic&location="+location
xScript = scriptPro.getScript(scriptName)
return xScript


The basic script in the standard library runs

 
sub waitPython()
    wait(1000)
end Sub