Source code for autokey.scripting.clipboard_wayland

#  Copyright (C) 2026 David King <dave@daveking.com>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#####################################################################

"""
Implementation of the clipboard API functions on a Wayland system
"""

try:
    from .abstract_clipboard import AbstractClipboard
except:
    #  For standalone testing
    pass

import subprocess
import pathlib
import re

try:
    logger = __import__("autokey.logger").logger.get_logger(__name__)
except Exception:
    #  For standalone testing
    import logging
    logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG)
    logger = logging.getLogger(__name__)

# Swap these lines for standalone testing:
#class WaylandClipboard():
[docs] class WaylandClipboard(AbstractClipboard): """ Read write access to the Wayland desktop using the wl-clipboard utility """ def __init__(self, app=None): """ Initialize the Wayland version of the clipboard API Usage: Called when WaylandClipboard is imported :param app: refers to the application instance """ self.app = app
[docs] def fill_clipboard(self, contents: str): """ Copy text into the clipboard Usage: C{clipboard.fill_clipboard(contents)} :param contents: string to be placed in the selection """ try: subprocess.run(['wl-copy', contents], check=True) except subprocess.CalledProcessError: logger.exception('Unexpected error running wl-copy program. AutoKey continues.') return
[docs] def get_clipboard(self): """ Read text from the clipboard Usage: C{clipboard.get_clipboard()} :return: text contents of the clipboard :rtype: C{str}, or C{bytearray} on Python >= 3.13 """ try: proc = subprocess.run(['wl-paste'], check=True, capture_output=True) if proc.stdout is not None: return re.sub(b'[\r\n]*$', b'', proc.stdout) else: logger.warning("No content available from the clipboard") except subprocess.CalledProcessError as e: if e.returncode == 1: logger.warning("No content available from the clipboard") return "" logger.exception('Unexpected error running wl-copy program. AutoKey continues.') return ""
[docs] def fill_selection(self, contents: str): """ Copy text into the selection Usage: C{clipboard.fill_selection(contents)} :param contents: string to be placed in the selection """ try: subprocess.run(['wl-copy', '--primary', contents], check=True) except subprocess.CalledProcessError: logger.exception('Unexpected error running wl-copy program. AutoKey continues.') return
[docs] def get_selection(self): """ Read text from the X selection The selection refers to the currently highlighted text. **Notice:** This is not possible under Wayland. Usage: C{clipboard.get_selection()} :return: text contents of the mouse selection :rtype: C{str} """ try: proc = subprocess.run(['wl-paste', '--primary'], check=True, capture_output=True) if proc.stdout is not None: return re.sub(b'[\r\n]*$', b'', proc.stdout) else: logger.warning("No content selected on desktop") except subprocess.CalledProcessError as e: if e.returncode == 1: logger.warning("No content selected on desktop") return "" logger.exception('Unexpected error running wl-copy program. AutoKey continues.') return ""
[docs] def set_clipboard_image(self, path: str): """ Set clipboard to image Usage: C{clipboard.set_clipboard_image(path)} :param path: Path to image file :raise OSError: If path does not exist """ try: image_file = pathlib.Path(path).expanduser() if image_file.exists(): subprocess.run(f'wl-copy <{image_file}', shell=True, check=True) except subprocess.CalledProcessError: logger.exception('Unexpected error running wl-copy program. AutoKey continues.') return
# For standalone testing if __name__ == '__main__': import datetime logger.debug('Instantiating clipboard') clipboard = WaylandClipboard() logger.debug('Testing the regular clipboard') logger.debug(f'\tThe clipboard\'s current content = "{clipboard.get_clipboard()}"') test_content = f'The current date and time are {datetime.datetime.now()}' logger.debug('\tPushing test string onto clipboard') clipboard.fill_clipboard(test_content) logger.debug('\tPulling text from clipboard') result = clipboard.get_clipboard() if result == test_content: logger.debug('\tThe text pulled matches the text pushed') else: logger.error(f'\tThe text pushed, "{test_content}", does not match the text pulled, "{result}"') logger.debug('\tPushing image file to clipboard, try creating a new image from the cliboard with a tool like GIMP') clipboard.set_clipboard_image('~/src/autokey-wayland/readthedocs/editconfig.jpg') logger.debug('Testing the primary (selection) clipboard') logger.debug(f'\tThe primary clipboard\'s current content = "{clipboard.get_selection()}"') test_content = f'The current date and time are {datetime.datetime.now()}' logger.debug('\tPushing test string onto primary clipboard') clipboard.fill_selection(test_content) logger.debug('\tPulling text from primary clipboard') result = clipboard.get_selection() if result == test_content: logger.debug('\tThe text pulled matches the text pushed') else: logger.error(f'\tThe text pushed, "{test_content}", does not match the text pulled, "{result}"')