Run Python with Tkinter (sometimes) headless OR replacement for root.after()

1.5k views Asked by At

I have working code below.

I have a set of machines operated with Python. I have a gui in Tkinter but very often these machines are run headless with the python code auto-starting at boot.

I really like the design pattern of using root.after() to start multiple tasks and keep them going. My problem is that this comes from the Tkinter library and when running headless the line "root=Tk()" will throw an error.

I have two questions

  1. Can I perform some trick to have the code ignore the fact there is no display?

OR

  1. Is there a library that will match the design pattern of Tkinter "root.after(time_in_ms,function_to_call)".

I did try to poke around in the underlying code of Tkinter to see if there was simply another library wrapped by Tkinter but I don't have the skill to decode what is going on in that library.

This code works with a display connected: (it prints hello 11 times then ends)

from Tkinter import *

#   def __init__(self, screenName=None, baseName=None, className='Tk', useTk=1, sync=0, use=None):

root = Tk()  # error is thrown here if starting this command in headless hardware setup

h = None
count = 0
c = None


def stop_saying_hello():
    global count
    global h
    global c
    if count > 10:
        root.after_cancel(h)
        print "counting cancelled"
    else:
        c = root.after(200, stop_saying_hello)


def hello():
    global h
    global count
    print "hello " + str(count)
    count += 1
    h = root.after(1000, hello)


h = root.after(1000, hello)  # time in ms, function
c = root.after(200, stop_saying_hello)


root.mainloop()

If this is run headless - in an ssh session from a remote computer then this error message is returned

Traceback (most recent call last): File "tkinter_headless.py", line 5, in root = Tk() File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1813, in init self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use) _tkinter.TclError: no display name and no $DISPLAY environment variable

1

There are 1 answers

1
furas On BEST ANSWER

You can use

or create own taks manager with own after() and mainloop()

Simple example

import time

class TaskManager():

    def __init__(self):
        self.tasks = dict()
        self.index = 0
        self.running = True

    def after(self, delay, callback):
        # calcuate time using delay
        current_time = time.time()*1000
        run_time = current_time + delay

        # add to tasks
        self.index += 1
        self.tasks[self.index] = (run_time, callback)

        # return index
        return self.index

    def after_cancel(self, index):
        if index in self.tasks:
            del self.tasks[index]

    def mainloop(self):

        self.running = True

        while self.running:

            current_time = time.time()*1000

            # check all tasks
            # Python 3 needs `list(self.tasks.keys())` 
            # because `del` changes `self.tasks.keys()`
            for key in self.tasks.keys():
                if key in self.tasks:
                    run_time, callback = self.tasks[key]
                    if current_time >= run_time:
                        # execute task  
                        callback()
                        # remove from list
                        del self.tasks[key]

            # to not use all CPU
            time.sleep(0.1)

    def quit(self):
        self.running = False

    def destroy(self):
        self.running = False

# --- function ---

def stop_saying_hello():
    global count
    global h
    global c

    if count > 10:
        root.after_cancel(h)
        print "counting cancelled"
    else:
        c = root.after(200, stop_saying_hello)

def hello():
    global count
    global h

    print "hello", count
    count += 1

    h = root.after(1000, hello)

# --- main ---

count = 0
h = None
c = None

root = TaskManager()

h = root.after(1000, hello)  # time in ms, function
c = root.after(200, stop_saying_hello)

d = root.after(12000, root.destroy)

root.mainloop()