11 changed files with 1031 additions and 591 deletions
@ -0,0 +1,183 @@
|
||||
# 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. |
||||
|
||||
__all__ = ['TTkInput'] |
||||
|
||||
import re |
||||
from time import time |
||||
|
||||
import platform |
||||
|
||||
from ..drivers import TTkInputDriver |
||||
|
||||
from TermTk.TTkCore.log import TTkLog |
||||
from TermTk.TTkCore.constant import TTkK |
||||
from TermTk.TTkCore.signal import pyTTkSignal |
||||
from TermTk.TTkCore.TTkTerm.term import TTkTerm |
||||
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent |
||||
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent |
||||
|
||||
|
||||
class TTkInput: |
||||
inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent) |
||||
pasteEvent = pyTTkSignal(str) |
||||
_pasteBuffer = "" |
||||
_bracketedPaste = False |
||||
_readInput = None |
||||
_leftLastTime = 0 |
||||
_midLastTime = 0 |
||||
_rightLastTime = 0 |
||||
_leftTap = 0 |
||||
_midTap = 0 |
||||
_rightTap = 0 |
||||
_mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])") |
||||
|
||||
class Mouse(int): |
||||
ON = 0x01 |
||||
DIRECT = 0x02 |
||||
|
||||
@staticmethod |
||||
def init(mouse:bool=False, directMouse:bool=False) -> None: |
||||
TTkInput._readInput = TTkInputDriver() |
||||
TTkTerm.setMouse(mouse, directMouse) |
||||
|
||||
@staticmethod |
||||
def close() -> None: |
||||
TTkTerm.setMouse(False, False) |
||||
if TTkInput._readInput: |
||||
TTkInput._readInput.close() |
||||
|
||||
@staticmethod |
||||
def stop() -> None: |
||||
pass |
||||
|
||||
@staticmethod |
||||
def cont() -> None: |
||||
if TTkInput._readInput: |
||||
TTkInput._readInput.cont() |
||||
|
||||
@staticmethod |
||||
def start() -> None: |
||||
for stdinRead in TTkInput._readInput.read(): |
||||
TTkInput.key_process(stdinRead) |
||||
TTkLog.debug("Close TTkInput") |
||||
|
||||
@staticmethod |
||||
def key_process(stdinRead:str) -> None: |
||||
if TTkInput._bracketedPaste: |
||||
if stdinRead.endswith("\033[201~"): |
||||
TTkInput._pasteBuffer += stdinRead[:-6] |
||||
TTkInput._bracketedPaste = False |
||||
# due to the CRNL methos (don't ask me why) the terminal |
||||
# is substituting all the \n with \r |
||||
TTkInput.pasteEvent.emit(TTkInput._pasteBuffer.replace('\r','\n')) |
||||
TTkInput._pasteBuffer = "" |
||||
else: |
||||
TTkInput._pasteBuffer += stdinRead |
||||
return |
||||
|
||||
mevt,kevt = None, None |
||||
|
||||
if not stdinRead.startswith("\033[<"): |
||||
# Key Event |
||||
kevt = TTkKeyEvent.parse(stdinRead) |
||||
else: |
||||
# Mouse Event |
||||
m = TTkInput._mouse_re.match(stdinRead) |
||||
if not m: |
||||
# TODO: Return Error |
||||
hex = [f"0x{ord(x):02x}" for x in stdinRead] |
||||
TTkLog.error("UNHANDLED (mouse): "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex)) |
||||
return None, None |
||||
code = int(m.group(1)) |
||||
x = int(m.group(2))-1 |
||||
y = int(m.group(3))-1 |
||||
state = m.group(4) |
||||
key = TTkMouseEvent.NoButton |
||||
evt = TTkMouseEvent.Move |
||||
tap = 0 |
||||
|
||||
def _checkTap(lastTime, tap): |
||||
if state=="M": |
||||
t = time() |
||||
if (t-lastTime) < 0.4: |
||||
return t, tap+1 |
||||
else: |
||||
return t, 1 |
||||
return lastTime, tap |
||||
|
||||
mod = TTkK.NoModifier |
||||
if code & 0x10: |
||||
code &= ~0x10 |
||||
mod |= TTkK.ControlModifier |
||||
if code & 0x08: |
||||
code &= ~0x08 |
||||
mod |= TTkK.AltModifier |
||||
|
||||
if code == 0x00: |
||||
TTkInput._leftLastTime, TTkInput._leftTap = _checkTap(TTkInput._leftLastTime, TTkInput._leftTap) |
||||
tap = TTkInput._leftTap |
||||
key = TTkMouseEvent.LeftButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x01: |
||||
TTkInput._midLastTime, TTkInput._midTap = _checkTap(TTkInput._midLastTime, TTkInput._midTap) |
||||
tap = TTkInput._midTap |
||||
key = TTkMouseEvent.MidButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x02: |
||||
TTkInput._rightLastTime, TTkInput._rightTap = _checkTap(TTkInput._rightLastTime, TTkInput._rightTap) |
||||
tap = TTkInput._rightTap |
||||
key = TTkMouseEvent.RightButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x20: |
||||
key = TTkMouseEvent.LeftButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x21: |
||||
key = TTkMouseEvent.MidButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x22: |
||||
key = TTkMouseEvent.RightButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x40: |
||||
key = TTkMouseEvent.Wheel |
||||
evt = TTkMouseEvent.Up |
||||
elif code == 0x41: |
||||
key = TTkMouseEvent.Wheel |
||||
evt = TTkMouseEvent.Down |
||||
elif code == 0x23: |
||||
evt = TTkMouseEvent.Move |
||||
elif code == 0x27: |
||||
mod |= TTkK.ShiftModifier |
||||
evt = TTkMouseEvent.Move |
||||
|
||||
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>")) |
||||
if kevt or mevt: |
||||
TTkInput.inputEvent.emit(kevt, mevt) |
||||
return |
||||
|
||||
if stdinRead.startswith("\033[200~"): |
||||
TTkInput._pasteBuffer = stdinRead[6:] |
||||
TTkInput._bracketedPaste = True |
||||
return |
||||
|
||||
hex = [f"0x{ord(x):02x}" for x in stdinRead] |
||||
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex)) |
||||
@ -0,0 +1,213 @@
|
||||
# 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. |
||||
|
||||
__all__ = ['TTkInput'] |
||||
|
||||
import re |
||||
from time import time |
||||
|
||||
import threading, queue |
||||
|
||||
from ..drivers import TTkInputDriver |
||||
|
||||
from TermTk.TTkCore.log import TTkLog |
||||
from TermTk.TTkCore.constant import TTkK |
||||
from TermTk.TTkCore.signal import pyTTkSignal |
||||
from TermTk.TTkCore.TTkTerm.term import TTkTerm |
||||
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent |
||||
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent |
||||
|
||||
|
||||
class TTkInput: |
||||
inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent) |
||||
pasteEvent = pyTTkSignal(str) |
||||
_pasteBuffer = "" |
||||
_bracketedPaste = False |
||||
_readInput = None |
||||
_inputThread = None |
||||
_inputQueue = None |
||||
_leftLastTime = 0 |
||||
_midLastTime = 0 |
||||
_rightLastTime = 0 |
||||
_leftTap = 0 |
||||
_midTap = 0 |
||||
_rightTap = 0 |
||||
_mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])") |
||||
|
||||
class Mouse(int): |
||||
ON = 0x01 |
||||
DIRECT = 0x02 |
||||
|
||||
@staticmethod |
||||
def init(mouse:bool=False, directMouse:bool=False) -> None: |
||||
TTkInput._readInput = TTkInputDriver() |
||||
TTkInput._inputThread = threading.Thread(target=TTkInput._run) |
||||
TTkInput._inputQueue = queue.Queue() |
||||
TTkTerm.setMouse(mouse, directMouse) |
||||
|
||||
@staticmethod |
||||
def close() -> None: |
||||
TTkTerm.setMouse(False, False) |
||||
if TTkInput._readInput: |
||||
TTkInput._readInput.close() |
||||
|
||||
@staticmethod |
||||
def stop() -> None: |
||||
pass |
||||
|
||||
@staticmethod |
||||
def cont() -> None: |
||||
if TTkInput._readInput: |
||||
TTkInput._readInput.cont() |
||||
|
||||
@staticmethod |
||||
def start() -> None: |
||||
TTkInput._inputThread.start() |
||||
while inq := TTkInput._inputQueue.get(): |
||||
kevt,mevt,paste = inq |
||||
|
||||
# Try to filter out the queued moved mouse events |
||||
while (not kevt and |
||||
not paste and |
||||
mevt and mevt.evt == TTkK.Drag and |
||||
not TTkInput._inputQueue.empty() ): |
||||
mevtOld = mevt |
||||
kevt, mevt, paste = TTkInput._inputQueue.get() |
||||
if (kevt or |
||||
paste or |
||||
mevt and mevt.evt != TTkK.Drag): |
||||
TTkInput.inputEvent.emit(kevt, mevtOld) |
||||
break |
||||
|
||||
if kevt or mevt: |
||||
TTkInput.inputEvent.emit(kevt, mevt) |
||||
if paste: |
||||
TTkInput.pasteEvent.emit(paste) |
||||
TTkLog.debug("Close TTkInput") |
||||
|
||||
@staticmethod |
||||
def _run(): |
||||
for stdinRead in TTkInput._readInput.read(): |
||||
outq = TTkInput.key_process(stdinRead) |
||||
TTkInput._inputQueue.put(outq) |
||||
TTkInput._inputQueue.put(None) |
||||
|
||||
@staticmethod |
||||
def key_process(stdinRead:str) -> None: |
||||
if TTkInput._bracketedPaste: |
||||
if stdinRead.endswith("\033[201~"): |
||||
TTkInput._pasteBuffer += stdinRead[:-6] |
||||
TTkInput._bracketedPaste = False |
||||
# due to the CRNL methos (don't ask me why) the terminal |
||||
# is substituting all the \n with \r |
||||
_paste = TTkInput._pasteBuffer.replace('\r','\n') |
||||
TTkInput._pasteBuffer = "" |
||||
return None, None, _paste |
||||
else: |
||||
TTkInput._pasteBuffer += stdinRead |
||||
return None, None, None |
||||
|
||||
mevt,kevt = None,None |
||||
|
||||
if not stdinRead.startswith("\033[<"): |
||||
# Key Event |
||||
kevt = TTkKeyEvent.parse(stdinRead) |
||||
else: |
||||
# Mouse Event |
||||
m = TTkInput._mouse_re.match(stdinRead) |
||||
if not m: |
||||
# TODO: Return Error |
||||
hex = [f"0x{ord(x):02x}" for x in stdinRead] |
||||
TTkLog.error("UNHANDLED (mouse): "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex)) |
||||
return None, None, None |
||||
code = int(m.group(1)) |
||||
x = int(m.group(2))-1 |
||||
y = int(m.group(3))-1 |
||||
state = m.group(4) |
||||
key = TTkMouseEvent.NoButton |
||||
evt = TTkMouseEvent.Move |
||||
tap = 0 |
||||
|
||||
def _checkTap(lastTime, tap): |
||||
if state=="M": |
||||
t = time() |
||||
if (t-lastTime) < 0.4: |
||||
return t, tap+1 |
||||
else: |
||||
return t, 1 |
||||
return lastTime, tap |
||||
|
||||
mod = TTkK.NoModifier |
||||
if code & 0x10: |
||||
code &= ~0x10 |
||||
mod |= TTkK.ControlModifier |
||||
if code & 0x08: |
||||
code &= ~0x08 |
||||
mod |= TTkK.AltModifier |
||||
|
||||
if code == 0x00: |
||||
TTkInput._leftLastTime, TTkInput._leftTap = _checkTap(TTkInput._leftLastTime, TTkInput._leftTap) |
||||
tap = TTkInput._leftTap |
||||
key = TTkMouseEvent.LeftButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x01: |
||||
TTkInput._midLastTime, TTkInput._midTap = _checkTap(TTkInput._midLastTime, TTkInput._midTap) |
||||
tap = TTkInput._midTap |
||||
key = TTkMouseEvent.MidButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x02: |
||||
TTkInput._rightLastTime, TTkInput._rightTap = _checkTap(TTkInput._rightLastTime, TTkInput._rightTap) |
||||
tap = TTkInput._rightTap |
||||
key = TTkMouseEvent.RightButton |
||||
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release |
||||
elif code == 0x20: |
||||
key = TTkMouseEvent.LeftButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x21: |
||||
key = TTkMouseEvent.MidButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x22: |
||||
key = TTkMouseEvent.RightButton |
||||
evt = TTkMouseEvent.Drag |
||||
elif code == 0x40: |
||||
key = TTkMouseEvent.Wheel |
||||
evt = TTkMouseEvent.Up |
||||
elif code == 0x41: |
||||
key = TTkMouseEvent.Wheel |
||||
evt = TTkMouseEvent.Down |
||||
elif code == 0x23: |
||||
evt = TTkMouseEvent.Move |
||||
elif code == 0x27: |
||||
mod |= TTkK.ShiftModifier |
||||
evt = TTkMouseEvent.Move |
||||
|
||||
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>")) |
||||
if kevt or mevt: |
||||
return kevt, mevt, None |
||||
|
||||
if stdinRead.startswith("\033[200~"): |
||||
TTkInput._pasteBuffer = stdinRead[6:] |
||||
TTkInput._bracketedPaste = True |
||||
return None, None, None |
||||
|
||||
hex = [f"0x{ord(x):02x}" for x in stdinRead] |
||||
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex)) |
||||
@ -0,0 +1,28 @@
|
||||
# 0.36.0-a |
||||
Single Thread, |
||||
|
||||
``` |
||||
TTkInputDriver TTkInput TTK |
||||
read() <- stdin |
||||
yield inString --> for inString in _readInput.read() |
||||
key_process(inString) |
||||
inputEvent.emit(kevt, mevt) ----> _processInput |
||||
pasteEvent.emit(str) ----> _pasteInput |
||||
``` |
||||
|
||||
# 0.xx.0-a + |
||||
multithread |
||||
Rework key_process to return kevt,mevt,paste |
||||
|
||||
``` |
||||
TTkInputDriver TTkInput TTK |
||||
Thread1 Thread2 (mainn) |
||||
read() <- stdin |
||||
yield inString --> for inString in _readInput.read() |
||||
kevt,mevt,paste = key_process(inString) |
||||
queue.put(kevt,mevt,paste) |
||||
|
||||
queue.get() |
||||
inputEvent.emit(kevt, mevt) ------> _processInput |
||||
pasteEvent.emit(str) ------> _pasteInput |
||||
``` |
||||
@ -0,0 +1,205 @@
|
||||
/* |
||||
MIT License |
||||
|
||||
Copyright (c) 2023 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. |
||||
*/ |
||||
|
||||
// Declaration
|
||||
class TTkProxy { |
||||
constructor(term) { |
||||
this.term = term |
||||
} |
||||
|
||||
pyodideProxy = { |
||||
consoleLog: function(m){ |
||||
console.log("TTk:",m) |
||||
}, |
||||
termPush: function (s) { |
||||
this.term.write(s); |
||||
}, |
||||
termSize: function () { |
||||
return [this.term.cols, this.term.rows] |
||||
}, |
||||
setTimeout: function(t, i) { |
||||
// console.log("TIME (Start)",i,t)
|
||||
return setTimeout(() => this.ttk_timer(i), t) |
||||
}, |
||||
stopTimeout: function(t) { |
||||
// console.log("TIME (Stop)",t)
|
||||
clearTimeout(t) |
||||
}, |
||||
clearTimeout: function(){ |
||||
let highestTimeoutId = setTimeout(";"); |
||||
for (let i = 0 ; i < highestTimeoutId ; i++) { |
||||
clearTimeout(i); |
||||
} |
||||
}, |
||||
setInterval: function(t, i) { |
||||
setTinterval(() => console.log('WIP -> Interval' + i), t) |
||||
} |
||||
}; |
||||
|
||||
async init(){ |
||||
this.pyodide = await loadPyodide(); |
||||
this.ttk_timer = (i) => console.log("ttk_timer unimplemented") |
||||
this.term.write('Pyodide ('+this.pyodide.version+') - Loaded\n\r') |
||||
|
||||
this.pyodide.registerJsModule("pyodideProxy", this.pyodideProxy); |
||||
this.term.write('Pyodide Proxy - Loaded\n\r') |
||||
} |
||||
|
||||
async loadLib(lib) { |
||||
let zipResponse = await fetch(lib); |
||||
let zipBinary = await zipResponse.arrayBuffer(); |
||||
this.pyodide.unpackArchive(zipBinary, ".tar.gz"); |
||||
} |
||||
|
||||
async loadFile(fileUri,file){ |
||||
this.pyodide.FS.writeFile(this.pyodide.FS.currentPath+file, await (await fetch(fileUri)).text()); |
||||
} |
||||
|
||||
readFile(file){ |
||||
return this.pyodide.FS.readFile(file, {encoding:'utf8'}) |
||||
} |
||||
|
||||
currentPath(){ |
||||
return this.pyodide.FS.currentPath |
||||
} |
||||
|
||||
getAllFiles(p){ |
||||
let ls = this.pyodide.FS.readdir(p) |
||||
let ret = [] |
||||
for(let i=0 ; i<ls.length; i++){ |
||||
let n = p+"/"+ls[i] |
||||
if(ls[i]=='.' || ls[i]=='..' || n==(this.pyodide.FS.currentPath+"/TermTk")) continue; |
||||
if( this.pyodide.FS.isDir(this.pyodide.FS.lstat(n).mode)){ |
||||
ret.push({id:n, text:ls[i], expanded: true, nodes:this.getAllFiles(n)}) |
||||
}else{ |
||||
if(n.endsWith('.py')){ |
||||
ret.push({id:n, text:ls[i]}) |
||||
} |
||||
} |
||||
} |
||||
return ret |
||||
} |
||||
|
||||
preRun(){ |
||||
this.namespace = this.pyodide.globals.get("dict")(); |
||||
this.pyodide.runPython(` |
||||
import sys |
||||
import TermTk as ttk |
||||
from TermTk.TTkCore.TTkTerm.input import TTkInput |
||||
import pyodideProxy |
||||
|
||||
def ttk_input(val): |
||||
kevt,mevt,paste = TTkInput.key_process(val) |
||||
if kevt or mevt: |
||||
TTkInput.inputEvent.emit(kevt, mevt) |
||||
if paste: |
||||
TTkInput.pasteEvent.emit(paste) |
||||
|
||||
def ttk_resize(w,h): |
||||
ttk.TTkLog.debug(f"Resize: {w=} {h=}") |
||||
if ttk.TTkHelper._rootWidget: |
||||
ttk.TTkHelper._rootWidget._win_resize_cb(w,h) |
||||
ttk.TTkHelper.rePaintAll() |
||||
ttk.TTkTerm.cont() |
||||
|
||||
def ttk_timer(tid): |
||||
ttk.TTkTimer.triggerTimerId(tid) |
||||
|
||||
def ttk_log(val): |
||||
# hex = [f"0x{ord(x):02x}" for x in val] |
||||
ttk.TTkLog.debug("---> "+val.replace("\\033","<ESC>") + " - ") |
||||
ttk.TTkHelper.paintAll() |
||||
|
||||
def ttk_clean(): |
||||
if ttk.TTkHelper._rootWidget: |
||||
ttk.TTkTimer.pyodideQuit() |
||||
ttk.TTkHelper._rootWidget.quit() |
||||
ttk.TTkHelper._focusWidget = None |
||||
ttk.TTkHelper._rootCanvas = None |
||||
ttk.TTkHelper._rootWidget = None |
||||
ttk.TTkHelper._updateBuffer = set() |
||||
ttk.TTkHelper._updateWidget = set() |
||||
ttk.TTkHelper._overlay = [] |
||||
ttk.TTkHelper._shortcut = [] |
||||
ttk.TTkLog._messageHandler = [message_handler] |
||||
|
||||
def ttk_init(): |
||||
ttk.TTkToolTip.toolTipTimer = ttk.TTkTimer() |
||||
ttk.TTkToolTip.toolTipTimer.timeout.connect(ttk.TTkToolTip._toolTipShow) |
||||
|
||||
def message_handler(mode, context, message): |
||||
msgType = "DEBUG" |
||||
if mode == ttk.TTkLog.InfoMsg: msgType = "[INFO]" |
||||
elif mode == ttk.TTkLog.WarningMsg: msgType = "[WARNING]" |
||||
elif mode == ttk.TTkLog.CriticalMsg: msgType = "[CRITICAL]" |
||||
elif mode == ttk.TTkLog.FatalMsg: msgType = "[FATAL]" |
||||
elif mode == ttk.TTkLog.ErrorMsg: msgType = "[ERROR]" |
||||
pyodideProxy.consoleLog(f"{msgType} {context.file} {message}") |
||||
# Register the callback to the message handler |
||||
ttk.TTkLog.installMessageHandler(message_handler) |
||||
`,{ globals: this.namespace }
|
||||
); |
||||
|
||||
this.ttk_log = this.namespace.get("ttk_log"); |
||||
this.ttk_input = this.namespace.get("ttk_input"); |
||||
this.ttk_timer = this.namespace.get("ttk_timer"); |
||||
this.ttk_resize = this.namespace.get("ttk_resize"); |
||||
this.ttk_clean = this.namespace.get("ttk_clean"); |
||||
this.ttk_init = this.namespace.get("ttk_init"); |
||||
|
||||
this.pyodideProxy.ttk_timer = this.ttk_timer |
||||
this.pyodideProxy.term = this.term |
||||
|
||||
this.term.onResize( (obj) => { |
||||
this.term.reset() |
||||
this.ttk_resize(obj.cols, obj.rows) |
||||
}); |
||||
this.term.onData((d, evt) => { this.ttk_input(d) }) |
||||
|
||||
this.pyodide.runPython(` |
||||
import sys,os |
||||
sys.path.append(os.path.join(sys.path[0],'demo')) |
||||
__name__ = "__main__" |
||||
`,{ globals: this.namespace }
|
||||
); |
||||
} |
||||
|
||||
run(code,filename,fps) { |
||||
this.ttk_clean() |
||||
console.log("Run App") |
||||
|
||||
let pwd = this.pyodide.PATH.dirname(filename) |
||||
|
||||
this.pyodide.runPython(` |
||||
__file__='`+filename+`' |
||||
os.chdir('`+pwd+`') |
||||
ttk.TTkCfg.maxFps = `+fps,{ globals: this.namespace })
|
||||
|
||||
this.ttk_init() |
||||
|
||||
this.pyodide.runPython(code,{ globals: this.namespace }); |
||||
|
||||
this.ttk_log(filename + " - LOADED") |
||||
} |
||||
} |
||||
@ -0,0 +1,338 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<title>Mr. pyTermTk Sandbox</title> |
||||
<link rel="icon" type="image/x-icon" href="www/favicon.ico"> |
||||
|
||||
<script src="www/pyodide/pyodide.js"></script> |
||||
|
||||
<link href="www/xterm/xterm.css" rel="stylesheet" /> |
||||
<script src="www/xterm/xterm.js"></script> |
||||
<script src="www/xterm-addon-fit/xterm-addon-fit.js"></script> |
||||
<script src="www/xterm-addon-unicode11/xterm-addon-unicode11.js"></script> |
||||
|
||||
<link href="www/fontawesome/fontawesome.min.css" rel="stylesheet"> |
||||
<link href="www/fontawesome/regular.min.css" rel="stylesheet"> |
||||
|
||||
<script type="text/javascript" src="www/w2ui/w2ui-2.0.min.js"></script> |
||||
<link rel="stylesheet" type="text/css" href="www/w2ui/w2ui-2.0.min.css" /> |
||||
|
||||
<link href="www/codemirror/theme/mbo.css" rel="stylesheet" > |
||||
<link href="www/codemirror/codemirror.css" rel="stylesheet" /> |
||||
<script src="www/codemirror/codemirror.js"></script> |
||||
<script src="www/codemirror/modes/python.js"></script> |
||||
|
||||
<style> |
||||
.CodeMirror { height: 100%; } |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<div id="layout" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px"></div> |
||||
|
||||
<script type="text/javascript"> |
||||
let pstyle = 'border: 1px solid #efefef; padding: 5px;'; |
||||
let expand = 'position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px'; |
||||
new w2layout({ |
||||
box: '#layout', |
||||
name: 'layout_pyTermTk_sandbox', |
||||
padding: 4, |
||||
panels: [ |
||||
//{ type: 'top', size: 50, resizable: true, style: pstyle, html: 'top' }, |
||||
{ type: 'left', size: 200, resizable: true, style: pstyle, |
||||
html: '<div id="sidebar" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px"></div>' }, |
||||
{ type: 'main', style: pstyle, |
||||
toolbar: { |
||||
items: [ |
||||
{ type: 'button', id: 'run_button', text: 'Run', icon: 'far fa-play-circle' }, |
||||
{ type: 'html', id: 'uri', |
||||
html(item) { |
||||
let html = |
||||
'<div style="padding: 0px 10px; margin-top: -2px;" >'+ |
||||
' URI: <input id="codeUri" size="50"/>'+ |
||||
' FPS Cap: <input id="fpsCap" value="30" size="1">'+ |
||||
'</div>'; |
||||
return html; |
||||
}, |
||||
}, |
||||
], |
||||
onClick(event) { run(); } |
||||
}, |
||||
html: '<div id="codeArea" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 25px"></div>'+ |
||||
`<div id="codeArea" style="position: absolute; left: 15px; right: 0px; bottom: 0px"> |
||||
<a href="https://github.com/ceccopierangiolieugenio/pyTermTk">pyTermTk</a> sandbox, |
||||
Powered by <a href="https://pyodide.org/">Pyodide</a> |
||||
and <a href="https://xtermjs.org">xterm.js</a> |
||||
and <a href="https://codemirror.net/5/">CodeMirror5</a> |
||||
and <a href="https://w2ui.com/">w2ui</a> |
||||
</div>`}, |
||||
{ type: 'right', size: 1000, resizable: true, style: pstyle, |
||||
html: '<div id="terminal" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px"></div>' } |
||||
] |
||||
}); |
||||
new w2field({ el: query('#fpsCap')[0], type: 'int', autoFormat: false }) |
||||
</script> |
||||
|
||||
<script type="text/javascript"> |
||||
/* xterm.js demo */ |
||||
/* https://www.npmjs.com/package/xterm-addon-fit */ |
||||
const fitAddon = new FitAddon.FitAddon(); |
||||
/* https://www.npmjs.com/package/xterm-addon-unicode11 */ |
||||
const unicode11Addon = new Unicode11Addon.Unicode11Addon(); |
||||
|
||||
var term = new Terminal({allowProposedApi: true}); |
||||
term.loadAddon(fitAddon); |
||||
term.loadAddon(unicode11Addon); |
||||
|
||||
term.unicode.activeVersion = '11'; |
||||
|
||||
term.open(document.getElementById('terminal')); |
||||
|
||||
term.write('xterm.js - Loaded\n\r') |
||||
|
||||
fitAddon.fit() |
||||
|
||||
w2ui.layout_pyTermTk_sandbox.on('resize', (event) => { |
||||
setTimeout(()=>{fitAddon.fit()},0.5) |
||||
}); |
||||
|
||||
/* Code Mirror */ |
||||
let myCodeMirror = CodeMirror(document.getElementById('codeArea'), { |
||||
mode: "python", |
||||
lineNumbers: true, |
||||
styleActiveLine: true, |
||||
matchBrackets: true |
||||
}); |
||||
myCodeMirror.setOption("theme", "mbo"); |
||||
let getCode = function(){ |
||||
return myCodeMirror.getValue() |
||||
} |
||||
let setCode = function(txt){ |
||||
myCodeMirror.setValue(txt) |
||||
} |
||||
|
||||
|
||||
/* pyodide demo */ |
||||
var pyodide = null |
||||
var run = null |
||||
var namespace = null |
||||
async function main(){ |
||||
pyodide = await loadPyodide(); |
||||
|
||||
let pyodideProxy = { |
||||
consoleLog: function(m){ |
||||
console.log("TTk:",m) |
||||
}, |
||||
termPush: function (s) { |
||||
term.write(s); |
||||
}, |
||||
termSize: function () { |
||||
return [term.cols, term.rows] |
||||
}, |
||||
setTimeout: function(t, i) { |
||||
// console.log("TIME (Start)",i,t) |
||||
return setTimeout(() => ttk_timer(i), t) |
||||
}, |
||||
stopTimeout: function(t) { |
||||
// console.log("TIME (Stop)",t) |
||||
clearTimeout(t) |
||||
}, |
||||
clearTimeout: function(){ |
||||
let highestTimeoutId = setTimeout(";"); |
||||
for (let i = 0 ; i < highestTimeoutId ; i++) { |
||||
clearTimeout(i); |
||||
} |
||||
}, |
||||
setInterval: function(t, i) { |
||||
setTinterval(() => console.log('WIP -> Interval' + i), t) |
||||
} |
||||
}; |
||||
|
||||
term.write('Pyodide ('+pyodide.version+') - Loaded\n\r') |
||||
|
||||
pyodide.registerJsModule("pyodideProxy", pyodideProxy); |
||||
|
||||
term.write('Pyodide Proxy - Loaded\n\r') |
||||
|
||||
let zipResponse = await fetch("bin/TermTk.tgz"); |
||||
let zipBinary = await zipResponse.arrayBuffer(); |
||||
pyodide.unpackArchive(zipBinary, ".tar.gz"); |
||||
|
||||
term.write('TermTk - Loaded\n\r') |
||||
|
||||
zipResponse = await fetch("bin/demo.tgz"); |
||||
zipBinary = await zipResponse.arrayBuffer(); |
||||
pyodide.unpackArchive(zipBinary, ".tar.gz"); |
||||
|
||||
term.write('Demos - Loaded\n\r') |
||||
|
||||
zipResponse = await fetch("bin/tutorial.tgz"); |
||||
zipBinary = await zipResponse.arrayBuffer(); |
||||
pyodide.unpackArchive(zipBinary, ".tar.gz"); |
||||
|
||||
term.write('Tutorials - Loaded\n\r') |
||||
|
||||
/* Sidebar |
||||
Fetch all the files in the pyodide.FS |
||||
And push them in the sidebar |
||||
*/ |
||||
let getAllFiles = function(p){ |
||||
let ls = pyodide.FS.readdir(p) |
||||
let ret = [] |
||||
for(let i=0 ; i<ls.length; i++){ |
||||
let n = p+"/"+ls[i] |
||||
if(ls[i]=='.' || ls[i]=='..' || n==(pyodide.FS.currentPath+"/TermTk")) continue; |
||||
if( pyodide.FS.isDir(pyodide.FS.lstat(n).mode)){ |
||||
ret.push({id:n, text:ls[i], expanded: true, nodes:getAllFiles(n)}) |
||||
}else{ |
||||
if(n.endsWith('.py')){ |
||||
ret.push({id:n, text:ls[i]}) |
||||
} |
||||
} |
||||
} |
||||
return ret |
||||
} |
||||
let files = getAllFiles(pyodide.FS.currentPath) |
||||
|
||||
new w2sidebar({ |
||||
box: '#sidebar', |
||||
name: 'sidebar', |
||||
nodes: files }) |
||||
|
||||
w2ui.sidebar.on('click', function (event) { |
||||
console.log('Last Event: ' + event.type + ' Target: ' + event.target); |
||||
loadFile(event.target) |
||||
}); |
||||
|
||||
var loadFile = function(f){ |
||||
let content = pyodide.FS.readFile(f, {encoding:'utf8'}) |
||||
setCode(content) |
||||
document.getElementById("codeUri").value = f |
||||
} |
||||
|
||||
/* check the "fileUri" field in the address |
||||
and load it if defined */ |
||||
const queryString = window.location.search; |
||||
console.log(queryString); |
||||
const urlParams = new URLSearchParams(queryString); |
||||
fileUri = urlParams.get("fileUri") |
||||
filePath = urlParams.get("filePath") |
||||
if (fileUri != null){ |
||||
pyodide.FS.writeFile(pyodide.FS.currentPath+"/test_file.py", await (await fetch(fileUri)).text()); |
||||
loadFile(pyodide.FS.currentPath+"/test_file.py") |
||||
}else if (filePath != null){ |
||||
loadFile(pyodide.FS.currentPath+"/"+filePath) |
||||
}else{ |
||||
loadFile(pyodide.FS.currentPath+"/demo/demo.py") |
||||
} |
||||
//loadFile("/home/pyodide/tutorial/calculator/calculator.005.py") |
||||
w2ui.sidebar.select(pyodide.FS.currentPath+"/demo/demo.py") |
||||
|
||||
term.write('Starting Demo...\n\r') |
||||
|
||||
namespace = pyodide.globals.get("dict")(); |
||||
pyodide.runPython(` |
||||
import sys |
||||
import TermTk as ttk |
||||
from TermTk.TTkCore.TTkTerm.input import TTkInput |
||||
import pyodideProxy |
||||
|
||||
def ttk_input(val): |
||||
kevt,mevt,paste = TTkInput.key_process(val) |
||||
if kevt or mevt: |
||||
TTkInput.inputEvent.emit(kevt, mevt) |
||||
if paste: |
||||
TTkInput.pasteEvent.emit(paste) |
||||
|
||||
def ttk_resize(w,h): |
||||
ttk.TTkLog.debug(f"Resize: {w=} {h=}") |
||||
if ttk.TTkHelper._rootWidget: |
||||
ttk.TTkHelper._rootWidget._win_resize_cb(w,h) |
||||
ttk.TTkHelper.rePaintAll() |
||||
ttk.TTkTerm.cont() |
||||
|
||||
def ttk_timer(tid): |
||||
ttk.TTkTimer.triggerTimerId(tid) |
||||
|
||||
def ttk_log(val): |
||||
# hex = [f"0x{ord(x):02x}" for x in val] |
||||
ttk.TTkLog.debug("---> "+val.replace("\\033","<ESC>") + " - ") |
||||
ttk.TTkHelper.paintAll() |
||||
|
||||
def ttk_clean(): |
||||
if ttk.TTkHelper._rootWidget: |
||||
ttk.TTkTimer.pyodideQuit() |
||||
ttk.TTkHelper._rootWidget.quit() |
||||
ttk.TTkHelper._focusWidget = None |
||||
ttk.TTkHelper._rootCanvas = None |
||||
ttk.TTkHelper._rootWidget = None |
||||
ttk.TTkHelper._updateBuffer = set() |
||||
ttk.TTkHelper._updateWidget = set() |
||||
ttk.TTkHelper._overlay = [] |
||||
ttk.TTkHelper._shortcut = [] |
||||
ttk.TTkLog._messageHandler = [message_handler] |
||||
|
||||
def ttk_init(): |
||||
ttk.TTkToolTip.toolTipTimer = ttk.TTkTimer() |
||||
ttk.TTkToolTip.toolTipTimer.timeout.connect(ttk.TTkToolTip._toolTipShow) |
||||
|
||||
def message_handler(mode, context, message): |
||||
msgType = "DEBUG" |
||||
if mode == ttk.TTkLog.InfoMsg: msgType = "[INFO]" |
||||
elif mode == ttk.TTkLog.WarningMsg: msgType = "[WARNING]" |
||||
elif mode == ttk.TTkLog.CriticalMsg: msgType = "[CRITICAL]" |
||||
elif mode == ttk.TTkLog.FatalMsg: msgType = "[FATAL]" |
||||
elif mode == ttk.TTkLog.ErrorMsg: msgType = "[ERROR]" |
||||
pyodideProxy.consoleLog(f"{msgType} {context.file} {message}") |
||||
# Register the callback to the message handler |
||||
ttk.TTkLog.installMessageHandler(message_handler) |
||||
`,{ globals: namespace } |
||||
); |
||||
|
||||
let ttk_log = namespace.get("ttk_log"); |
||||
let ttk_input = namespace.get("ttk_input"); |
||||
let ttk_timer = namespace.get("ttk_timer"); |
||||
let ttk_resize = namespace.get("ttk_resize"); |
||||
let ttk_clean = namespace.get("ttk_clean"); |
||||
let ttk_init = namespace.get("ttk_init"); |
||||
|
||||
term.onResize( (obj) => { |
||||
term.reset() |
||||
ttk_resize(obj.cols, obj.rows) |
||||
}); |
||||
term.onData((d, evt) => { ttk_input(d) }) |
||||
|
||||
pyodide.runPython(` |
||||
import sys,os |
||||
sys.path.append(os.path.join(sys.path[0],'demo')) |
||||
__name__ = "__main__" |
||||
`,{ globals: namespace } |
||||
); |
||||
|
||||
run = function(){ |
||||
ttk_clean() |
||||
console.log("Run App") |
||||
|
||||
let filename = document.getElementById("codeUri").value |
||||
let fps = document.getElementById("fpsCap").value |
||||
let pwd = pyodide.PATH.dirname(filename) |
||||
|
||||
pyodide.runPython(` |
||||
__file__='`+filename+`' |
||||
os.chdir('`+pwd+`') |
||||
ttk.TTkCfg.maxFps = `+fps,{ globals: namespace }) |
||||
|
||||
ttk_init() |
||||
|
||||
let content = getCode() |
||||
pyodide.runPython(content,{ globals: namespace }); |
||||
|
||||
ttk_log(filename + " - LOADED") |
||||
}; |
||||
run() |
||||
} |
||||
main() |
||||
</script> |
||||
|
||||
</body> |
||||
</html> |
||||
Loading…
Reference in new issue