diff --git a/docs/HLD.md b/docs/HLD.md
index b5b8e9af..357e4ca8 100644
--- a/docs/HLD.md
+++ b/docs/HLD.md
@@ -1 +1,2 @@
+# HLD

diff --git a/docs/TODO.md b/docs/TODO.md
index e38af2bb..191e65b8 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -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
\ No newline at end of file
diff --git a/docs/hld.svg b/docs/hld.svg
index 0a281c57..6fba041b 100644
--- a/docs/hld.svg
+++ b/docs/hld.svg
@@ -1,3 +1,3 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tests/test.draw.py b/tests/test.draw.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test.input.py b/tests/test.input.py
new file mode 100644
index 00000000..7540b858
--- /dev/null
+++ b/tests/test.input.py
@@ -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 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)
\ No newline at end of file
diff --git a/tests/utf-8.txt b/tests/utf-8.txt
new file mode 100755
index 00000000..257bfdcd
--- /dev/null
+++ b/tests/utf-8.txt
@@ -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
\ No newline at end of file
diff --git a/ttk/__init__.py b/ttk/__init__.py
new file mode 100644
index 00000000..98fed2c6
--- /dev/null
+++ b/ttk/__init__.py
@@ -0,0 +1 @@
+from .log import *
\ No newline at end of file
diff --git a/ttk/libbpytop/__init__.py b/ttk/libbpytop/__init__.py
new file mode 100644
index 00000000..0e3ca9fc
--- /dev/null
+++ b/ttk/libbpytop/__init__.py
@@ -0,0 +1,2 @@
+from .input import *
+from .term import *
\ No newline at end of file
diff --git a/ttk/libbpytop/input.py b/ttk/libbpytop/input.py
new file mode 100644
index 00000000..3538692a
--- /dev/null
+++ b/ttk/libbpytop/input.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 Eugenio Parodi
+# 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",""))
+ 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", ""))
+
+ input_key = ""
+ if callback is not None:
+ callback(kevt, mevt)
+
+def main():
+ print("Retrieve Keyboard, Mouse press/drag/wheel Events")
+ print("Press q or 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()
\ No newline at end of file
diff --git a/ttk/libbpytop/term.py b/ttk/libbpytop/term.py
new file mode 100644
index 00000000..5ad60db3
--- /dev/null
+++ b/ttk/libbpytop/term.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 Eugenio Parodi
+# 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)
+
diff --git a/ttk/log.py b/ttk/log.py
new file mode 100644
index 00000000..49dc260d
--- /dev/null
+++ b/ttk/log.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+
+# MIT License
+#
+# Copyright (c) 2021 Eugenio Parodi
+#
+# 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
\ No newline at end of file