11 changed files with 409 additions and 4 deletions
@ -1,10 +1,15 @@ |
|||||||
- [ ] Handle Logs |
- [ ] Handle Logs |
||||||
- [ ] Terminal Helper |
- [ ] Terminal Helper |
||||||
|
- [ ] Events |
||||||
|
- [x] Window : SIGWINCH triggered when the terminal is resized |
||||||
- [ ] Input Class |
- [ ] Input Class |
||||||
|
- [ ] Return Error if Mouse RE does not match |
||||||
|
- [x] Handle the Paste Buffer |
||||||
|
- [ ] Handle the middle button mouse paste |
||||||
|
- [ ] Handle Special Keys (UP, Down, . . .) |
||||||
- [ ] Events |
- [ ] Events |
||||||
https://tkinterexamples.com/events/events.html |
https://tkinterexamples.com/events/events.html |
||||||
https://www.pythontutorial.net/tkinter/tkinter-event-binding/ |
https://www.pythontutorial.net/tkinter/tkinter-event-binding/ |
||||||
- [ ] Keyboard |
- [x] Keyboard |
||||||
- [ ] Mouse |
- [x] Mouse |
||||||
- [ ] Window |
|
||||||
- [ ] Canvas Class |
- [ ] Canvas Class |
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,39 @@ |
|||||||
|
import sys, os |
||||||
|
import logging |
||||||
|
|
||||||
|
sys.path.append(os.path.join(sys.path[0],'..')) |
||||||
|
import ttk.libbpytop as lbt |
||||||
|
import ttk |
||||||
|
|
||||||
|
def message_handler(mode, context, message): |
||||||
|
if mode == ttk.InfoMsg: mode = 'INFO' |
||||||
|
elif mode == ttk.WarningMsg: mode = 'WARNING' |
||||||
|
elif mode == ttk.CriticalMsg: mode = 'CRITICAL' |
||||||
|
elif mode == ttk.FatalMsg: mode = 'FATAL' |
||||||
|
else: mode = 'DEBUG' |
||||||
|
logging.debug(f"{mode} {context.file} {message}") |
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG, |
||||||
|
format='(%(threadName)-9s) %(message)s',) |
||||||
|
ttk.installMessageHandler(message_handler) |
||||||
|
|
||||||
|
ttk.info("Retrieve Keyboard, Mouse press/drag/wheel Events") |
||||||
|
ttk.info("Press q or <ESC> to exit") |
||||||
|
|
||||||
|
lbt.Term.push(lbt.Term.mouse_on) |
||||||
|
lbt.Term.echo(False) |
||||||
|
|
||||||
|
def keyCallback(kevt=None, mevt=None): |
||||||
|
if kevt is not None: |
||||||
|
ttk.info(f"Key Event: {kevt}") |
||||||
|
if mevt is not None: |
||||||
|
ttk.info(f"Mouse Event: {mevt}") |
||||||
|
|
||||||
|
def winCallback(width, height): |
||||||
|
ttk.info(f"Resize: w:{width}, h:{height}") |
||||||
|
|
||||||
|
lbt.Term.registerResizeCb(winCallback) |
||||||
|
lbt.Input.get_key(keyCallback) |
||||||
|
|
||||||
|
lbt.Term.push(lbt.Term.mouse_off, lbt.Term.mouse_direct_off) |
||||||
|
lbt.Term.echo(True) |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
This is a testing file with some UTF-8 chars: |
||||||
|
x x x x x x x x x x x x |
||||||
|
£ @ £ ¬ ` 漢 _ _ あ _ _ |
||||||
|
x x x x x xx x x xx x x |
||||||
@ -0,0 +1,2 @@ |
|||||||
|
from .input import * |
||||||
|
from .term import * |
||||||
@ -0,0 +1,198 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
# Copyright 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||||
|
# Copyright 2020 Aristocratos (https://github.com/aristocratos/bpytop) |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
|
||||||
|
import os, sys, io, threading, signal, re, subprocess, logging, logging.handlers, argparse |
||||||
|
import re |
||||||
|
import queue |
||||||
|
from select import select |
||||||
|
from time import time, sleep, strftime, localtime |
||||||
|
from typing import TextIO, List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable, Type, NamedTuple |
||||||
|
|
||||||
|
try: import fcntl, termios, tty, pwd |
||||||
|
except Exception as e: |
||||||
|
print(f'ERROR: {e}') |
||||||
|
exit(1) |
||||||
|
|
||||||
|
class MouseEvent: |
||||||
|
# Keys |
||||||
|
NoButton = 0x00000000 # The button state does not refer to any button (see QMouseEvent::button()). |
||||||
|
AllButtons = 0x07ffffff # This value corresponds to a mask of all possible mouse buttons. Use to set the 'acceptedButtons' property of a MouseArea to accept ALL mouse buttons. |
||||||
|
LeftButton = 0x00000001 # The left button is pressed, or an event refers to the left button. (The left button may be the right button on left-handed mice.) |
||||||
|
RightButton = 0x00000002 # The right button. |
||||||
|
MidButton = 0x00000004 # The middle button. |
||||||
|
MiddleButton = MidButton # The middle button. |
||||||
|
Wheel = 0x00000008 |
||||||
|
|
||||||
|
# Events |
||||||
|
NoEvent = 0x00000000 |
||||||
|
Press = 0x00010000 |
||||||
|
Release = 0x00020000 |
||||||
|
Drag = 0x00040000 |
||||||
|
Move = 0x00080000 |
||||||
|
Up = 0x00100000 # Wheel Up |
||||||
|
Down = 0x00200000 # Wheel Down |
||||||
|
|
||||||
|
__slots__ = ('x','y','key','evt','raw') |
||||||
|
def __init__(self, x: int, y: int, key: int, evt: int, raw: str): |
||||||
|
self.x = x |
||||||
|
self.y = y |
||||||
|
self.key = key |
||||||
|
self.evt = evt |
||||||
|
self.raw = raw |
||||||
|
|
||||||
|
def key2str(self): |
||||||
|
return { |
||||||
|
MouseEvent.NoButton : "NoButton", |
||||||
|
MouseEvent.AllButtons : "AllButtons", |
||||||
|
MouseEvent.LeftButton : "LeftButton", |
||||||
|
MouseEvent.RightButton : "RightButton", |
||||||
|
MouseEvent.MidButton : "MidButton", |
||||||
|
MouseEvent.MiddleButton : "MiddleButton", |
||||||
|
MouseEvent.Wheel : "Wheel", |
||||||
|
}.get(self.key, "Undefined") |
||||||
|
|
||||||
|
def evt2str(self): |
||||||
|
return { |
||||||
|
MouseEvent.NoEvent : "NoEvent", |
||||||
|
MouseEvent.Press : "Press", |
||||||
|
MouseEvent.Release : "Release", |
||||||
|
MouseEvent.Drag : "Drag", |
||||||
|
MouseEvent.Move : "Move", |
||||||
|
MouseEvent.Up : "Up", |
||||||
|
MouseEvent.Down : "Down", |
||||||
|
}.get(self.evt, "Undefined") |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return f"MouseEvent ({self.x},{self.y}) {self.key2str()} {self.evt2str()} - {self.raw}" |
||||||
|
|
||||||
|
class KeyEvent: |
||||||
|
__slots__ = ('id', 'key') |
||||||
|
def __init__(self, id:int, key: str): |
||||||
|
self.id = id |
||||||
|
self.key = key |
||||||
|
def __str__(self): |
||||||
|
return f"KeyEvent: {self.key}" |
||||||
|
|
||||||
|
class Input: |
||||||
|
class _nonblocking(object): |
||||||
|
"""Set nonblocking mode for device""" |
||||||
|
def __init__(self, stream: TextIO): |
||||||
|
self.stream = stream |
||||||
|
self.fd = self.stream.fileno() |
||||||
|
def __enter__(self): |
||||||
|
self.orig_fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) |
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl | os.O_NONBLOCK) |
||||||
|
def __exit__(self, *args): |
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl) |
||||||
|
|
||||||
|
class _raw(object): |
||||||
|
"""Set raw input mode for device""" |
||||||
|
def __init__(self, stream: TextIO): |
||||||
|
self.stream = stream |
||||||
|
self.fd = self.stream.fileno() |
||||||
|
def __enter__(self): |
||||||
|
self.original_stty = termios.tcgetattr(self.stream) |
||||||
|
tty.setcbreak(self.stream) |
||||||
|
def __exit__(self, type, value, traceback): |
||||||
|
termios.tcsetattr(self.stream, termios.TCSANOW, self.original_stty) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def get_key(callback=None): |
||||||
|
input_key: str = "" |
||||||
|
mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])") |
||||||
|
|
||||||
|
while not False: |
||||||
|
with Input._raw(sys.stdin): |
||||||
|
#if not select([sys.stdin], [], [], 0.1)[0]: #* Wait 100ms for input on stdin then restart loop to check for stop flag |
||||||
|
# continue |
||||||
|
input_key += sys.stdin.read(1) #* Read 1 key safely with blocking on |
||||||
|
if input_key == "\033": #* If first character is a escape sequence keep reading |
||||||
|
with Input._nonblocking(sys.stdin): #* Set non blocking to prevent read stall |
||||||
|
input_key += sys.stdin.read(20) |
||||||
|
if input_key.startswith("\033[<"): |
||||||
|
_ = sys.stdin.read(1000) |
||||||
|
# ttk.debug("INPUT: "+input_key.replace("\033","<ESC>")) |
||||||
|
mevt = None |
||||||
|
kevt = None |
||||||
|
if input_key == "\033" or input_key == "q": #* Key is "escape" key if only containing \033 |
||||||
|
break |
||||||
|
elif len(input_key) == 1: |
||||||
|
# Key Pressed |
||||||
|
kevt = KeyEvent(0, input_key) |
||||||
|
elif input_key.startswith("\033[<"): |
||||||
|
# Mouse Event |
||||||
|
m = mouse_re.match(input_key) |
||||||
|
if not m: |
||||||
|
# TODO: Return Error |
||||||
|
continue |
||||||
|
code = int(m.group(1)) |
||||||
|
x = int(m.group(2)) |
||||||
|
y = int(m.group(3)) |
||||||
|
state = m.group(4) |
||||||
|
key = MouseEvent.NoButton |
||||||
|
evt = MouseEvent.NoEvent |
||||||
|
if code == 0x00: |
||||||
|
key = MouseEvent.LeftButton |
||||||
|
evt = MouseEvent.Press if state=="M" else MouseEvent.Release |
||||||
|
elif code == 0x01: |
||||||
|
key = MouseEvent.MidButton |
||||||
|
evt = MouseEvent.Press if state=="M" else MouseEvent.Release |
||||||
|
elif code == 0x02: |
||||||
|
key = MouseEvent.RightButton |
||||||
|
evt = MouseEvent.Press if state=="M" else MouseEvent.Release |
||||||
|
elif code == 0x20: |
||||||
|
key = MouseEvent.LeftButton |
||||||
|
evt = MouseEvent.Drag |
||||||
|
elif code == 0x21: |
||||||
|
key = MouseEvent.MidButton |
||||||
|
evt = MouseEvent.Drag |
||||||
|
elif code == 0x22: |
||||||
|
key = MouseEvent.RightButton |
||||||
|
evt = MouseEvent.Drag |
||||||
|
elif code == 0x40: |
||||||
|
key = MouseEvent.Wheel |
||||||
|
evt = MouseEvent.Up |
||||||
|
elif code == 0x41: |
||||||
|
key = MouseEvent.Wheel |
||||||
|
evt = MouseEvent.Down |
||||||
|
mevt = MouseEvent(x, y, key, evt, m.group(0).replace("\033", "<ESC>")) |
||||||
|
|
||||||
|
input_key = "" |
||||||
|
if callback is not None: |
||||||
|
callback(kevt, mevt) |
||||||
|
|
||||||
|
def main(): |
||||||
|
print("Retrieve Keyboard, Mouse press/drag/wheel Events") |
||||||
|
print("Press q or <ESC> to exit") |
||||||
|
import term as t |
||||||
|
|
||||||
|
t.Term.push(t.Term.mouse_on) |
||||||
|
t.Term.echo(False) |
||||||
|
|
||||||
|
def callback(kevt=None, mevt=None): |
||||||
|
if kevt is not None: |
||||||
|
print(f"Key Event: {kevt}") |
||||||
|
if mevt is not None: |
||||||
|
print(f"Mouse Event: {mevt}") |
||||||
|
|
||||||
|
Input.get_key(callback) |
||||||
|
|
||||||
|
t.Term.push(t.Term.mouse_off, t.Term.mouse_direct_off) |
||||||
|
t.Term.echo(True) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
main() |
||||||
@ -0,0 +1,83 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
# Copyright 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||||
|
# Copyright 2020 Aristocratos (https://github.com/aristocratos/bpytop) |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
|
||||||
|
import os, sys, io, threading, signal, re, subprocess, logging, logging.handlers, argparse |
||||||
|
import queue |
||||||
|
from select import select |
||||||
|
from time import time, sleep, strftime, localtime |
||||||
|
from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable, Type, NamedTuple |
||||||
|
|
||||||
|
try: import fcntl, termios, tty, pwd |
||||||
|
except Exception as e: |
||||||
|
print(f'ERROR: {e}') |
||||||
|
exit(1) |
||||||
|
|
||||||
|
class Term: |
||||||
|
"""Terminal info and commands""" |
||||||
|
width: int = 0 |
||||||
|
height: int = 0 |
||||||
|
fg: str = "" #* Default foreground color |
||||||
|
bg: str = "" #* Default background color |
||||||
|
hide_cursor = "\033[?25l" #* Hide terminal cursor |
||||||
|
show_cursor = "\033[?25h" #* Show terminal cursor |
||||||
|
alt_screen = "\033[?1049h" #* Switch to alternate screen |
||||||
|
normal_screen = "\033[?1049l" #* Switch to normal screen |
||||||
|
clear = "\033[2J\033[0;0f" #* Clear screen and set cursor to position 0,0 |
||||||
|
mouse_on = "\033[?1002h\033[?1015h\033[?1006h" #* Enable reporting of mouse position on click and release |
||||||
|
mouse_off = "\033[?1002l" #* Disable mouse reporting |
||||||
|
mouse_direct_on = "\033[?1003h" #* Enable reporting of mouse position at any movement |
||||||
|
mouse_direct_off = "\033[?1003l" #* Disable direct mouse reporting |
||||||
|
|
||||||
|
_sigWinChCb = None |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def echo(on: bool): |
||||||
|
"""Toggle input echo""" |
||||||
|
(iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(sys.stdin.fileno()) |
||||||
|
if on: |
||||||
|
lflag |= termios.ECHO # type: ignore |
||||||
|
else: |
||||||
|
lflag &= ~termios.ECHO # type: ignore |
||||||
|
new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] |
||||||
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, new_attr) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def push(*args): |
||||||
|
try: |
||||||
|
print(*args, sep="", end="", flush=True) |
||||||
|
except BlockingIOError: |
||||||
|
pass |
||||||
|
print(*args, sep="", end="", flush=True) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def title(text: str = "") -> str: |
||||||
|
out: str = f'{os.environ.get("TERMINAL_TITLE", "")}' |
||||||
|
if out and text: out += " " |
||||||
|
if text: out += f'{text}' |
||||||
|
return f'\033]0;{out}\a' |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _sigWinCh(signum, frame): |
||||||
|
Term.width, Term.height = os.get_terminal_size() |
||||||
|
if Term._sigWinChCb is not None: |
||||||
|
Term._sigWinChCb(Term.width, Term.height) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def registerResizeCb(callback): |
||||||
|
Term._sigWinChCb = callback |
||||||
|
signal.signal(signal.SIGWINCH, Term._sigWinCh) |
||||||
|
|
||||||
@ -0,0 +1,72 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
# MIT License |
||||||
|
# |
||||||
|
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
||||||
|
# |
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
# of this software and associated documentation files (the "Software"), to deal |
||||||
|
# in the Software without restriction, including without limitation the rights |
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
# copies of the Software, and to permit persons to whom the Software is |
||||||
|
# furnished to do so, subject to the following conditions: |
||||||
|
# |
||||||
|
# The above copyright notice and this permission notice shall be included in all |
||||||
|
# copies or substantial portions of the Software. |
||||||
|
# |
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
# SOFTWARE. |
||||||
|
|
||||||
|
# This code is inspired by |
||||||
|
# https://github.com/ceccopierangiolieugenio/pyCuT/blob/master/cupy/CuTCore/CuDebug.py |
||||||
|
|
||||||
|
import inspect |
||||||
|
|
||||||
|
DebugMsg = 0 # A message generated by the Debug() function. |
||||||
|
InfoMsg = 4 # A message generated by the Info() function. |
||||||
|
WarningMsg = 1 # A message generated by the Warning() function. |
||||||
|
CriticalMsg = 2 # A message generated by the Critical() function. |
||||||
|
FatalMsg = 3 # A message generated by the Fatal() function. |
||||||
|
SystemMsg = CriticalMsg |
||||||
|
|
||||||
|
_MessageHandler = None |
||||||
|
|
||||||
|
def _process_msg(mode, msg): |
||||||
|
global _MessageHandler |
||||||
|
if _MessageHandler is not None: |
||||||
|
curframe = inspect.currentframe() |
||||||
|
calframe = inspect.getouterframes(curframe,1) |
||||||
|
if len(calframe) > 2: |
||||||
|
class context: |
||||||
|
__slots__ = ('file', 'line', 'function') |
||||||
|
def __str__(self): |
||||||
|
return f"{self.file}:{self.line} [{self.function}]" |
||||||
|
ctx = context() |
||||||
|
ctx.file = calframe[2][1] |
||||||
|
ctx.line = calframe[2][2] |
||||||
|
ctx.function = calframe[2][3] |
||||||
|
_MessageHandler(mode, ctx, msg) |
||||||
|
|
||||||
|
def debug(msg): |
||||||
|
_process_msg(DebugMsg, msg) |
||||||
|
|
||||||
|
def info(msg): |
||||||
|
_process_msg(InfoMsg, msg) |
||||||
|
|
||||||
|
def warn(msg): |
||||||
|
_process_msg(WarningMsg, msg) |
||||||
|
|
||||||
|
def critical(msg): |
||||||
|
_process_msg(CriticalMsg, msg) |
||||||
|
|
||||||
|
def fatal(msg): |
||||||
|
_process_msg(FatalMsg, msg) |
||||||
|
|
||||||
|
def installMessageHandler(mh): |
||||||
|
global _MessageHandler |
||||||
|
_MessageHandler = mh |
||||||
Loading…
Reference in new issue