_actions.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import abc
  2. import logging
  3. import random
  4. import time
  5. import typing
  6. import Xlib.protocol.event
  7. import Xlib.X
  8. import Xlib.XK
  9. _XKEYEVENT_TYPE = typing.Union[
  10. Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease
  11. ]
  12. class EngineAction:
  13. def __init__(self, target_engine_index: typing.Optional[int] = None):
  14. self._target_engine_index = target_engine_index
  15. @abc.abstractmethod
  16. def execute_on_window(
  17. self,
  18. overlay: "rescriptoon.Overlay",
  19. xkeyevent: _XKEYEVENT_TYPE,
  20. engine_window: "Xlib.display.Window",
  21. ):
  22. raise NotImplementedError()
  23. def execute(
  24. self, overlay: "rescriptoon.Overlay", xkeyevent: _XKEYEVENT_TYPE,
  25. ):
  26. if self._target_engine_index is None:
  27. for target_window in overlay.engine_windows:
  28. self.execute_on_window(overlay, xkeyevent, target_window)
  29. elif self._target_engine_index >= len(overlay.engine_windows):
  30. logging.warning("target engine index out of bounds")
  31. else:
  32. self.execute_on_window(
  33. overlay, xkeyevent, overlay.engine_windows[self._target_engine_index]
  34. )
  35. class CenterClickAction(EngineAction):
  36. def __init__(self, target_engine_index: int, factor_x: float, factor_y: float):
  37. super().__init__(target_engine_index=target_engine_index,)
  38. self._button = Xlib.X.Button1
  39. self._factor_x = factor_x
  40. self._factor_y = factor_y
  41. def execute_on_window(
  42. self,
  43. overlay: "rescriptoon.Overlay",
  44. xkeyevent: _XKEYEVENT_TYPE,
  45. engine_window: "Xlib.display.Window",
  46. ):
  47. engine_geometry = engine_window.get_geometry()
  48. smaller_dimension = min(engine_geometry.width, engine_geometry.height)
  49. attr = dict(
  50. window=engine_window,
  51. detail=self._button,
  52. state=xkeyevent.state,
  53. event_x=int(engine_geometry.width / 2 + smaller_dimension * self._factor_x),
  54. event_y=int(
  55. engine_geometry.height / 2 + smaller_dimension * self._factor_y
  56. ),
  57. # apparently root_x & root_y do not need to correspond with event_x/y.
  58. # attributes are still required to be set.
  59. root_x=0, # xkeyevent.root_x,
  60. root_y=0, # xkeyevent.root_y,
  61. child=xkeyevent.child,
  62. root=xkeyevent.root,
  63. time=xkeyevent.time, # X.CurrentTime
  64. same_screen=xkeyevent.same_screen,
  65. )
  66. if isinstance(xkeyevent, Xlib.protocol.event.KeyPress):
  67. button_event = Xlib.protocol.event.ButtonPress(**attr)
  68. else:
  69. button_event = Xlib.protocol.event.ButtonRelease(**attr)
  70. engine_window.send_event(button_event)
  71. class SelectGagAction(CenterClickAction):
  72. X_OFFSET = -0.286
  73. X_FACTOR = 0.081
  74. def __init__(self, target_engine_index: int, column_index: int, factor_y: float):
  75. super().__init__(
  76. target_engine_index=target_engine_index,
  77. factor_x=self.X_OFFSET + self.X_FACTOR * column_index,
  78. factor_y=factor_y,
  79. )
  80. def _send_rewritten_xkeyevent(
  81. event_template: _XKEYEVENT_TYPE,
  82. window: "Xlib.display.Window",
  83. event_type: typing.Optional[typing.Type] = None,
  84. keycode=None,
  85. ) -> _XKEYEVENT_TYPE:
  86. if not event_type:
  87. event_type = type(event_template)
  88. window.send_event(
  89. event_type(
  90. window=window,
  91. detail=keycode if keycode else event_template.detail,
  92. state=event_template.state,
  93. root_x=event_template.root_x,
  94. root_y=event_template.root_y,
  95. event_x=event_template.event_x,
  96. event_y=event_template.event_y,
  97. child=event_template.child,
  98. root=event_template.root,
  99. time=event_template.time, # X.CurrentTime
  100. same_screen=event_template.same_screen,
  101. )
  102. )
  103. class RewriteKeyEventAction(EngineAction):
  104. def __init__(self, target_engine_index: typing.Optional[int] = None, keysym=None):
  105. super().__init__(target_engine_index=target_engine_index,)
  106. self._keysym = keysym
  107. def execute_on_window(
  108. self,
  109. overlay: "rescriptoon.Overlay",
  110. xkeyevent: _XKEYEVENT_TYPE,
  111. engine_window: "Xlib.display.Window",
  112. ):
  113. _send_rewritten_xkeyevent(
  114. event_template=xkeyevent,
  115. window=engine_window,
  116. keycode=overlay.xdisplay.keysym_to_keycode(self._keysym),
  117. )
  118. class LowThrowAction(EngineAction):
  119. _THROW_KEYSYM = Xlib.XK.XK_Delete # pylint: no-member
  120. def execute_on_window(
  121. self,
  122. overlay: "rescriptoon.Overlay",
  123. xkeyevent: _XKEYEVENT_TYPE,
  124. engine_window: "Xlib.display.Window",
  125. ):
  126. if isinstance(xkeyevent, Xlib.protocol.event.KeyRelease):
  127. return
  128. keycode = overlay.xdisplay.keysym_to_keycode(self._THROW_KEYSYM)
  129. _send_rewritten_xkeyevent(
  130. event_template=xkeyevent,
  131. window=engine_window,
  132. keycode=keycode,
  133. event_type=Xlib.protocol.event.KeyPress,
  134. )
  135. time.sleep(random.uniform(0.04, 0.08))
  136. _send_rewritten_xkeyevent(
  137. event_template=xkeyevent,
  138. window=engine_window,
  139. keycode=keycode,
  140. event_type=Xlib.protocol.event.KeyRelease,
  141. )
  142. class ToggleOverlayAction:
  143. def execute(self, overlay: "rescriptoon.Overlay", xkeyevent: "Xlib.display.Window"):
  144. if isinstance(xkeyevent, Xlib.protocol.event.KeyPress):
  145. overlay.toggle()