title: TclError: no display name and no $DISPLAY environment variable
body: <p>I have developed my own photobooth software using a Raspberry Pi and a DSLR cam, as well as Python, TkInter and gPhoto library. The photobooth software is pretty flexible, because of using a config file and supports serveral types of input buttons and leds as well as live preview and random photo gallery (as a screensaver). I am also able to manipulate camera settings for live preview and taking the final photo.  </p>
  
  <p>Most of the time the software runs quite stable. But somethings (mostly once at an evening) I get an unhandled exception which I am not able to fix and the photobooth software needs to be restarted manually. The unhandled exception appears after taking a final photo. Due to the fact that the unhandled exception appears randomly I am not able to reproduce it on my own.</p>
  
  <p>The unhandled exception is always the same:</p>
  
  <blockquote>
    <p>Traceback (most recent call last):</p>
    
    <p>File "/home/pi/photoBooth/scripts/photoBoothGUI.pyw", line 476, in main
       try: ThreadedClient(Tk()).start()</p>
    
    <p>File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1823, in <strong>init</strong>
       self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)</p>
    
    <p>TclError: no display name and no $DISPLAY environment variable</p>
  </blockquote>
  
  <p>As a quick'n'dirty solution I already tried to put most of the function calls which could possibly throw an exception inside a try-and-catch-block. But that did not fix the exception above.</p>
  
  <p>Here is the code of photoBoothGui.pyw:</p>
  
  <pre><code>#!/usr/bin/python
  # -*- coding: utf-8 -*-
  
  #Imports
  import Queue, threading
  import sys, os, traceback
  import time, datetime, random
  
  from gPhotoWrapper import GPhotoWrapper
  from photoUtils import PhotoUtils
  
  from RPi import GPIO
  from Tkinter import Tk, Frame, Label, BOTH, Button, CENTER
  from tkFont import Font
  from PIL import Image, ImageTk, ImageOps
  from configparser import ConfigParser
  from enum import Enum
  from shutil import copyfile
  
  
  #Class Definitions
  
  class MessageType(Enum):
      IMAGE = 0
      TEXT = 1
      IMAGE_TEXT = 2
  
  class Message():
  
      def __init__(self, mType, text = None, fontSize=None, image=None):
          self.mType = mType
          self.text = text
          self.fontSize = fontSize
          self.image = image
  
  class GUI(Frame):
  
      def __init__(self, parent, queue, config):
  
          #Init root window and root queue
          Frame.__init__(self, parent)
          self.parent = parent
          self.queue = queue
  
          #Configure font
          self.labelFont = Font(family=config.get('Font','Family'), size=config.get('Font','SizeTitle'))
          self.labelFont.configure(weight='bold') if config.getboolean('Font','Bold') else None
  
          #Configure main label    
          self.mainLabel = Label(self, compound=CENTER, font=self.labelFont, text=config.get('RootWindow','AppTitle'), wraplength=1280)
          self.mainLabel.configure(background=config.get('RootWindow','BackgroundColor'))
          self.mainLabel.configure(foreground=config.get('RootWindow','ForegroundColor'))
          self.mainLabel.pack(fill=BOTH, expand=1)
          self.pack(fill=BOTH, expand=1)
  
          #Configure root window
          self.parent.title(config.get('RootWindow','AppTitle'))
          self.parent.attributes('-fullscreen', config.getboolean('RootWindow','Fullscreen'))
          self.config(cursor='none')
  
  
      def processIncoming(self):
  
          while self.queue.qsize():
              try:
                  msg = self.queue.get(0)
                  if msg.mType == MessageType.TEXT:
                      self.showText(msg.text, msg.fontSize)
                      self.resetImage()
                  elif msg.mType == MessageType.IMAGE:
                      self.showImage(msg.image)
                      self.resetText()
                  elif msg.mType == MessageType.IMAGE_TEXT:
                      self.showImage(msg.image)
                      self.showText(msg.text, msg.fontSize)
                  else:
                      print('Message Type "' + str(msg.mType) + '" not implemented, yet')
              except:
                  pass
  
      def showImage(self, img):
          try:
              photoImage = ImageTk.PhotoImage(img)
              self.mainLabel.configure(image = photoImage)
              self.mainLabel.image = photoImage
          except:
              pass
  
      def showText(self, textToShow, fontSize):
          try:
              self.mainLabel.configure(text=textToShow)
              self.labelFont.configure(size=fontSize)
          except:
              pass
  
      def resetImage(self):
          self.mainLabel.configure(image='')
  
      def resetText(self):
          self.mainLabel.configure(text='')
  
      def getMaxImageWidth(self):
          return self.mainLabel.winfo_width();
  
      def getMaxImageHeight(self):
          return self.mainLabel.winfo_height();
  
  class ClientState(Enum):
      BUSY = 0
      READY_CAPTURE = 1
  
  class ThreadedClient:
  
      def __init__(self,window):
  
          #Save a reference to root window
          self.window = window
  
          #Init config object
          self.config = ConfigParser()
          try:
              if not self.config.read(sys.argv[1]):
                  raise IOError('File "' + sys.argv[1] + '" not found!') 
          except IndexError, IOError:
              if not self.config.read(os.path.splitext(os.path.basename(sys.argv[0]))[0]+'.config'):
                  print('Loading config file failed.')
                  sys.exit()
  
          #Setup GPhoto Wrapper
          self.photoWrapper = GPhotoWrapper()
  
          #Setup photo utils
          self.photoUtils = PhotoUtils()
  
          #Create queue
          self.queue = Queue.Queue()
  
          #Setup the GUI
          self.gui = GUI(window, self.queue , self.config)
  
          #Bind keypress event to GUI
          self.gui.parent.bind("&lt;Return&gt;", self.readKeypressCapture)
  
          #Start periodic call in GUI
          self.periodicCall(self.config.getint('RootWindow','UpdateIntervall'))
  
          #Setup the GPIO 
          self.setupGPIO()
  
          #Setup Slideshow thread
          self.setupThreads()
  
          #Set the application to be ready for taking the first photo
          self.setClientState(ClientState.READY_CAPTURE)
  
      def start(self):
          self.window.protocol('WM_DELETE_WINDOW', self.close)
          self.window.mainloop()
  
      def close(self):
  
          #Cancel background thread
          self.threadSlideshow.cancel()
  
          #Reset GPIO pins
          GPIO.cleanup()
  
          #Close GUI
          self.window.destroy()
  
      def setClientState(self, clientState):
          self.state = clientState
  
      def periodicCall(self, intervall):
  
          #Process incoming queue messages
          self.gui.processIncoming()
  
          #Restart periodic call every x ms
          self.window.after(intervall, self.periodicCall, intervall)
  
      def setupGPIO(self):
  
          #Setup GPIO pin layout
          GPIO.setmode(GPIO.BCM)
          GPIO.setwarnings(False)
          bounceTime = self.config.getint('RPi GPIO','Bouncetime')
  
          #Capturing: Setup GPIO pins and wait for capture events
          GPIO.setup(self.config.getint('RPi GPIO','ButtonCaptureGPIO'), GPIO.IN, pull_up_down=GPIO.PUD_UP)
          GPIO.add_event_detect(self.config.getint('RPi GPIO','ButtonCaptureGPIO'), GPIO.FALLING, callback=self.readButtonCapture, bouncetime=bounceTime)
  
          if self.config.getboolean('RPi GPIO','EnableRadioCapture'):
              GPIO.setup(self.config.getint('RPi GPIO','ButtonCaptureRadioGPIO'), GPIO.IN, pull_up_down=GPIO.PUD_UP)
              GPIO.add_event_detect(self.config.getint('RPi GPIO','ButtonCaptureRadioGPIO'), GPIO.FALLING, callback=self.readButtonCapture, bouncetime=bounceTime)
  
          #LED: Setup GPIO pin and initialize LED status to high signal
          if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
              GPIO.setup(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.OUT)
              GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.HIGH)
  
      def setupThreads(self):
  
          if self.config.getboolean('Slideshow','EnableSlideshow'):
              self.threadSlideshow = RepeatingTimer(interval=self.config.getint('Slideshow','DisplayDuration'), target=self.showRandomImage)
          else:
              self.threadSlideshow = RepeatingTimer(interval=self.config.getint('Slideshow','DisplayDuration'), target=self.dummy)
  
      def readButtonCapture(self, channel):
          threading.Thread(target=self.captureImageButtonThread, args=[channel]).start()
  
      def readKeypressCapture(self, key):
          threading.Thread(target=self.captureImageThread).start()
  
      def captureImageButtonThread(self, channel):
  
          try:
  
              #Check the button to be pressed curtain amount of time 
              timeThreshold = time.time() + (self.config.getint('RPi GPIO','Threshold')/1000)
              while timeThreshold &gt; time.time():
                  if GPIO.input(channel): return
  
              self.captureImageThread()
  
          except:
  
              #Show Error Message
              self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamFailed'), fontSize=self.config.getint('Font','SizeMessage')))
  
      def captureImageThread(self):
  
          if self.state == ClientState.BUSY: return
  
          try:
  
              #Stop background threads (LED and Slideshow)
              self.threadSlideshow.cancel()
              if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
                  GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.LOW)
  
              #Capture image
              self.captureImage()
  
              #Restart background threads (LED and Slideshow)
              self.threadSlideshow.startAfter(self.config.getint('Slideshow','StartTime'))
              if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
                  GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.HIGH)
  
          except:
  
              #Show Error Message
              self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamFailed'), fontSize=self.config.getint('Font','SizeMessage')))
  
      def captureImage(self):
  
          #Check whether application is ready to capture new image
          if self.state == ClientState.BUSY: return
  
          #Switch applications' state
          self.setClientState(ClientState.BUSY)
  
          #Read config values for capture mode
          fontSizeMessage = self.config.getint('Font','SizeMessage')
          fontSizeCountdown = self.config.getint('Font','SizeCountdown')
          isLivePreview = self.config.getboolean('GPhotoParameter','LivePreview')
  
          #Establish connection to camera
          if self.photoWrapper.connect():
  
              #Start live preview
              if isLivePreview:
                  isoKey = self.config.get('GPhotoParameter','IsoSpeed')
                  isoValuePrev = self.config.get('GPhotoParameter','IsoSpeedValuePreview')
                  apertureKey = self.config.get('GPhotoParameter','Aperture')
                  apertureValuePrev = self.config.get('GPhotoParameter','ApertureValuePreview')
                  apertureValueCapt = isoValueCapt = None
  
                  if self.config.getboolean('GPhotoParameter','FixCaptureSettings'):
                      apertureValueCapt = self.config.get('GPhotoParameter','ApertureValueCapture')
                      isoValueCapt = self.config.get('GPhotoParameter','IsoSpeedValueCapture')
  
                  if not self.photoWrapper.startPreview(isoKey, isoValuePrev, isoValueCapt, apertureKey, apertureValuePrev, apertureValueCapt):
  
                      #Close connection to camera
                      self.photoWrapper.disconnect()
  
                      #Show Error Message
                      self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
  
                      #Switch applications' state
                      self.setClientState(ClientState.READY_CAPTURE)
  
                      return
  
              #Start countdown for posing
              countdown = self.config.getfloat('Photos','Countdown')
              stepSize = self.config.getfloat('RootWindow','UpdateIntervall')/1000
  
              while countdown &gt; 0:
                  number = int(countdown)
                  textMsg = number if number&gt;0 else self.config.get('Text','CamPhotoShot')
                  textSize = fontSizeCountdown if number&gt;0 else fontSizeMessage
                  if isLivePreview:
                          prevImage = self.photoWrapper.capturePreview()
                          if prevImage != False: self.queue.put(Message(mType=MessageType.IMAGE_TEXT, text=textMsg, fontSize=textSize, image=ImageOps.mirror(prevImage)))
                          else: self.queue.put(Message(mType=MessageType.TEXT, text=textMsg, fontSize=textSize))
                  else: self.queue.put(Message(mType=MessageType.TEXT, text=textMsg, fontSize=textSize))
                  countdown -= stepSize
                  time.sleep(stepSize)
  
              #Stop live preview
              if not self.photoWrapper.stopPreview():
  
                  #Close connection to camera
                  self.photoWrapper.disconnect()
  
                  #Show Error Message
                  self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
  
                  #Switch applications' state
                  self.setClientState(ClientState.READY_CAPTURE)
  
                  return
  
              #Capture image
              camFilePath = self.photoWrapper.captureImage()
              if camFilePath:
  
                  #Download image
                  self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoDownload'), fontSize=fontSizeMessage))
                  try:
                      unixTimeStamp = self.photoWrapper.getConfigValue(self.config.get('GPhotoParameter','CameraDateTime'))
                      localFilePath = os.path.join(self.config.get('Photos','PathLocal'), self.fileName(unixTimeStamp))
                  except:
                      localFilePath = None
  
                  if (localFilePath and self.photoWrapper.downloadImage(camFilePath,localFilePath)):
  
                      try:
  
                          #Process image
                          self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoProcessing'), fontSize=fontSizeMessage))
                          origImage = Image.open(localFilePath)
  
                          #Resize image in two steps (best trade-off between speed and quality)
                          scaledImage = self.photoUtils.resizeAspectRatio(origImage, self.gui.getMaxImageHeight())
                          previewImage = scaledImage.copy()
  
                          #Save (resized) image to webservers' photo directory
                          if self.config.getboolean('Photos','CopyToWebServer'):
                              self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoSave'), fontSize=fontSizeMessage))
                              targetOrig = os.path.join(self.config.get('Photos','PathWebServerOrig'), os.path.basename(localFilePath))
                              targetThumb = os.path.join(self.config.get('Photos','PathWebServerThumb'), os.path.basename(localFilePath))
                              if self.config.getboolean('Photos','SaveScaledImage'):
                                  scaledImage = self.photoUtils.resizeFixed(origImage, self.config.getint('Photos','OrigHeight'), self.config.getint('Photos','OrigWidth'))
                                  scaledImage.save(targetOrig, quality=self.config.getint('Photos','OrigQuality'))
                              else: copyfile(localFilePath, targetOrig)
                              scaledImage = self.photoUtils.resizeFixed(scaledImage, self.config.getint('Photos','ThumbHeight'), self.config.getint('Photos','ThumbWidth'))
                              scaledImage.save(targetThumb)
  
                          #Show image
                          self.queue.put(Message(mType=MessageType.IMAGE, image=previewImage))
  
                      except:
  
                          self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoProcessFailed'), fontSize=fontSizeMessage))
  
                  else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoDownloadFailed'), fontSize=fontSizeMessage))
              else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
          else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotDetected'), fontSize=fontSizeMessage))
  
          #Close connection to camera
          self.photoWrapper.disconnect()
  
          #Switch applications' state
          self.setClientState(ClientState.READY_CAPTURE)
  
      def showRandomImage(self):
  
          #Check client state
          if self.state == ClientState.BUSY: return False
          self.setClientState(ClientState.BUSY)
  
          #Show loading message
          self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','PleaseWait'), fontSize=self.config.get('Font','SizeMessage')))
  
          #Get random image from slideshow directory
          if (self.config.getboolean('Slideshow','EnableAdvertisements')):
              if(random.randint(0,3) &gt; 0):
                  filePath = self.config.get('Slideshow','SlideShowFolder')
              else:
                  filePath = self.config.get('Slideshow','AdvertisementFolder')
          else:
              filePath = self.config.get('Slideshow','SlideShowFolder')
  
          if os.listdir(filePath):
  
              try:
  
                  fileName = random.choice([x for x in os.listdir(filePath) if os.path.isfile(os.path.join(filePath, x))])
                  filePathAndName = os.path.join(filePath, fileName)
  
                  #Resize image in two steps (best trade-off between speed and quality)
                  scaledImage = self.photoUtils.resizeAspectRatio(Image.open(filePathAndName), self.gui.getMaxImageHeight())
  
                  #Show Image
                  self.queue.put(Message(mType=MessageType.IMAGE, image=scaledImage))
  
              except:
  
                  self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('RootWindow','AppTitle'), fontSize=self.config.get('Font','SizeTitle')))
  
          #Change Client State
          self.setClientState(ClientState.READY_CAPTURE)
  
          return True
  
      def reset(self):
          if not self.state == ClientState.READY_CAPTURE: return
          self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('RootWindow','AppTitle'), fontSize=self.config.get('Font','SizeTitle')))
  
      def fileName(self, unixTimeStamp):
          prefix = self.config.get('Photos','Prefix')
          return prefix + time.strftime('%Y%m%d_%H%M%S', time.localtime(unixTimeStamp)) + '.jpg'
  
      def dummy():
          return False
  
  class RepeatingTimer(object):
  
      def __init__(self, interval, target, onCancel=None, *args, **kwargs):
          self.interval = interval
          self.target = target
          self.onCancel = onCancel
          self.args = args
          self.kwargs = kwargs
          self.timer = None
          self.isAlive = False
  
      def callback(self):
          if self.target(*self.args, **self.kwargs):
              self.isAlive = False
              self.startAfter(self.interval)
          else: self.isAlive = False
  
      def cancel(self):
          self.isAlive = False
          if not self.timer == None: self.timer.cancel()
          if not self.onCancel == None: self.onCancel(*self.args, **self.kwargs)
  
      def start(self):
          self.startAfter(self.interval)
  
      def startAfter(self, interval):
          if self.isAlive: return
          self.timer = threading.Timer(interval, self.callback)
          self.isAlive = True
          self.timer.start()
  
  class Logger(file):
  
      def __init__(self, name, mode = 'w'):
          self = file.__init__(self, name, mode)
  
      def writeLine(self, string):
          self.writelines(string + '\n')
          return None
  
  #Main Function
  
  def main():
  
      while(True):
  
          try: ThreadedClient(Tk()).start()
  
          except:
  
              #Get information from unhandled exception
              exc_type, exc_value, exc_traceback = sys.exc_info()
              exceptionInfo = repr(traceback.format_exception(exc_type, exc_value,exc_traceback))
  
              #Output unhandled exception to error.log file
              f = Logger(os.path.dirname(sys.argv[0])+'/errors.log','a')
              f.writeLine('')
              f.writeLine('***************************************************')
              f.writeLine('**                   EXCEPTION                   **')
              f.writeLine('***************************************************')
              for line in traceback.format_exception(exc_type, exc_value,exc_traceback): f.writeLine(line)
              f.close()
  
          else: break
  
  if __name__ == '__main__':   
      main()
  </code></pre>
  
  <p>Would be great to get some hints how to fix the unhandled exception.</p>