clipboard_speaker.py (3371B)
1 #!/usr/bin/env python3 2 3 import argparse 4 import os 5 import sys 6 from pathlib import Path 7 from subprocess import Popen, PIPE 8 9 DEFAULT_WORDS_PER_MINUTE = "175" 10 11 CLIPBOARD_SPEAKER_PATH = Path.home() / ".clipboard-speaker" 12 PID_FILE_PATH = CLIPBOARD_SPEAKER_PATH / "pid" 13 FIFO_FILE_PATH = CLIPBOARD_SPEAKER_PATH / "fifo" 14 15 WORDS_PER_MINUTE_PATH = CLIPBOARD_SPEAKER_PATH / "words-per-minute" 16 17 18 def get_words_per_minute() -> str: 19 if WORDS_PER_MINUTE_PATH.exists(): 20 with WORDS_PER_MINUTE_PATH.open("r") as words_per_minute_file: 21 words_per_minute = words_per_minute_file.read().strip() 22 return words_per_minute 23 else: 24 with WORDS_PER_MINUTE_PATH.open("w") as words_per_minute_file: 25 words_per_minute_file.write(DEFAULT_WORDS_PER_MINUTE) 26 return DEFAULT_WORDS_PER_MINUTE 27 28 29 def get_argparse_options() -> argparse.Namespace: 30 parser = argparse.ArgumentParser() 31 32 group = parser.add_mutually_exclusive_group(required=True) 33 34 group.add_argument( 35 "-p", 36 "--primary", 37 help="Read the mouse selection kind of clipboard.", 38 action="store_true", 39 ) 40 41 group.add_argument( 42 "-b", 43 "--clipboard", 44 help="Read the ctrl-c kind of clipboard.", 45 action="store_true", 46 ) 47 48 args = parser.parse_args() 49 50 return args 51 52 53 def main() -> None: 54 commandline_args = get_argparse_options() 55 56 if not CLIPBOARD_SPEAKER_PATH.exists(): 57 CLIPBOARD_SPEAKER_PATH.mkdir(mode=500) 58 words_per_minute = get_words_per_minute() 59 try: 60 os.mkfifo(FIFO_FILE_PATH, mode=0o600) 61 except FileExistsError: 62 pass 63 64 # https://stackoverflow.com/questions/63132778/how-to-use-fifo-named-pipe-as-stdin-in-popen-python 65 with os.fdopen( 66 os.open(FIFO_FILE_PATH, os.O_RDONLY | os.O_NONBLOCK), "r" 67 ) as fifo_read_file, os.fdopen( 68 os.open(FIFO_FILE_PATH, os.O_WRONLY), "w" 69 ) as fifo_write_file: 70 71 if commandline_args.primary: 72 xsel_process = Popen( 73 ["xsel", "-p"], 74 stdout=PIPE, 75 ) 76 elif commandline_args.clipboard: 77 xsel_process = Popen( 78 ["xsel", "-b"], 79 stdout=PIPE, 80 ) 81 82 # Replace each newline and each consecutive newlines with a single space. 83 message = b" ".join( 84 line.strip() for line in xsel_process.stdout for line in line.split(b"\n") 85 ).decode("utf-8") 86 87 print(message) 88 89 # Write to the fifo with a newline as a good luck token. (it may or may 90 # not be what will make it show up immediately on the other sideā¦) 91 fifo_write_file.write(message + "\n") 92 93 # We make sure that things are written right now by flushing them down. 94 fifo_write_file.flush() 95 96 if not PID_FILE_PATH.exists(): 97 speak_ng_process = Popen( 98 [ 99 "speak-ng", 100 f"-s {words_per_minute}", 101 ], 102 stdin=fifo_read_file, 103 ) 104 105 with PID_FILE_PATH.open("w") as pid_file: 106 pid_file.write(str(speak_ng_process.pid)) 107 108 try: 109 speak_ng_process.wait() 110 except KeyboardInterrupt as e: 111 os.remove(PID_FILE_PATH) 112 113 os.remove(PID_FILE_PATH) 114 115 116 if __name__ == "__main__": 117 main()