Coverage for sources / mimeogram / clipboard.py: 18%
26 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-18 17:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-18 17:27 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
4#============================================================================#
5# #
6# Licensed under the Apache License, Version 2.0 (the "License"); #
7# you may not use this file except in compliance with the License. #
8# You may obtain a copy of the License at #
9# #
10# http://www.apache.org/licenses/LICENSE-2.0 #
11# #
12# Unless required by applicable law or agreed to in writing, software #
13# distributed under the License is distributed on an "AS IS" BASIS, #
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15# See the License for the specific language governing permissions and #
16# limitations under the License. #
17# #
18#============================================================================#
21# Clipboard operations module.
22#
23# This module exists to work around a bug in pyperclip where calling
24# subprocess.communicate() on xclip causes the process to hang indefinitely.
25# xclip forks into the background to serve clipboard paste requests and never
26# exits, but pyperclip waits for it to exit. The same bug exists in pyclip.
27#
28# See: https://github.com/asweigart/pyperclip/issues/247
29#
30# Our solution: Call xclip directly on Linux/X11 without waiting for exit.
31# Fall back to pyperclip on other platforms (may still hang on some systems).
34''' Clipboard operations. '''
37import subprocess as _subprocess
39from . import __
42_scribe = __.produce_scribe( __name__ )
45def copy_to_clipboard( text: str ) -> None:
46 ''' Copies text to clipboard. '''
47 # Try xclip first on Linux with X11
48 if __.sys.platform == 'linux' and __.os.environ.get( 'DISPLAY' ):
49 try:
50 # Use xclip directly, don't wait for it to finish
51 # xclip forks into background and stays running to serve pastes
52 proc = _subprocess.Popen(
53 [ 'xclip', '-selection', 'clipboard' ], # noqa: S607
54 stdin = _subprocess.PIPE,
55 stdout = _subprocess.DEVNULL,
56 stderr = _subprocess.DEVNULL,
57 close_fds = True,
58 )
59 except FileNotFoundError:
60 _scribe.debug( "xclip not found, falling back to pyperclip" )
61 except Exception as exc:
62 _scribe.warning(
63 f"xclip failed ({exc}), falling back to pyperclip" )
64 else:
65 assert proc.stdin is not None # noqa: S101
66 proc.stdin.write( text.encode( 'utf-8' ) )
67 proc.stdin.close( )
68 # Don't call proc.wait() or proc.communicate() - let xclip fork
69 _scribe.debug( "Copied to clipboard via xclip" )
70 return
71 # Fall back to pyperclip for other platforms or if xclip fails
72 from pyperclip import copy
73 try:
74 copy( text )
75 except Exception as exc:
76 _scribe.error( f"Failed to copy to clipboard: {exc}" )
77 raise
78 _scribe.debug( "Copied to clipboard via pyperclip" )
81def copy_from_clipboard( ) -> str:
82 ''' Copies text from clipboard. '''
83 from pyperclip import paste
84 return paste( )