使用Python管理多个x11窗口

使用Python管理多个x11窗口,python,python-multithreading,x11,xorg,Python,Python Multithreading,X11,Xorg,我正在尝试创建一个类似于微软Fancyzone的基于Python的窗口管理器。此管理器的任务之一是允许用户创建窗口,作为windows可以“捕捉”到的区域的模板。我的想法是,我可以只允许用户创建多个窗口,在“保存”时,模块将缓存窗口几何图形,以便稍后捕捉。我遇到的问题是管理多个窗口。在我的程序中,“add”关键字使一个线程旋转起来,该线程处理单个窗口及其后续事件循环。一个线程就可以了-我们可以在需要的时候缓存几何体。但是,多个线程最终会使程序陷入困境 Q:是否有一个我在这里遗漏的设计范例可以让它

我正在尝试创建一个类似于微软Fancyzone的基于Python的窗口管理器。此管理器的任务之一是允许用户创建窗口,作为windows可以“捕捉”到的区域的模板。我的想法是,我可以只允许用户创建多个窗口,在“保存”时,模块将缓存窗口几何图形,以便稍后捕捉。我遇到的问题是管理多个窗口。在我的程序中,“add”关键字使一个线程旋转起来,该线程处理单个窗口及其后续事件循环。一个线程就可以了-我们可以在需要的时候缓存几何体。但是,多个线程最终会使程序陷入困境

Q:
是否有一个我在这里遗漏的设计范例可以让它得以实现

import json
import os
import sys
import threading

from Xlib import X
from Xlib.display import Display
from Xlib.error import XError, ConnectionClosedError


HERE = os.path.abspath(os.path.dirname(__file__))


def active_window(display, window_id=None):
    if not window_id:
        window_id = display.screen().root.get_full_property(
            display.intern_atom('_NET_ACTIVE_WINDOW'), X.AnyPropertyType
        ).value[0]
    try:
        return display.create_resource_object('window', window_id)
    except XError:
        return None


class Window:
    def __init__(self, display, msg):
        self.display = display
        self.msg = msg
 
        self.screen = self.display.screen()
        self.window = self.screen.root.create_window(
            10, 10, 500, 250, 1,
            self.screen.root_depth,
            background_pixel=self.screen.black_pixel,
            event_mask=X.ExposureMask | X.KeyPressMask,
        )
        self.gc = self.window.create_gc(
            background = self.screen.black_pixel,
        )
        self.window.map()
 
    def loop(self):
        while True:
            try:
                e = self.display.next_event()
                if e.type == X.Expose:
                    self.window.draw_text(self.gc, 10, 50, self.msg)
                elif e.type == X.KeyPress:
                    raise SystemExit
            except ConnectionClosedError:
                # raised when X button is pressed
                # 
                # TODO: figure out way to allow exit of single window without
                # having to kill the entire application (currently get a
                # "socket_error" if you try to add a window after exiting 
                # an already made window)
                #
                # for now, just kill everything
                os._exit(1)

    @property
    def _id(self):
        return self.window.id

class ZoneBuilder:

    def __init__(self) -> None:
        self.zones = []
        self.display = Display()
        self.screen = self.display.screen()

    def _main_action(self):
        return input(
            'specify action:\n$ '
        )

    def main(self):
        action = self._main_action()
        if action == "add":
            self.add

        elif action == "done":
            self.done

        elif action == "exit":
            self.terminate() 

        self.main()

    @property
    def done(self):
        results = []
        for zone, _ in self.zones:
            window = active_window(self.display, zone._id)
            pg = window.query_tree().parent.query_tree().parent.get_geometry()
            results.append({
                "x": pg.x,
                "y": pg.y,
                "width": pg.width,
                "height": pg.height
            })
        self._write(results)
        self.terminate()
            
    @property
    def add(self):
        window = Window(self.display, f"Zone {len(self.zones) + 1}")
        thread = threading.Thread(target=window.loop)
        thread.daemon = True
        thread.start()
        self.zones.append([window, thread])

    def _read(self, path = os.path.join(HERE, "zones.json")):
        with open(path, 'r') as f:
            return json.loads(f.read())

    def _write(self, results, path = os.path.join(HERE, "zones.json")):
        with open(path, 'w') as f:
            f.write(json.dumps(results, indent=2, sort_keys=True))

    def terminate(self):
        sys.exit()

if __name__ == "__main__":
    zb = ZoneBuilder()
    zb.main()