Module TermTk.libbpytop.input

Expand source code
#!/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
except Exception as e:
    print(f'ERROR: {e}')
    exit(1)

from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.libbpytop.inputkey import *

class MouseEvent:
    # Keys
    NoButton      = TTkK.NoButton     # The button state does not refer to any button (see QMouseEvent::button()).
    AllButtons    = TTkK.AllButtons   # 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    = TTkK.LeftButton   # 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   = TTkK.RightButton  # The right button.
    MidButton     = TTkK.MidButton    # The middle button.
    MiddleButton  = TTkK.MiddleButton # The middle button.
    Wheel         = TTkK.Wheel

    # Events
    NoEvent = TTkK.NoEvent
    Press   = TTkK.Press
    Release = TTkK.Release
    Drag    = TTkK.Drag
    Move    = TTkK.Move
    Up      = TTkK.WHEEL_Up
    Down    = TTkK.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 clone(self, pos=None):
        x,y = pos if pos != None else (self.x, self.y)
        return MouseEvent(x, y, self.key, self.evt, self.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 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)
                # TTkLog.debug("INPUT: "+input_key.replace("\033","<ESC>"))
                mevt = None
                kevt = KeyEvent.parse(input_key)
                if kevt is None and \
                   input_key.startswith("\033[<"):
                    # Mouse Event
                    m = mouse_re.match(input_key)
                    if not m:
                        # TODO: Return Error
                        TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
                        continue
                    code = int(m.group(1))
                    x = int(m.group(2))-1
                    y = int(m.group(3))-1
                    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>"))
                if kevt is None and mevt is None:
                    TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
                input_key = ""
                if callback is not None:
                    if not callback(kevt, mevt):
                        break

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()

Functions

def main()
Expand source code
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)

Classes

class Input
Expand source code
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)
                # TTkLog.debug("INPUT: "+input_key.replace("\033","<ESC>"))
                mevt = None
                kevt = KeyEvent.parse(input_key)
                if kevt is None and \
                   input_key.startswith("\033[<"):
                    # Mouse Event
                    m = mouse_re.match(input_key)
                    if not m:
                        # TODO: Return Error
                        TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
                        continue
                    code = int(m.group(1))
                    x = int(m.group(2))-1
                    y = int(m.group(3))-1
                    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>"))
                if kevt is None and mevt is None:
                    TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
                input_key = ""
                if callback is not None:
                    if not callback(kevt, mevt):
                        break

Static methods

def get_key(callback=None)
Expand source code
@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)
            # TTkLog.debug("INPUT: "+input_key.replace("\033","<ESC>"))
            mevt = None
            kevt = KeyEvent.parse(input_key)
            if kevt is None and \
               input_key.startswith("\033[<"):
                # Mouse Event
                m = mouse_re.match(input_key)
                if not m:
                    # TODO: Return Error
                    TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
                    continue
                code = int(m.group(1))
                x = int(m.group(2))-1
                y = int(m.group(3))-1
                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>"))
            if kevt is None and mevt is None:
                TTkLog.error("UNHANDLED: "+input_key.replace("\033","<ESC>"))
            input_key = ""
            if callback is not None:
                if not callback(kevt, mevt):
                    break
class MouseEvent (x: int, y: int, key: int, evt: int, raw: str)
Expand source code
class MouseEvent:
    # Keys
    NoButton      = TTkK.NoButton     # The button state does not refer to any button (see QMouseEvent::button()).
    AllButtons    = TTkK.AllButtons   # 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    = TTkK.LeftButton   # 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   = TTkK.RightButton  # The right button.
    MidButton     = TTkK.MidButton    # The middle button.
    MiddleButton  = TTkK.MiddleButton # The middle button.
    Wheel         = TTkK.Wheel

    # Events
    NoEvent = TTkK.NoEvent
    Press   = TTkK.Press
    Release = TTkK.Release
    Drag    = TTkK.Drag
    Move    = TTkK.Move
    Up      = TTkK.WHEEL_Up
    Down    = TTkK.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 clone(self, pos=None):
        x,y = pos if pos != None else (self.x, self.y)
        return MouseEvent(x, y, self.key, self.evt, self.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 variables

var AllButtons
var Down
var Drag
var LeftButton
var MidButton
var MiddleButton
var Move
var NoButton
var NoEvent
var Press
var Release
var RightButton
var Up
var Wheel

Instance variables

var evt

Return an attribute of instance, which is of type owner.

var key

Return an attribute of instance, which is of type owner.

var raw

Return an attribute of instance, which is of type owner.

var x

Return an attribute of instance, which is of type owner.

var y

Return an attribute of instance, which is of type owner.

Methods

def clone(self, pos=None)
Expand source code
def clone(self, pos=None):
    x,y = pos if pos != None else (self.x, self.y)
    return MouseEvent(x, y, self.key, self.evt, self.raw)
def evt2str(self)
Expand source code
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 key2str(self)
Expand source code
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")