11

One can write a simple python script to embed an xterm into a tk frame:

from Tkinter import *
import subprocess

root = Tk()
termf = Frame(root, height=800, width=1000)

termf.pack(fill=BOTH, expand=YES)
wid = termf.winfo_id()

After the window is established

proc = subprocess.Popen('xterm -into %d -sb ' % wid,shell=True)
root.mainloop()

On my computer it looks like embedded xterm How can I make the embedded xterm be dynamically sized to the size of the termf frame even when resizing the frame(by dragging the corner)?

Response to answer by @tinmarino tk frame with xterm embedded, but at wrong size, not completely filling the frame

oguz ismail
  • 1
  • 16
  • 47
  • 69
esmit
  • 1,748
  • 14
  • 27
  • Ask the root what its sizes are and use those, with adjustments, as the parameters to Frame()? – wallyk Oct 31 '16 at 23:08
  • I edited my question to reflect that although I can instantiate the xterm to the right size of the Frame, as you suggest @wallyk, I want to know how to continue to get it to the size of the Frame as the Frame is resized by dragging its corner. – esmit Nov 01 '16 at 05:50
  • You would need to use an event binding to constantly update the size. – Mike - SMT Nov 01 '17 at 20:10
  • While not dynamic as I hoped, the xterm can be resized using commands such as `echo -e '\e[4;600;800t'` in the xterm itself. See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html window manipulation. – esmit Jan 09 '18 at 20:21

2 Answers2

4

small

resized

Implementation of @EthanField solution. 3 functions:

  1. main create gui, xterm and plug the pipes
  2. on_resize is called when the frame containing the terminal is resized
  3. get_xterm_pts is parsing the output for the pts to feed stdin to xterm
import tkinter as tk
import subprocess as sp
from re import match
from threading import Thread
from queue import Queue


def main():
    # Init
    root = tk.Tk()
    queue = Queue()

    # Pack main frame
    termf = tk.Frame(root, width=800, height=800)
    termf.pack(fill=tk.BOTH, expand=tk.YES, padx=0, pady=0)
    wid = termf.winfo_id()

    # Allow window resize
    sp.Popen("""echo '*VT100.allowWindowOps: true' | xrdb -merge""", shell=True)

    # Craft command
    cmd = (
        # Create into me
        f'xterm -into {wid} -geometry 100x50 '
        # Log to stdout
        r'-sb -l -lc -lf /dev/stdout '
        # Launch `ps` command: output, tty, = for remove header
        """-e /bin/bash -c "ps -o tt=;bash" """
        r'| tee'
    )
    print('Launching:', cmd)

    # Spawn Xterm
    process = sp.Popen(
        cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE)
    print('Xterm pid:', process.pid)

    # Get pts
    thread = Thread(target=lambda: get_xterm_pts(termf, process, queue))
    thread.start()

    # Set resize callback
    termf.bind("<Configure>", lambda event: on_resize(event, queue))

    # Start
    root.mainloop()


def on_resize(event, queue):
    """On resize: send escape sequence to pts"""
    # Magic && Check
    magic_x, magic_y = 6.1, 13
    print('Resize (w, h):', event.width, event.height)
    if not queue.queue: return

    # Calculate
    width = int(event.width / magic_x)
    height = int(event.height / magic_y)
    print('To (lin,col):', height, width)
    ctl = f"\u001b[8;{height};{width}t"

    # Send to pts
    with open(queue.queue[0], 'w') as f:
        f.write(ctl)


def get_xterm_pts(parent, process, queue):
    """Retrieve pts(`process`) -> `queue`"""
    while True:
        out = process.stdout.readline().decode()
        print('Xterm out' + out)

        match_pts = match(r'pts/\d+', out)
        if match_pts:
            pts = '/dev/' + match_pts.group(0)
            print('-----------> pts:', pts)
            queue.put(pts)
            break

        if out == b'' and process.poll() is not None:
            break

    # Resize now
    fake_event = tk.Event()
    fake_event.width = parent.winfo_width()
    fake_event.height = parent.winfo_height()
    on_resize(fake_event, queue)


if __name__ == '__main__':
    main()

The search of the pts of the new shell in a new thread seems a lot, but we want the pts of the interactive shell and not the sh which spawn it. I actually tried pip -> pts with some Popen ps, and it was less resilient.

11844 pts/1    00:00:00 sh      <- pid returned to Popen
11847 pts/1    00:00:00 xterm   <- then ...
11848 pts/1    00:00:00 tee     <- that is how a pipe work, first plug, then spawn (apparently) (seems logical)
11854 pts/35   00:00:00 bash    <- then favorite shell is Michel 
Tinmarino
  • 3,693
  • 24
  • 33
  • 1
    Replace `fake_event = Namespace()` with `fake_event = tk.Event()` – stovfl Jan 19 '20 at 10:21
  • Not quite. My window still doesn't start with the right size, so then it doesn't resize correctly (although it is getting the resize commands, which is a good start). I will put a picture in the original question, as it doesn't seem possible here to paste it. – esmit Jan 22 '20 at 08:20
  • Does it resize somehow (i.e. the xterm size is changing) ? I putted some magic `magic_x, magic_y = 6.1, 13`. In your case, you must increase like: `magic_x, magic_y = 7, 15` You may change that. Should have putted at beginning maybe. Thank for the feedback. – Tinmarino Jan 22 '20 at 14:50
  • 1
    It isn't good practise to call `tkinter` functions from threads other than the one where the `tk.Tk()` was created. In rare cases, it can crash `python` without an error message/traceback. – TheLizzard Nov 17 '22 at 13:14
1

As was pointed out in the comments above you can use .bind() on the Tk() window in order to get a call back every time the window is configured.

from tkinter import *

root = Tk()

def callback(event):
    print(event.width, event.height)

root.bind("<Configure>", callback)

root.mainloop()

I don't have the ability to test how you can use this to resize the xterm window on Windows 10. So I'll leave this answer here for anyone who wants to edit it or use this answer to build their own.

Ethan Field
  • 4,646
  • 3
  • 22
  • 43
  • I understand how to write a callback. How can that callback communicate with the opened xterm? – esmit Nov 08 '17 at 22:15
  • The call back gives you the size of the widget. Surely you can use that to change the size of the xterm? – Ethan Field Nov 08 '17 at 22:43
  • Thanks, it helped (especially from someone on a Windows machine, you wouldn't get it right anyway) ! I will not edit it because my working code is longer (escape sequence, synchronization, prints). – Tinmarino Jan 19 '20 at 04:47