Renamed utiols folder; fixed widget error on start; Symbols logic fix
This commit is contained in:
parent
a3496263b9
commit
d073282c66
|
@ -4,10 +4,12 @@ An onscreen keyboard for the mouse.
|
||||||
### Requirements
|
### Requirements
|
||||||
* PyGObject
|
* PyGObject
|
||||||
* python-xlib
|
* python-xlib
|
||||||
|
* pyautogui
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
<li>Get save and execute of custom commands working.</li>
|
<li>Get save and execute of custom commands working.</li>
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
![1 GUI of the alphabet. ](images/pic1.png)
|
![1 image of the alphabet. ](images/pic1.png)
|
||||||
![2 GUI of the symbols. ](images/pic2.png)
|
![2 Image of the symbols. ](images/pic2.png)
|
||||||
|
![3 Image of the emoji. ](images/pic3.png)
|
18
debugger.sh
18
debugger.sh
|
@ -1,18 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# . CONFIG.sh
|
|
||||||
|
|
||||||
# set -o xtrace ## To debug scripts
|
|
||||||
# set -o errexit ## To exit on error
|
|
||||||
# set -o errunset ## To exit if a variable is referenced but not set
|
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )"
|
|
||||||
cd "${SCRIPTPATH}"
|
|
||||||
echo "Working Dir: " $(pwd)
|
|
||||||
|
|
||||||
source '/home/abaddon/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/activate'
|
|
||||||
python -m pudb $(pwd)/src/__main__.py; bash
|
|
||||||
}
|
|
||||||
main "$@";
|
|
BIN
images/pic1.png
BIN
images/pic1.png
Binary file not shown.
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 88 KiB |
BIN
images/pic2.png
BIN
images/pic2.png
Binary file not shown.
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 87 KiB |
Binary file not shown.
After Width: | Height: | Size: 123 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
PyGObject
|
||||||
|
pyautogui
|
||||||
|
python-xlib
|
||||||
|
setproctitle
|
|
@ -6,9 +6,9 @@ import threading
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from utils.pyautogui_control import ControlMixin
|
from libs.pyautogui_control import ControlMixin
|
||||||
from utils.endpoint_registry import EndpointRegistry
|
from libs.endpoint_registry import EndpointRegistry
|
||||||
from utils.event_system import EventSystem
|
from libs.event_system import EventSystem
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
|
||||||
# Python imports
|
# Python imports
|
||||||
import argparse
|
import argparse
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
@ -10,36 +9,38 @@ from setproctitle import setproctitle
|
||||||
import tracemalloc
|
import tracemalloc
|
||||||
tracemalloc.start()
|
tracemalloc.start()
|
||||||
|
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from __builtins__ import *
|
||||||
from app import Application
|
from app import Application
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main(args, unknownargs):
|
||||||
|
setproctitle(f'{app_name}')
|
||||||
|
Application(args, unknownargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
""" Set process title, get arguments, and create GTK main thread. """
|
''' Set process title, get arguments, and create GTK main thread. '''
|
||||||
|
|
||||||
try:
|
|
||||||
# import web_pdb
|
|
||||||
# web_pdb.set_trace()
|
|
||||||
|
|
||||||
setproctitle('Mouse Keyboard')
|
|
||||||
faulthandler.enable() # For better debug info
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
# Add long and short arguments
|
# Add long and short arguments
|
||||||
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
|
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
|
||||||
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
|
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
|
||||||
|
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
|
||||||
|
|
||||||
|
parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.")
|
||||||
|
parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
|
||||||
|
|
||||||
# Read arguments (If any...)
|
# Read arguments (If any...)
|
||||||
args, unknownargs = parser.parse_known_args()
|
args, unknownargs = parser.parse_known_args()
|
||||||
|
|
||||||
Application(args, unknownargs)
|
try:
|
||||||
Gtk.main()
|
faulthandler.enable() # For better debug info
|
||||||
|
main(args, unknownargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
quit()
|
quit()
|
35
src/app.py
35
src/app.py
|
@ -1,20 +1,37 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import inspect
|
import signal
|
||||||
|
import os
|
||||||
|
|
||||||
# Gtk imports
|
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from __builtins__ import *
|
from libs.debugging import debug_signal_handler
|
||||||
from core.window import Window
|
from core.window import Window
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AppLaunchException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Application:
|
||||||
|
""" docstring for Application. """
|
||||||
|
|
||||||
class Application(object):
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
|
super(Application, self).__init__()
|
||||||
|
|
||||||
|
self.setup_debug_hook()
|
||||||
|
Window(args, unknownargs).main()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_debug_hook(self):
|
||||||
try:
|
try:
|
||||||
Window(args, unknownargs)
|
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
||||||
except Exception as e:
|
signal.signal(
|
||||||
raise
|
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"),
|
||||||
|
debug_signal_handler
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
# Typically: ValueError: signal only works in main thread
|
||||||
|
...
|
|
@ -21,6 +21,8 @@ class Keys_Column(Gtk.Box):
|
||||||
super(Keys_Column, self).__init__()
|
super(Keys_Column, self).__init__()
|
||||||
|
|
||||||
self.setup_styling()
|
self.setup_styling()
|
||||||
|
self.setup_signals()
|
||||||
|
self.setup_custom_signals()
|
||||||
self.setup_key_buttons()
|
self.setup_key_buttons()
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
@ -31,6 +33,16 @@ class Keys_Column(Gtk.Box):
|
||||||
self.set_property("homogeneous", True)
|
self.set_property("homogeneous", True)
|
||||||
self.set_hexpand(True)
|
self.set_hexpand(True)
|
||||||
|
|
||||||
|
def setup_signals(self):
|
||||||
|
self.connect("button-release-event", self._on_button_release_event)
|
||||||
|
|
||||||
|
def setup_custom_signals(self):
|
||||||
|
event_system.subscribe("itterate_mode", self.itterate_mode)
|
||||||
|
|
||||||
|
def _on_button_release_event(self, widget = None, eve = None):
|
||||||
|
if eve.button == 3: # NOTE: right-click
|
||||||
|
event_system.emit_and_await("itterate_mode")
|
||||||
|
|
||||||
def setup_key_buttons(self):
|
def setup_key_buttons(self):
|
||||||
keys = keys_set["keys"]
|
keys = keys_set["keys"]
|
||||||
children = keys.keys()
|
children = keys.keys()
|
||||||
|
@ -57,3 +69,15 @@ class Keys_Column(Gtk.Box):
|
||||||
self.add(row_box)
|
self.add(row_box)
|
||||||
|
|
||||||
return row_box
|
return row_box
|
||||||
|
|
||||||
|
def itterate_mode(self):
|
||||||
|
emoji_view_shown = event_system.emit_and_await("is_emoji_view_shown")
|
||||||
|
is_symbols_enabled = event_system.emit_and_await("is_symbols_enabled")
|
||||||
|
|
||||||
|
if not is_symbols_enabled and not emoji_view_shown:
|
||||||
|
event_system.emit("toggle_symbol_keys")
|
||||||
|
elif is_symbols_enabled and not emoji_view_shown:
|
||||||
|
event_system.emit("show_emoji_view")
|
||||||
|
elif is_symbols_enabled and emoji_view_shown:
|
||||||
|
event_system.emit("hide_emoji_view")
|
||||||
|
event_system.emit("toggle_symbol_keys")
|
||||||
|
|
|
@ -20,18 +20,6 @@ class Esc_Key(Key):
|
||||||
def setup_signals(self):
|
def setup_signals(self):
|
||||||
self.connect("released", self._do_press_special_key)
|
self.connect("released", self._do_press_special_key)
|
||||||
|
|
||||||
class Symbols_Key(Key):
|
|
||||||
def __init__(self):
|
|
||||||
super(Symbols_Key, self).__init__("Symbols", "Symbols", iscontrol=True)
|
|
||||||
|
|
||||||
def setup_signals(self):
|
|
||||||
self.connect("released", self._clicked)
|
|
||||||
|
|
||||||
def _clicked(self, widget = None):
|
|
||||||
ctx = widget.get_style_context()
|
|
||||||
ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else ctx.add_class("toggled_bttn")
|
|
||||||
event_system.emit("toggle_symbol_keys")
|
|
||||||
|
|
||||||
class CAPS_Key(Key):
|
class CAPS_Key(Key):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CAPS_Key, self).__init__("Caps", "Caps", iscontrol = True)
|
super(CAPS_Key, self).__init__("Caps", "Caps", iscontrol = True)
|
||||||
|
@ -80,8 +68,26 @@ class Emoji_Key(Key):
|
||||||
def unset_selected(self, widget = None):
|
def unset_selected(self, widget = None):
|
||||||
self._ctx.remove_class("toggled_bttn")
|
self._ctx.remove_class("toggled_bttn")
|
||||||
|
|
||||||
|
class Symbols_Key(Key):
|
||||||
|
def __init__(self):
|
||||||
|
super(Symbols_Key, self).__init__("Symbols", "Symbols", iscontrol = True)
|
||||||
|
self.setup_custom_signals()
|
||||||
|
|
||||||
|
def setup_signals(self):
|
||||||
|
self.connect("released", self._clicked)
|
||||||
|
|
||||||
|
def setup_custom_signals(self):
|
||||||
|
event_system.subscribe("is_symbols_enabled", self.is_symbols_enabled)
|
||||||
|
event_system.subscribe("toggle_symbol_keys", self.toggle_symbol_keys)
|
||||||
|
|
||||||
|
def _clicked(self, widget = None):
|
||||||
|
ctx = widget.get_style_context()
|
||||||
|
ctx.remove_class("toggled_bttn") if ctx.has_class("toggled_bttn") else ctx.add_class("toggled_bttn")
|
||||||
|
event_system.emit("toggle_symbol_keys")
|
||||||
|
|
||||||
|
def is_symbols_enabled(self):
|
||||||
|
ctx = self.get_style_context()
|
||||||
|
return True if ctx.has_class("toggled_bttn") else False
|
||||||
|
|
||||||
class Enter_Key(Key):
|
class Enter_Key(Key):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -49,12 +49,16 @@ class Emoji_Notebook(Gtk.Notebook):
|
||||||
for group in emoji_grouping:
|
for group in emoji_grouping:
|
||||||
tab_widget = Gtk.Label(label=group)
|
tab_widget = Gtk.Label(label=group)
|
||||||
scroll, grid = self.create_scroll_and_grid()
|
scroll, grid = self.create_scroll_and_grid()
|
||||||
|
self.append_page(scroll, tab_widget)
|
||||||
|
self.set_tab_reorderable(scroll, False)
|
||||||
|
self.set_tab_detachable(scroll, False)
|
||||||
|
|
||||||
top = 0
|
top = 0
|
||||||
left = 0
|
left = 0
|
||||||
for emoji in emoji_grouping[group]:
|
for emoji in emoji_grouping[group]:
|
||||||
key = Key(emoji["emoji"], emoji["emoji"])
|
key = Key(emoji["emoji"], emoji["emoji"])
|
||||||
key._is_emoji = True
|
key._is_emoji = True
|
||||||
|
key.show()
|
||||||
grid.attach(key, left, top, width, height)
|
grid.attach(key, left, top, width, height)
|
||||||
|
|
||||||
left += 1
|
left += 1
|
||||||
|
@ -62,9 +66,6 @@ class Emoji_Notebook(Gtk.Notebook):
|
||||||
left = 0
|
left = 0
|
||||||
top += 1
|
top += 1
|
||||||
|
|
||||||
self.append_page(scroll, tab_widget)
|
|
||||||
self.set_tab_reorderable(scroll, False)
|
|
||||||
self.set_tab_detachable(scroll, False)
|
|
||||||
|
|
||||||
def create_scroll_and_grid(self):
|
def create_scroll_and_grid(self):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
|
@ -83,8 +84,9 @@ class Emoji_Popover(Gtk.Popover):
|
||||||
|
|
||||||
emoji_notebook = Emoji_Notebook()
|
emoji_notebook = Emoji_Notebook()
|
||||||
self.add(emoji_notebook)
|
self.add(emoji_notebook)
|
||||||
self.set_default_widget(emoji_notebook)
|
|
||||||
self.setup_styling()
|
self.setup_styling()
|
||||||
|
self.setup_signals()
|
||||||
|
self.setup_custom_signals()
|
||||||
|
|
||||||
self._emoji_key = None
|
self._emoji_key = None
|
||||||
|
|
||||||
|
@ -94,8 +96,22 @@ class Emoji_Popover(Gtk.Popover):
|
||||||
self.set_size_request(480, 280)
|
self.set_size_request(480, 280)
|
||||||
|
|
||||||
def setup_signals(self):
|
def setup_signals(self):
|
||||||
self.connect("closed", self._emoji_key.unset_selected)
|
...
|
||||||
|
|
||||||
|
def setup_custom_signals(self):
|
||||||
|
event_system.subscribe("is_emoji_view_shown", self.is_emoji_view_shown)
|
||||||
|
event_system.subscribe("show_emoji_view", self.show_emoji_view)
|
||||||
|
event_system.subscribe("hide_emoji_view", self.hide_emoji_view)
|
||||||
|
|
||||||
def set_parent_key(self, emoji_key):
|
def set_parent_key(self, emoji_key):
|
||||||
self._emoji_key = emoji_key
|
self._emoji_key = emoji_key
|
||||||
self.setup_signals()
|
self.setup_signals()
|
||||||
|
|
||||||
|
def is_emoji_view_shown(self):
|
||||||
|
return self.is_visible()
|
||||||
|
|
||||||
|
def show_emoji_view(self):
|
||||||
|
self.popup()
|
||||||
|
|
||||||
|
def hide_emoji_view(self):
|
||||||
|
self.popdown()
|
||||||
|
|
|
@ -67,3 +67,6 @@ class Window(SignalsMixin, Gtk.ApplicationWindow):
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
cr.paint()
|
cr.paint()
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
cr.set_operator(cairo.OPERATOR_OVER)
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
Gtk.main()
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Break into a Python console upon SIGUSR1 (Linux) or SIGBREAK (Windows:
|
||||||
|
# CTRL+Pause/Break). To be included in all production code, just in case.
|
||||||
|
def debug_signal_handler(signal, frame):
|
||||||
|
del signal
|
||||||
|
del frame
|
||||||
|
|
||||||
|
try:
|
||||||
|
import rpdb2
|
||||||
|
logger.debug("\n\nStarting embedded RPDB2 debugger. Password is 'foobar'\n\n")
|
||||||
|
rpdb2.start_embedded_debugger("foobar", True, True)
|
||||||
|
rpdb2.setbreak(depth=1)
|
||||||
|
return
|
||||||
|
except StandardError:
|
||||||
|
...
|
||||||
|
|
||||||
|
try:
|
||||||
|
from rfoo.utils import rconsole
|
||||||
|
logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
|
||||||
|
rconsole.spawn_server()
|
||||||
|
return
|
||||||
|
except StandardError as ex:
|
||||||
|
...
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pudb import set_trace
|
||||||
|
logger.debug("\n\nStarting PuDB debugger...\n\n")
|
||||||
|
set_trace(paused = True)
|
||||||
|
return
|
||||||
|
except StandardError as ex:
|
||||||
|
...
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pdb
|
||||||
|
logger.debug("\n\nStarting embedded PDB debugger...\n\n")
|
||||||
|
pdb.Pdb(skip=['gi.*']).set_trace()
|
||||||
|
return
|
||||||
|
except StandardError as ex:
|
||||||
|
...
|
||||||
|
|
||||||
|
try:
|
||||||
|
import code
|
||||||
|
code.interact()
|
||||||
|
except StandardError as ex:
|
||||||
|
logger.debug(f"{ex}, returning to normal program flow...")
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Python imports
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EventSystem(Singleton):
|
||||||
|
""" Create event system. """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.subscribers = defaultdict(list)
|
||||||
|
self._is_paused = False
|
||||||
|
|
||||||
|
self._subscribe_to_events()
|
||||||
|
|
||||||
|
|
||||||
|
def _subscribe_to_events(self):
|
||||||
|
self.subscribe("pause_event_processing", self._pause_processing_events)
|
||||||
|
self.subscribe("resume_event_processing", self._resume_processing_events)
|
||||||
|
|
||||||
|
def _pause_processing_events(self):
|
||||||
|
self._is_paused = True
|
||||||
|
|
||||||
|
def _resume_processing_events(self):
|
||||||
|
self._is_paused = False
|
||||||
|
|
||||||
|
def subscribe(self, event_type, fn):
|
||||||
|
self.subscribers[event_type].append(fn)
|
||||||
|
|
||||||
|
def unsubscribe(self, event_type, fn):
|
||||||
|
self.subscribers[event_type].remove(fn)
|
||||||
|
|
||||||
|
def unsubscribe_all(self, event_type):
|
||||||
|
self.subscribers.pop(event_type, None)
|
||||||
|
|
||||||
|
def emit(self, event_type, data = None):
|
||||||
|
if self._is_paused and event_type != "resume_event_processing":
|
||||||
|
return
|
||||||
|
|
||||||
|
if event_type in self.subscribers:
|
||||||
|
for fn in self.subscribers[event_type]:
|
||||||
|
if data:
|
||||||
|
if hasattr(data, '__iter__') and not type(data) is str:
|
||||||
|
fn(*data)
|
||||||
|
else:
|
||||||
|
fn(data)
|
||||||
|
else:
|
||||||
|
fn()
|
||||||
|
|
||||||
|
def emit_and_await(self, event_type, data = None):
|
||||||
|
if self._is_paused and event_type != "resume_event_processing":
|
||||||
|
return
|
||||||
|
|
||||||
|
""" NOTE: Should be used when signal has only one listener and vis-a-vis """
|
||||||
|
if event_type in self.subscribers:
|
||||||
|
response = None
|
||||||
|
for fn in self.subscribers[event_type]:
|
||||||
|
if data:
|
||||||
|
if hasattr(data, '__iter__') and not type(data) is str:
|
||||||
|
response = fn(*data)
|
||||||
|
else:
|
||||||
|
response = fn(data)
|
||||||
|
else:
|
||||||
|
response = fn()
|
||||||
|
|
||||||
|
if not response in (None, ''):
|
||||||
|
break
|
||||||
|
|
||||||
|
return response
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton:
|
||||||
|
ccount = 0
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
obj = super(Singleton, cls).__new__(cls)
|
||||||
|
cls.ccount += 1
|
||||||
|
|
||||||
|
if cls.ccount == 2:
|
||||||
|
raise SingletonError(f"Exceeded {cls.__name__} instantiation limit...")
|
||||||
|
|
||||||
|
return obj
|
|
@ -1,30 +0,0 @@
|
||||||
# Python imports
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class EventSystem:
|
|
||||||
""" Create event system. """
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.subscribers = defaultdict(list)
|
|
||||||
|
|
||||||
|
|
||||||
def subscribe(self, event_type, fn):
|
|
||||||
self.subscribers[event_type].append(fn)
|
|
||||||
|
|
||||||
def emit(self, event_type, data = None):
|
|
||||||
if event_type in self.subscribers:
|
|
||||||
for fn in self.subscribers[event_type]:
|
|
||||||
if data:
|
|
||||||
if hasattr(data, '__iter__') and not type(data) is str:
|
|
||||||
fn(*data)
|
|
||||||
else:
|
|
||||||
fn(data)
|
|
||||||
else:
|
|
||||||
fn()
|
|
Loading…
Reference in New Issue