bash-stuff/python3-pkg-efck/output.py

197 lines
7.2 KiB
Python

import logging
import os
import shutil
import subprocess
from tempfile import NamedTemporaryFile
from . import IS_MACOS, IS_X11, IS_WAYLAND, IS_WIDOWS, PLATFORM
from .qt import *
logger = logging.getLogger(__name__)
def type_chars(text: str, force_clipboard):
TYPEOUT_COMMANDS = (
(IS_X11, ['xdotool', 'type', text]),
(IS_X11 or IS_WAYLAND, ['ydotool', 'type', '--next-delay', '0', '--key-delay', '0', text]),
(IS_X11 or IS_WAYLAND, ['wtype', text]),
(IS_MACOS, lambda: _type_macos(text)),
(IS_WIDOWS, lambda: _type_windos(text))
)
once = False
res = -1 # 0/Falsy = success
for cond, args in TYPEOUT_COMMANDS:
if cond:
if not once:
logger.info('Typing out text: %s', text)
once = True
if callable(args):
res = args()
break
if not shutil.which(args[0]):
logger.warning('Platform "%s" but command "%s" unavailable', PLATFORM, args[0])
continue
logger.info('Executing: %s', args)
proc = subprocess.run(args)
res = proc.returncode
if not res:
break
logger.warning('Subprocess exit code/error: %s', res)
else:
logger.error('No command applies. Please see above for additional warnings.')
if res == 0 and not force_clipboard:
# Supposedly we're done
return
# Otherwise
_copy_to_clipboard(text)
def _copy_to_clipboard(text):
# Copy the emoji to global system clipboard
QApplication.instance().clipboard().setText(text, QClipboard.Mode.Clipboard)
assert QApplication.instance().clipboard().text(QClipboard.Mode.Clipboard) == text
logger.info('Text copied to clipboard: %s', text)
# And raise a desktop notification about it ...
"""
# Export emoji to icon for the notification
qicon, icon_fname = None, None
try:
text_is_emoji = len(text) == 1
if text_is_emoji:
def _emoji_to_qicon(text, filename):
pixmap = QPixmap(128, 128)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
from .tabs import EmojiTab
font = QFont(EmojiTab.Delegate.ICON_FONT)
font.setPixelSize(int(round(pixmap.rect().height() * .95)))
painter.setFont(font)
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, text)
del painter # https://stackoverflow.com/a/59605570/1090455
res = pixmap.toImage().save(filename)
if not res:
logger.warning('Failed to save emoji icon to "%s"', filename)
return QIcon(pixmap)
with NamedTemporaryFile(prefix=QApplication.instance().applicationName() + '-',
suffix='.png', delete=False) as fd:
qicon = _emoji_to_qicon(text, fd.name)
icon_fname = fd.name
notification_msg = [QApplication.instance().applicationName(),
f'Text {text!r} copied to clipboard.']
if shutil.which('notify-send'):
# XDG notification looks better than QSystemTrayIcon message, so try it first
subprocess.run(['notify-send',
'--expire-time', '5000',
'--icon', icon_fname or 'info',
'--urgency', 'low',
*notification_msg])
elif QSystemTrayIcon.isSystemTrayAvailable():
from efck.gui import ICON_DIR
tray = QSystemTrayIcon(QIcon(QPixmap(str(ICON_DIR / 'awesome-emoji.png'))),
QApplication.instance(), visible=True)
if qicon:
notification_msg.append(qicon) # FIXME: Why isn't qicon shown in the tooltip???
# notification_msg.append(QSystemTrayIcon.Information) # Yet this works
tray.showMessage(*notification_msg, msecs=5000)
else:
logger.warning('No desktop notification system (notify-send) or systray detected')
finally:
if icon_fname:
os.remove(icon_fname)
"""
def _type_macos(text):
# https://apple.stackexchange.com/questions/171709/applescript-get-active-application
# https://stackoverflow.com/questions/41673019/insert-emoji-into-focused-text-input
# https://stackoverflow.com/questions/60385810/inserting-chinese-characters-in-applescript
# https://apple.stackexchange.com/questions/288536/is-it-possible-to-keystroke-special-characters-in-applescript
_OSASCRIPT = '''
set prev_contents to the clipboard
set the clipboard to "{}"
tell application "System Events"
keystroke "v" using command down
key code 124 -- right arrow clears the selection
delay 0.05
end tell
set the clipboard to prev_contents
'''
text = text.replace('"', '\\"')
proc = subprocess.run(['sh', '-c', f"sleep .2 && nohup osascript -e '{_OSASCRIPT.format(text)}' & disown"])
return proc.returncode
def _type_windos(text):
import ctypes
from ctypes.wintypes import DWORD, LONG, WORD, PULONG
# God help us!
# Adapted from:
# https://github.com/Drov3r/Forza/blob/b3e489c1447f2fdc46081e053008aaa1ab77a12a/control.py#L63
# https://stackoverflow.com/questions/22291282/using-sendinput-to-send-unicode-characters-beyond-uffff
class KeyboardInput(ctypes.Structure):
_fields_ = [("wVk", WORD),
("wScan", WORD),
("dwFlags", DWORD),
("time", DWORD),
("dwExtraInfo", PULONG)]
class HardwareInput(ctypes.Structure):
_fields_ = [("uMsg", DWORD),
("wParamL", WORD),
("wParamH", WORD)]
class MouseInput(ctypes.Structure):
_fields_ = [("dx", LONG),
("dy", LONG),
("mouseData", DWORD),
("dwFlags", DWORD),
("time", DWORD),
("dwExtraInfo", PULONG)]
class Input_I(ctypes.Union):
_fields_ = [("ki", KeyboardInput),
("mi", MouseInput),
("hi", HardwareInput)]
class Input(ctypes.Structure):
_fields_ = [("type", DWORD),
("ii", Input_I)]
def as_wchars(ch):
assert len(ch) == 1, (ch, len(ch))
bytes = ch.encode('utf-16be')
while bytes:
yield int.from_bytes(bytes[:2], 'big')
bytes = bytes[2:]
TYPE_KEYBOARD_INPUT = 1
KEYEVENTF_UNICODE = 0x4
KEYEVENTF_KEYUP = 0x2
KEYEVENTF_KEYDOWN = 0
inputs = []
for ch in text:
for wch in as_wchars(ch):
for flag in (KEYEVENTF_KEYDOWN,
KEYEVENTF_KEYUP):
input = Input(TYPE_KEYBOARD_INPUT)
# XXX: Assuming ctypes.Structure is memset to 0 at initialization?
input.ii.ki.wScan = WORD(wch)
input.ii.ki.dwFlags = KEYEVENTF_UNICODE | flag
inputs.append(input)
inputs = (Input * len(inputs))(*inputs)
n = ctypes.windll.user32.SendInput(len(inputs), inputs, ctypes.sizeof(Input))
assert n == len(inputs)
return 0