diff --git a/TermTk/TTkWidgets/TTkTerminal/terminal.py b/TermTk/TTkWidgets/TTkTerminal/terminal.py index 6a4f8fdf..61976a6c 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminal.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminal.py @@ -63,7 +63,7 @@ class TTkTerminal(TTkAbstractScrollArea): self.titleChanged = self._terminalView.titleChanged self.bell = self._terminalView.bell self.terminalClosed = pyTTkSignal(TTkTerminal) - self._terminalView.closed.connect(lambda : self.terminalClosed.emit(self)) + self._terminalView.terminalClosed.connect(lambda : self.terminalClosed.emit(self)) def close(self): self._terminalView.close() diff --git a/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py b/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py index daf75621..452aef77 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminal_screen.py @@ -198,10 +198,11 @@ class _TTkTerminalScreen(_TTkTerminalScreen_CSI, _TTkTerminalScreen_C1): self._terminalCursor = (x,y) self._pushTxt(lll,irm) - def paintEvent(self, canvas: TTkCanvas, w:int, h:int, ox:int=0, oy:int=0) -> None: + def paintEvent(self, canvas: TTkCanvas, w:int, h:int, ox:int=0, oy:int=0, select:list=None) -> None: w,h = self._w, self._h ll = len(self._bufferedLines) for y in range(ll-oy): canvas.drawTTkString(pos=(0,y),text=self._bufferedLines[oy+y]) s = (-ox,ll-oy,w,h) canvas.paintCanvas(self._canvas,s,s,s) + canvas.drawText(pos=(0,0),text=f"({select})") diff --git a/TermTk/TTkWidgets/TTkTerminal/terminalview.py b/TermTk/TTkWidgets/TTkTerminal/terminalview.py index f676e378..f517a1e5 100644 --- a/TermTk/TTkWidgets/TTkTerminal/terminalview.py +++ b/TermTk/TTkWidgets/TTkTerminal/terminalview.py @@ -89,7 +89,8 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): reportMove: bool = False sgrMode: bool = False - __slots__ = ('_shell', '_fd', '_inout', '_pid', + __slots__ = ('_selecct', + '_shell', '_fd', '_inout', '_pid', '_quit_pipe', '_resize_pipe', '_mode_normal' '_clipboard', @@ -97,10 +98,10 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): '_keyboard', '_mouse', '_terminal', '_screen_current', '_screen_normal', '_screen_alt', # Signals - 'titleChanged', 'bell', 'closed') + 'titleChanged', 'bell', 'terminalClosed') def __init__(self, *args, **kwargs): self.bell = pyTTkSignal() - self.closed = pyTTkSignal() + self.terminalClosed = pyTTkSignal() self.titleChanged = pyTTkSignal(str) self._shell = os.environ.get('SHELL', 'sh') @@ -110,6 +111,7 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): self._mode_normal = True self._quit_pipe = None self._resize_pipe = None + self._select = None self._terminal = TTkTerminalView._Terminal() self._keyboard = TTkTerminalView._Keyboard() self._mouse = TTkTerminalView._Mouse() @@ -252,10 +254,15 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): def _inputGenerator(self): while rs := select( [self._inout,self._quit_pipe[0],self._resize_pipe[0]], [], [])[0]: if self._quit_pipe[0] in rs: + os.close(self._quit_pipe[0]) + os.close(self._quit_pipe[1]) + os.close(self._resize_pipe[0]) + os.close(self._resize_pipe[1]) return if self._resize_pipe[0] in rs: self._resizeScreen() + os.read(self._resize_pipe[0],100) if self._inout not in rs: continue @@ -274,7 +281,7 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): fcntl.fcntl(self._inout, fcntl.F_SETFL, _fl) except Exception as e: _termLog.error(f"Error: {e=}") - self.closed.emit() + self.terminalClosed.emit() return # out = out.decode('utf-8','ignore') @@ -952,9 +959,29 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): self._inout.write(bah) return True - def mousePressEvent(self, evt): return self._sendMouse(evt) | True + def mousePressEvent(self, evt): + if self._mouse.reportPress: + self._select = None + return self._sendMouse(evt) | True + x,y = evt.x,evt.y + ox,oy = self.getViewOffsets() + self._select = [(x+ox,y+oy)] + self.update() + return True + + def mouseDragEvent(self, evt): + if self._mouse.reportPress: + self._select = None + return self._sendMouse(evt) + if not self._select: + return True + x,y = evt.x,evt.y + ox,oy = self.getViewOffsets() + self._select[1:] = [(x+ox,y+oy)] + self.update() + return True + def mouseReleaseEvent(self, evt): return self._sendMouse(evt) - def mouseDragEvent(self, evt): return self._sendMouse(evt) def wheelEvent(self, evt): return True if self._sendMouse(evt) else super().wheelEvent(evt) def mouseTapEvent(self, evt): return self._sendMouse(evt) def mouseDoubleClickEvent(self, evt): return self._sendMouse(evt) @@ -966,7 +993,7 @@ class TTkTerminalView(TTkAbstractScrollView, _TTkTerminal_CSI_DEC): def paintEvent(self, canvas: TTkCanvas): w,h = self.size() ox,oy = self.getViewOffsets() - self._screen_current.paintEvent(canvas,w,h,ox,oy) + self._screen_current.paintEvent(canvas,w,h,ox,oy,self._select) diff --git a/tests/test.pty.006.terminal.04.py b/tests/test.pty.006.terminal.04.py new file mode 100755 index 00000000..f340f091 --- /dev/null +++ b/tests/test.pty.006.terminal.04.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 test is based on: +# pyte - In memory VTXXX-compatible terminal emulator. +# Terminal Emulator Example +# https://github.com/selectel/pyte/blob/master/examples/terminal_emulator.py +# +# pty β€” Pseudo-terminal utilitiesΒΆ +# https://docs.python.org/3/library/pty.html#example +# +# Using a pseudo-terminal to interact with interactive Python in a subprocess +# by Thomas Billinger +# https://gist.github.com/thomasballinger/7979808 +# +# Run interactive Bash with popen and a dedicated TTY Python +# https://stackoverflow.com/questions/41542960/run-interactive-bash-with-popen-and-a-dedicated-tty-python + +import os +import pty +import sys +import threading +import argparse +from select import select + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-d', help='Debug (Add LogViewer Panel)', action='store_true') +args = parser.parse_args() + +# ttk.TTkLog.use_default_file_logging() +root = ttk.TTk(layout=ttk.TTkGridLayout(), mouseTrack=True) + +split = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL) + +split.addItem(top := ttk.TTkLayout()) + +if args.d: + split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log', size=20) + +quitBtn = ttk.TTkButton(text="QUIT", border=True) +quitBtn.clicked.connect(ttk.TTkHelper.quit) + +cb_c = ttk.TTkCheckbox(pos=(0,3),size=(20,1), text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked) +cb_s = ttk.TTkCheckbox(pos=(0,4),size=(20,1), text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked) +cb_z = ttk.TTkCheckbox(pos=(0,5),size=(20,1), text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked) +cb_q = ttk.TTkCheckbox(pos=(0,6),size=(20,1), text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked) + +cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked)) +cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked)) +cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked)) +cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked)) + +win = ttk.TTkWindow(pos=(10,0), size=(100,30), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint) +term = ttk.TTkTerminal(parent=win) +term.bell.connect(lambda : ttk.TTkLog.debug("BELL!!! πŸ””πŸ””πŸ””")) +term.titleChanged.connect(win.setTitle) +term.runShell() +term.terminalClosed.connect(win.close) +win.closed.connect(term.close) + +top.addWidgets([quitBtn, cb_c, cb_s, cb_z, cb_q, win]) + +term.setFocus() + +root.mainloop() \ No newline at end of file diff --git a/tests/test.pty.006.terminal.05.py b/tests/test.pty.006.terminal.05.py new file mode 100755 index 00000000..84866981 --- /dev/null +++ b/tests/test.pty.006.terminal.05.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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. + +import os, sys, argparse +from select import select + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-d', help='Debug (Add LogViewer Panel)', action='store_true') +args = parser.parse_args() + +# ttk.TTkLog.use_default_file_logging() +root = ttk.TTk(layout=ttk.TTkGridLayout(), mouseTrack=True) + +split = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.VERTICAL) + +split.addItem(top := ttk.TTkLayout()) + +if args.d: + split.addWidget(ttk.TTkLogViewer(follow=False ), title='Log', size=20) + +quitBtn = ttk.TTkButton(text="QUIT", border=True) +quitBtn.clicked.connect(ttk.TTkHelper.quit) + +cb_c = ttk.TTkCheckbox(pos=(0,3),size=(20,1), text="CTRL-C (VINTR) ", checked=ttk.TTkK.Checked) +cb_s = ttk.TTkCheckbox(pos=(0,4),size=(20,1), text="CTRL-S (VSTOP) ", checked=ttk.TTkK.Checked) +cb_z = ttk.TTkCheckbox(pos=(0,5),size=(20,1), text="CTRL-Z (VSUSP) ", checked=ttk.TTkK.Checked) +cb_q = ttk.TTkCheckbox(pos=(0,6),size=(20,1), text="CTRL-Q (VSTART)", checked=ttk.TTkK.Checked) + +cb_c.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_C,x==ttk.TTkK.Checked)) +cb_s.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_S,x==ttk.TTkK.Checked)) +cb_z.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Z,x==ttk.TTkK.Checked)) +cb_q.stateChanged.connect(lambda x: ttk.TTkTerm.setSigmask(ttk.TTkTerm.Sigmask.CTRL_Q,x==ttk.TTkK.Checked)) + +win = ttk.TTkWindow(pos=(10,0), size=(100,30), title="Terminallo n.2", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint) +term = ttk.TTkTerminal(parent=win) +term.bell.connect(lambda : ttk.TTkLog.debug("BELL!!! πŸ””πŸ””πŸ””")) +term.titleChanged.connect(win.setTitle) +term.runShell() +term.terminalClosed.connect(win.close) +win.closed.connect(term.close) + +winT = ttk.TTkWindow(pos=(20,10), size=(100,30), title="TextEdit", border=True, layout=ttk.TTkVBoxLayout(), flags = ttk.TTkK.WindowFlag.WindowMinMaxButtonsHint|ttk.TTkK.WindowFlag.WindowCloseButtonHint) +ttk.TTkTextEdit(parent=winT, readOnly=False, lineNumber=True) + +top.addWidgets([quitBtn, cb_c, cb_s, cb_z, cb_q, win, winT]) + +term.setFocus() + +root.mainloop() \ No newline at end of file