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

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

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#============================================================================# 

19 

20 

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). 

32 

33 

34''' Clipboard operations. ''' 

35 

36 

37import subprocess as _subprocess 

38 

39from . import __ 

40 

41 

42_scribe = __.produce_scribe( __name__ ) 

43 

44 

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" ) 

79 

80 

81def copy_from_clipboard( ) -> str: 

82 ''' Copies text from clipboard. ''' 

83 from pyperclip import paste 

84 return paste( )