from io import BytesIO import logging import threading import time import pasteboard _clipboard_thread = None _callback_update = None _run = False _first_run = False _block_image_once = False _pasteboard_lock = threading.Lock() _pb_writer = None def write_to_pasteboard(data, pb_type): global _pb_writer with _pasteboard_lock: if _pb_writer is None: _pb_writer = pasteboard.Pasteboard() _pb_writer.set_contents(data, pb_type) def _runner(enable_image_monitoring=False, enable_file_monitoring=False): global _first_run, _block_image_once try: pb_text = pasteboard.Pasteboard() if enable_image_monitoring: pb_image_png = pasteboard.Pasteboard() pb_image_tiff = pasteboard.Pasteboard() if enable_file_monitoring: pb_files = pasteboard.Pasteboard() image_processed = False files_processed = False while _run: # don't change the execution order (files,text,image or files,image,text) if enable_file_monitoring: # Files with _pasteboard_lock: clipboard_files = pb_files.get_file_urls(diff=True) if ( clipboard_files is not None and type(clipboard_files) is tuple and len(clipboard_files) > 0 ): if _callback_update and not _first_run: files_processed = True _callback_update("files", clipboard_files) # Text with _pasteboard_lock: clipboard_text = pb_text.get_contents( type=pasteboard.String, diff=True ) # If True, retrieves and returns the content only if it has changed since the last call. # This approach is efficient even in cases of frequent polling. if ( clipboard_text is not None and type(clipboard_text) is str and len(clipboard_text) > 0 ): if _callback_update and not _first_run and not files_processed: _callback_update("text", clipboard_text) if enable_image_monitoring: # Image (PNG) with _pasteboard_lock: clipboard_image_png = pb_image_png.get_contents( type=pasteboard.PNG, diff=True ) if ( clipboard_image_png is not None and type(clipboard_image_png) is bytes ): if _callback_update and not _first_run and not image_processed: image_processed = True if _block_image_once: _block_image_once = False else: if not files_processed: _callback_update("image", clipboard_image_png) # Image (TIFF) with _pasteboard_lock: clipboard_image_tiff = pb_image_tiff.get_contents( type=pasteboard.TIFF, diff=True ) if ( clipboard_image_tiff is not None and type(clipboard_image_tiff) is bytes ): if _callback_update and not _first_run and not image_processed: image_processed = True if _block_image_once: _block_image_once = False else: if not files_processed: _callback_update("image", clipboard_image_tiff) files_processed = False image_processed = False _first_run = False time.sleep(0.3) # seconds except Exception as e: logging.error(f"Error processing clipboard update: {e}") def _start(enable_image_monitoring=False, enable_file_monitoring=False): global _clipboard_thread, _run, _first_run if not _clipboard_thread: _run = True _first_run = True _clipboard_thread = threading.Thread( target=_runner, args=(enable_image_monitoring, enable_file_monitoring), daemon=True, ) _clipboard_thread.start() def stop(): global _clipboard_thread, _callback_update, _run, _first_run, _block_image_once, _pb_writer if _clipboard_thread: _run = False _clipboard_thread.join() # Wait for the thread to finish _first_run = False _clipboard_thread = None _callback_update = None _block_image_once = False _pb_writer = None logging.info("Clipboard monitor stopped") def wait(): global _clipboard_thread if _clipboard_thread: _clipboard_thread.join() def enable_block_image_once(): global _block_image_once _block_image_once = True def on_update(callback, enable_image_monitoring=False, enable_file_monitoring=False): global _callback_update _callback_update = callback _start(enable_image_monitoring, enable_file_monitoring)