How to write a TCL procedure that does something in an infinite loop until a user key press is detected?

Viewed 206

I am not sure how this is to be done in TCL. So basically, I need to write a procedure that will execute at 500ms intervals until a key is pressed. A key press will basically cause it to exit. How can this be written in TCL? I don't know how to detect key presses in TCL.

2 Answers

The tcl event loop makes it easy. The tricky bit is setting your terminal into raw mode so that keypresses aren't buffered. The Tcler's Wiki shows how for both Unix and Windows systems.

Example for Unix:

#!/usr/bin/env tclsh

proc enableRaw {{channel stdin}} {
   exec /bin/stty raw -echo <@$channel
}
proc disableRaw {{channel stdin}} {
   exec /bin/stty -raw echo <@$channel
}

proc timer {} {
    puts "Waiting for keypress..."
    after 500 timer ;# Re-schedule the looping procedure
}

proc read_key {} {
    set c [read stdin 1]
    if {[string length $c] == 1 || [chan eof stdin]} {
        global done
        puts "Got a keypress!"
        set done 1 ;# Exit the event loop.
    }
}

# Set up stdin to be non-blocking and register a callback function
# for when it's readable, and set it to raw mode
chan configure stdin -blocking 0 -buffering none
chan event stdin readable read_key
enableRaw
# Start the timer
after 500 timer
# And enter the event loop
vwait done
# Restore the terminal to normal cooked mode
disableRaw

It may be done in Tk:

#!/usr/bin/wish
bind . <KeyPress> {puts "Key has been pressed"; exit; }
realpath /usr/bin/wish
/usr/bin/wish8.6

IMPORTANT NOTE: Key should be pressed in the Tk window, which will be opened after Tk script will start.

More complex solution address both parts of the question. It is based on the following PyTk pytclsub.py script:

#!/usr/bin/python3.9
import tkinter as tk
import sys
import subprocess
import os
import signal
from pynput import keyboard

p = 0
def on_press(key):
    print("Key pressed, exiting")
    os.kill(p, signal.SIGINT)
    os._exit(0)

listener = keyboard.Listener(on_press=on_press)
listener.start()
print("Started keyboard listener")

def run_process():
    path = r"./mytcl.tcl"
    p = subprocess.run(path)

root = tk.Tk()
log = tk.Text(root)
b = tk.Button(root, text="Run stuff", command=lambda: run_process())

log.pack()
b.pack()    

root.mainloop()

TCL script mytcl.tcl

#!/usr/bin/tclsh
package require Tclx
while {1} {
    sleep 1
    puts "Waiting for keypress..."
}

Usage:

  1. Run pytclsub.py script
  2. When main window will open, press Run stuff button
  3. Put mouse cursor within main window
  4. Press any key

Expected outcome: application will exit

Example of the output

pytclsub.py
Started keyboard listener
Waiting for keypress...
Waiting for keypress...
Waiting for keypress...
Key pressed, exiting
Related