Search code examples
python-2.7tkinterheadless

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


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


Solution

  • 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()