11 changed files with 409 additions and 4 deletions
@ -1,10 +1,15 @@
|
||||
- [ ] Handle Logs |
||||
- [ ] Terminal Helper |
||||
- [ ] Events |
||||
- [x] Window : SIGWINCH triggered when the terminal is resized |
||||
- [ ] 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 |
||||
https://tkinterexamples.com/events/events.html |
||||
https://www.pythontutorial.net/tkinter/tkinter-event-binding/ |
||||
- [ ] Keyboard |
||||
- [ ] Mouse |
||||
- [ ] Window |
||||
- [x] Keyboard |
||||
- [x] Mouse |
||||
- [ ] 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