PYTHON

Bidirectionnal python/tk by talking to tk interpreter back and forth

Last time I exposed an old way learned in physical labs to do C or python/tk like in the old days: by summoning a tcl/tk interpreter and piping commands to it.

But what fun is it?

It’s funnier if the tcl/tk interperpreter talks back to python 😀 as an hommage to the 25 years awaited TK9 versions that solves a lot of unicode trouble.

Beforehand, to make sense to the code a little warning is required : this code targets only POSIX environment and loses portability because I chose to use a way that is not the « one best way » for enabling bidirectionnal talks.

First and foremost, the Popen now use p.stdout=PIPE enabling the channel on which tcl will talk. As a joke puts/gets are named from tcl/tk functions and are used in python to push/get strings from tcl.

Instead of using multithreading having one thread listen to the output and putting the events in a local queue that the main thread will consume I chose the funniest technique of setting tcl/tk output non blocking which does not work on windows. This is the fnctl part of the code.

Then, I chose not to parse the output of tcl/tk but exec it, making tcl/tk actually push python commands back to python. That’s the exec part of the code.

For this I needed an excuse : so I added buttons to change minutes/hours back and forth.

That’s the moment we all are gonna agree that tcl/tk that tcl/tk biggest sin is its default look. Don’t worry, next part is about using themes.

Compared to the first post, changes are minimal 😀

This is how it should look :

And here is the code, largely still below 100 sloc (by 3 lines).

#!/usr/bin/env python
from subprocess import Popen, PIPE
from time import sleep, time, localtime
import fcntl
import os

# let's talk to tk/tcl directly through p.stdin
p = Popen(['wish'], stdin=PIPE, stdout=PIPE)

fd = p.stdout.fileno()
flag = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
# ^-- this 3 lines can be replaced with this one liner --v
# os.set_blocking(p.stdout.fileno(), False)

def puts(s):
    for l in s.split("\n"):
        p.stdin.write((l + "\n").encode())
        p.stdin.flush()

def gets():
    ret=p.stdout.read()
    p.stdout.flush()
    return ret

WIDTH=HEIGHT=400

puts(f"""
canvas .c -width WIDTH -height HEIGHT -bg white
pack .c
. configure -background white

ttk::button  .ba -command   puts ch-=1  -text <<
pack .ba -side left   -anchor w
ttk::button .bb -command   puts cm-=1  -text  <
pack .bb -side left -anchor w
ttk::button .bc -command   puts ch+=1  -text >> 
pack .bc  -side right -anchor e
ttk::button .bd -command   puts cm+=1  -text > 
pack .bd  -side right -anchor e
""")

# Constant are CAPitalized in python by convention
from cmath import  pi as PI, e as E
ORIG=complex(WIDTH/2, HEIGHT/2)

# correcting python notations j => I  
I = complex("j")
rad_per_sec = 2.0 * PI /60.0
rad_per_min = rad_per_sec / 60
rad_per_hour = rad_per_min / 12

origin_vector_hand = WIDTH/2 *  I

size_of_sec_hand = .9
size_of_min_hand = .8
size_of_hour_hand = .65

rot_sec = lambda sec : -E ** (I * sec * rad_per_sec )
rot_min = lambda min : -E ** (I *  min * rad_per_min )
rot_hour = lambda hour : -E ** (I * hour * rad_per_hour )

to_real = lambda c1,c2 : "%f %f %f %f" % (c1.real,c1.imag,c2.real, c2.imag)
for n in range(60):
    direction= origin_vector_hand * rot_sec(n)
    start=.9 if n%5 else .85
    puts(f".c create line to_real(ORIG+start*direction,ORIG+.95*direction)")
    sleep(.01)

diff_offset_in_sec = (time() % (24*3600)) - \
    localtime()[3]*3600 -localtime()[4] * 60.0 \
    - localtime()[5] 
ch=cm=0
while True:
    # eventually parsing tcl output 
    back = gets()
    # trying is more concise than checking
    try:
        back = back.decode()
        exec(back)
    except Exception as e:
        pass

    t = time()
    s= t%60
    m = m_in_sec = t%(60 * 60) + cm * 60
    h = h_in_sec = (t- diff_offset_in_sec)%(24*60*60) + ch * 3600 + cm * 60
    puts(".c delete second")
    puts(".c delete minute")
    puts(".c delete hour")
    c0=ORIG+ -.1 * origin_vector_hand * rot_sec(s)
    c1=ORIG+ size_of_sec_hand * origin_vector_hand * rot_sec(s)
    puts( f".c create line to_real(c0,c1) -tag second -fill blue -smooth true")
    c1=ORIG+size_of_min_hand * origin_vector_hand * rot_min(m)
    puts(f".c create line to_real(ORIG, c1) -tag minute -fill green -smooth true")
    c1=ORIG+size_of_hour_hand * origin_vector_hand * rot_hour(h)
    puts(f".c create line to_real(ORIG,c1) -tag hour -fill red -smooth true")
    puts("flush stdout")
    sleep(.1)

I have been mentored in a physical lab where we where doing the pipe, fork, dup2 dance to tcl/tk from C to give a nice output to our simulations so we could control our instuition was right and could extract pictures for the publications. This is a trick that is almost as new as my arteries.
My mentor used to say : we are not coders, we need stuff to work fast and neither get drowned in computer complexity or endless quest for « the one best way » nor being drowned in bugs, we aim for the Keep It Simple Stupid Ways.

Hence, this is a Keep It Simple Stupid approach that I revived for the sake of seeing if it was still robust after 35 years without using it.

Well, if it’s robust and it’s working: it ain’t stupid even if it isn’t the « one best idiomatic way ». 😛

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button