Blender multi-touch addon with pygame

Hello everyone,

I have created multi-touch support using Pygame for my own Blender addon.

This is sample code for anyone trying to do the same. You will need to install the pygame dependency inside blenders python.
To do that, check out this link:
https://blender.stackexchange.com/questions/168448/bundling-python-library-with-addon

This code creates a small window that you can touch with multiple fingers.
The console will display many prints with the finger ID and position in relation to the small window.

import os
import pygame as pg

from mathutils import Vector

import win32gui
import win32con
import win32api
import winxpgui



import bpy


# This makes the touchpad be usable as a multi touch device.
os.environ['SDL_MOUSE_TOUCH_EVENTS'] = '1'

def get_window_rect(hwnd):
    rect = win32gui.GetWindowRect(hwnd)
    x = rect[0]
    y = rect[1]
    w = rect[2] - x
    h = rect[3] - y
    return x,y,w,h



class TOUCH_OT_example(bpy.types.Operator):
    "example of Multi Touch in Blemder"
    bl_idname = "touch.example"
    bl_label = "multi touch example"
    
    _timer = None

    def modal(self, context, event):

        pg.init()    
        width, height = (640, 480)
        screen = pg.display.set_mode((width, height))

        # this is windows gui stuff to make the pygame window transparent
        # stays always on top and inactive so it is not blocking key input
        #region winows gui
        hwnd_touch = pg.display.get_wm_info()["window"]
        
        alpha = 100

        x,y,w,h = get_window_rect(hwnd_touch)

        win32gui.SetWindowLong (hwnd_touch, win32con.GWL_EXSTYLE, win32gui.GetWindowLong (hwnd_touch, win32con.GWL_EXSTYLE ) | win32con.WS_EX_LAYERED )
        winxpgui.SetLayeredWindowAttributes(hwnd_touch, win32api.RGB(0,0,0), alpha, win32con.LWA_ALPHA)
        win32gui.SetWindowPos(hwnd_touch, win32con.HWND_TOP, x, y, w, h, win32con.SWP_NOACTIVATE) 
        
        #endregion
        
        caption = 'Touch'
        pg.display.set_caption(caption)
        
        pg.event.set_grab(False)
        pg.mouse.set_visible(True)

        if event.type == 'TIMER':
            for e in pg.event.get():
                # We look for finger down, finger motion, and then finger up.
                if e.type == pg.FINGERDOWN:

                    touch_pos = Vector((int(width * e.x), int(height-(height * e.y))))
                    print(f" Touch Id: {e.finger_id} Down at pos {touch_pos}")
                elif e.type == pg.FINGERMOTION:
                    touch_pos = Vector((int(width * e.x), int(height-(height * e.y))))
                    print(f" Touch Id: {e.finger_id} Moved at pos {touch_pos}")
                elif e.type == pg.FINGERUP:
                    touch_pos = Vector((int(width * e.x), int(height-(height * e.y))))
                    print(f" Touch Id: {e.finger_id} UP at pos {touch_pos}")


            pg.display.flip()

        if event.type in {'RIGHTMOUSE', 'ESC'}:
             pg.quit()
             self.cancel(context)
             return {'CANCELLED'}
         
        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.01, window=context.window) # Update timer every 0.01 s the touch input gets updated
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)



def register():
    bpy.utils.register_class(TOUCH_OT_example)



def unregister():
    bpy.utils.unregister_class(TOUCH_OT_example)
    
    
if __name__ == "__main__":
    register()

    # test call
    bpy.ops.touch.example()