Bidirectionnal python/tk by talking to tk interpreter back and forth
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 ». 😛