Пример #1
0
class Player(object):

    # common bits of __init__(...)
    def __init__(
        self,
        show_id,
        showlist,
        root,
        canvas,
        show_params,
        track_params,
        pp_dir,
        pp_home,
        pp_profile,
        end_callback,
        command_callback,
    ):

        # create debugging log object
        self.mon = Monitor()

        self.mon.trace(self, "")

        # instantiate arguments
        self.show_id = show_id
        self.showlist = showlist
        self.root = root
        self.canvas = canvas["canvas-obj"]
        self.show_canvas_x1 = canvas["show-canvas-x1"]
        self.show_canvas_y1 = canvas["show-canvas-y1"]
        self.show_canvas_x2 = canvas["show-canvas-x2"]
        self.show_canvas_y2 = canvas["show-canvas-y2"]
        self.show_canvas_width = canvas["show-canvas-width"]
        self.show_canvas_height = canvas["show-canvas-height"]
        self.show_canvas_centre_x = canvas["show-canvas-centre-x"]
        self.show_canvas_centre_y = canvas["show-canvas-centre-y"]
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.end_callback = end_callback
        self.command_callback = command_callback

        # get background image from profile.
        self.background_file = ""
        if self.track_params["background-image"] != "":
            self.background_file = self.track_params["background-image"]

        # get background colour from profile.
        if self.track_params["background-colour"] != "":
            self.background_colour = self.track_params["background-colour"]
        else:
            self.background_colour = self.show_params["background-colour"]

        # get animation instructions from profile
        self.animate_begin_text = self.track_params["animate-begin"]
        self.animate_end_text = self.track_params["animate-end"]

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim = PluginManager(
            self.show_id,
            self.root,
            self.canvas,
            self.show_params,
            self.track_params,
            self.pp_dir,
            self.pp_home,
            self.pp_profile,
        )

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj = None
        self.show_text_obj = None
        self.track_text_obj = None
        self.hint_obj = None
        self.background = None
        self.freeze_at_end_required = "no"  # overriden by videoplayer
        self.tick_timer = None
        self.terminate_signal = False
        self.play_state = ""

    def pre_load(self):
        # Control other shows at beginning
        self.show_control(self.track_params["show-control-begin"])
        pass

    # common bits of show(....)
    def pre_show(self):
        self.mon.trace(self, "")

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()

        # and whatecer the plugin has created
        self.pim.show_plugin()

        # ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)

        # create animation events
        reason, message = self.animate.animate(self.animate_begin_text, id(self))
        if reason == "error":
            self.mon.err(self, message)
            self.play_state = "show-failed"
            if self.finished_callback is not None:
                self.finished_callback("error", message)
        else:
            # return to start playing the track.
            self.mon.log(self, ">show track received from show Id: " + str(self.show_id))
            return

    # to keep landscape happy
    def ready_callback(self, enable_show_background):
        self.mon.fatal(self, "ready callback not overridden")
        self.end("error", "ready callback not overridden")

    def finished_callback(self, reason, message):
        self.mon.fatal(self, "finished callback not overridden")
        self.end("error", "finished callback not overridden")

    def closed_callback(self, reason, message):
        self.mon.fatal(self, "closed callback not overridden")
        self.end("error", "closed callback not overridden")

    # Control shows so pass the show control commands back to PiPresents via the command callback
    def show_control(self, show_control_text):
        lines = show_control_text.split("\n")
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line, self.show_params["show-ref"])

    # *****************
    # hide content and end animation, show control etc.
    # called by ready calback and end
    # *****************

    def hide(self):
        self.mon.trace(self, "")
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        self.hide_x_content()

        # stop the plugin
        if self.track_params["plugin"] != "":
            self.pim.stop_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params["show-control-end"])

        # clear events list for this track
        if self.track_params["animate-clear"] == "yes":
            self.animate.clear_events_list(id(self))

        # create animation events for ending
        reason, message = self.animate.animate(self.animate_end_text, id(self))
        if reason == "error":
            self.play_state = "show-failed"
            if self.finished_callback is not None:
                self.finished_callback("error", message)
        else:
            return

    def terminate(self):
        self.mon.trace(self, "")
        self.terminate_signal = True
        if self.play_state == "showing":
            # call the derived class's stop method
            self.stop()
        else:
            self.end("killed", "terminate with no track or show open")

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self, "stop not overidden by derived class")
        self.play_state = "show-failed"
        if self.finished_callback is not None:
            self.finished_callback("error", "stop not overidden by derived class")

    def get_play_state(self):
        return self.play_state

    # *****************
    # ending the player
    # *****************

    def end(self, reason, message):
        self.mon.trace(self, "")
        # stop the plugin

        if self.terminate_signal is True:
            reason = "killed"
            self.terminate_signal = False
            self.hide()

        self.end_callback(reason, message)
        self = None

    # *****************
    # displaying common things
    # *****************

    def load_plugin(self):
        # load the plugin if required
        if self.track_params["plugin"] != "":
            reason, message, self.track = self.pim.load_plugin(self.track, self.track_params["plugin"])
            return reason, message

    def draw_plugin(self):
        # load the plugin if required
        if self.track_params["plugin"] != "":
            self.pim.draw_plugin()
            return

    def load_x_content(self, enable_menu):
        self.mon.trace(self, "")
        self.background_obj = None
        self.background = None
        self.track_text_obj = None
        self.show_text_obj = None
        self.hint_obj = None
        self.track_obj = None

        # background image
        if self.background_file != "":
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return "error", "Track background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize((window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1, self.show_canvas_y1, image=self.background, anchor=NW
                )
                # print '\nloaded background_obj: ',self.background_obj

        # load the track content.  Dummy function below is overridden in players
        status, message = self.load_track_content()
        if status == "error":
            return "error", message

        # load show text if enabled
        if self.show_params["show-text"] != "" and self.track_params["display-show-text"] == "yes":
            self.show_text_obj = self.canvas.create_text(
                int(self.show_params["show-text-x"]) + self.show_canvas_x1,
                int(self.show_params["show-text-y"]) + self.show_canvas_y1,
                anchor=NW,
                text=self.show_params["show-text"],
                fill=self.show_params["show-text-colour"],
                font=self.show_params["show-text-font"],
            )

        # load track text if enabled
        if self.track_params["track-text"] != "":
            self.track_text_obj = self.canvas.create_text(
                int(self.track_params["track-text-x"]) + self.show_canvas_x1,
                int(self.track_params["track-text-y"]) + self.show_canvas_y1,
                anchor=NW,
                text=self.track_params["track-text"],
                fill=self.track_params["track-text-colour"],
                font=self.track_params["track-text-font"],
            )

        # load instructions if enabled
        if enable_menu is True:
            self.hint_obj = self.canvas.create_text(
                int(self.show_params["hint-x"]) + self.show_canvas_x1,
                int(self.show_params["hint-y"]) + self.show_canvas_y1,
                text=self.show_params["hint-text"],
                fill=self.show_params["hint-colour"],
                font=self.show_params["hint-font"],
                anchor=NW,
            )

        self.display_show_canvas_rectangle()

        self.pim.draw_plugin()

        self.canvas.tag_raise("pp-click-area")
        self.canvas.itemconfig(self.background_obj, state="hidden")
        self.canvas.itemconfig(self.show_text_obj, state="hidden")
        self.canvas.itemconfig(self.track_text_obj, state="hidden")
        self.canvas.itemconfig(self.hint_obj, state="hidden")
        self.canvas.update_idletasks()
        return "normal", "x-content loaded"

    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass

    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer

    def load_track_content(self):
        return "normal", "player has no track content to load"

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self, "")
        # background colour
        if self.background_colour != "":
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj, state="normal")
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj, state="normal")
        self.canvas.itemconfig(self.track_text_obj, state="normal")
        self.canvas.itemconfig(self.hint_obj, state="normal")
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params["display-show-background"] == "yes":
            self.enable_show_background = True
        else:
            self.enable_show_background = False
        # print 'ENABLE SB',self.enable_show_background

    def hide_x_content(self):
        self.mon.trace(self, "")
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj, state="hidden")
        self.canvas.itemconfig(self.show_text_obj, state="hidden")
        self.canvas.itemconfig(self.track_text_obj, state="hidden")
        self.canvas.itemconfig(self.hint_obj, state="hidden")
        # self.canvas.update_idletasks( )

        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background = None
        # self.canvas.update_idletasks( )

    # ****************
    # utilities
    # *****************

    def get_links(self):
        return self.track_params["links"]

    # produce an absolute path from the relative one in track paramters
    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        # self.mon.log(self,"Background image is "+ track_file)
        return track_file

    # get a text string from resources.cfg
    def resource(self, section, item):
        value = self.rr.get(section, item)
        return value  # False if not found
Пример #2
0
class Show(object):

    # ******************************
    # init a show
    # ******************************

    def base__init__(self, show_id, show_params, root, canvas, showlist,
                     pp_dir, pp_home, pp_profile, command_callback):

        # instantiate arguments
        self.show_id = show_id
        self.show_params = show_params
        self.root = root
        self.show_canvas = canvas
        self.canvas = canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height = canvas['show-canvas-height']
        self.show_canvas_centre_x = canvas['show-canvas-centre-x']
        self.show_canvas_centre_y = canvas['show-canvas-centre-y']
        self.showlist = showlist
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.command_callback = command_callback

        # init things that will then be reinitialised by derived classes
        self.medialist = None

        # set up logging
        self.mon = Monitor()
        self.mon.set_log_level(16)

        # create and instance of TimeOfDay scheduler so we can add events
        self.tod = TimeOfDay()

        # create an  instance of showmanager so we can init child/subshows
        self.show_manager = ShowManager(self.show_id, self.showlist,
                                        self.show_params, self.root,
                                        self.show_canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)

        # init variables
        self.current_player = None
        self.previous_player = None
        self.shower = None
        self.previous_shower = None
        self.user_stop_signal = False
        self.exit_signal = False
        self.terminate_signal = False
        self.show_timeout_signal = False
        self.egg_timer = None
        self.admin_message = None
        self.ending_reason = ''
        self.background_obj = None
        self.background_file = ''
        self.level = 0
        self.subshow_kickback_signal = False
        self.kickback_for_next_track = False

        # get background image from profile.
        # print 'background', self.show_params['background-image']
        if self.show_params['background-image'] != '':
            self.background_file = self.show_params['background-image']

    def base_play(self, end_callback, show_ready_callback,
                  parent_kickback_signal, level, controls_list):
        """ starts the common parts of the show
              end_callback - function to be called when the show exits- callback gets last player of subshow
              show_ready_callback - callback at start to get previous_player
              top is True when the show is top level (run from [start] or by show command from another show)
              direction_command - 'forward' or 'backward' direction to play a subshow
        """
        # instantiate the arguments
        self.end_callback = end_callback
        self.show_ready_callback = show_ready_callback
        self.parent_kickback_signal = parent_kickback_signal
        self.level = level
        # not needed as controls list is not passed down to subshows.
        # self.controls_list=controls_list
        self.mon.trace(
            self,
            self.show_params['show-ref'] + ' at level ' + str(self.level))
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Starting show")

        # check  data files are available.
        if self.show_params['medialist'] == '':
            self.mon.err(self,
                         "Blank Medialist in: " + self.show_params['title'])
            self.end('error',
                     "Blank Medialist in: " + self.show_params['title'])
        self.medialst_file = self.pp_profile + "/" + self.show_params[
            'medialist']
        if not os.path.exists(self.medialst_file):
            self.mon.err(self,
                         "Medialist file not found: " + self.medialst_file)
            self.end('error',
                     "Medialist file not found: " + self.medialst_file)

        # read the medialist for the show
        if self.medialist.open_list(self.medialst_file,
                                    self.showlist.sissue()) is False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self.end('error', "Version of medialist different to Pi Presents")

        if self.show_ready_callback is not None:
            # get the previous player from calling show its stored in current because its going to be shuffled before use
            self.previous_shower, self.current_player = self.show_ready_callback(
            )
            self.mon.trace(
                self, ' - previous shower and player is ' +
                self.mon.pretty_inst(self.previous_shower) + ' ' +
                self.mon.pretty_inst(self.current_player))

        #load the show background
        reason, message = Show.base_load_show_background(self)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

    # dummy, must be overidden by derived class
    def subshow_ready_callback(self):
        self.mon.err(self, "subshow_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_subshow_ready_callback(self):
        # callback from begining of a subshow, provide previous player to called show
        # used by show_ready_callback of called show
        # in the case of a menushow last track is always the menu
        self.mon.trace(
            self, ' -  sends ' + self.mon.pretty_inst(self.previous_player))
        return self, self.previous_player

    def base_shuffle(self):
        self.previous_player = self.current_player
        self.current_player = None
        self.mon.trace(
            self, ' - LOOP STARTS WITH current is: ' +
            self.mon.pretty_inst(self.current_player))
        self.mon.trace(
            self, '       -  previous is: ' +
            self.mon.pretty_inst(self.previous_player))

    def base_load_track_or_show(self, selected_track, loaded_callback,
                                end_shower_callback, enable_menu):
        track_type = selected_track['type']
        if track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index < 0:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self.end('error',
                         'show not in showlist: ' + selected_track['sub-show'])
            else:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
                self.shower = self.show_manager.init_subshow(
                    self.show_id, selected_show, self.show_canvas)
                self.mon.trace(
                    self, ' - show is: ' + self.mon.pretty_inst(self.shower) +
                    ' ' + selected_show['show-ref'])
                if self.shower is None:
                    self.mon.err(self,
                                 "Unknown Show Type: " + selected_show['type'])
                    self.terminate_signal = True
                    self.what_next_after_showing()
                else:
                    # empty controls list as not used, pleases landscape
                    # print 'send direction to subshow from show',self.kickback_for_next_track
                    # Show.base_withdraw_show_background(self)
                    self.shower.play(end_shower_callback,
                                     self.subshow_ready_callback,
                                     self.kickback_for_next_track,
                                     self.level + 1, [])
        else:
            # dispatch track by type
            self.mon.log(
                self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
                ": Track type is: " + track_type)

            self.current_player = self.base_init_selected_player(
                selected_track)
            #menu has no track file
            if selected_track['type'] == 'menu':
                track_file = ''

            # messageplayer passes the text not a file name
            elif selected_track['type'] == 'message':
                track_file = selected_track['text']
            else:
                track_file = self.base_complete_path(
                    selected_track['location'])

            self.mon.trace(self, ' - track is: ' + track_file)
            self.mon.trace(
                self, ' - current_player is: ' +
                self.mon.pretty_inst(self.current_player))
            self.current_player.load(track_file,
                                     loaded_callback,
                                     enable_menu=enable_menu)

    # DUMMY, must be overidden by derived class
    def what_next_after_showing(self):
        self.mon.err(self, "what_next_after showing not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_init_selected_player(self, selected_track):
        # dispatch track by type
        track_type = selected_track['type']
        self.mon.log(self, "Track type is: " + track_type)

        if track_type == "image":
            return ImagePlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "video":
            return VideoPlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "audio":
            return AudioPlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "web" and self.show_params['type'] not in (
                'artmediashow', 'artliveshow'):
            return BrowserPlayer(self.show_id, self.showlist, self.root,
                                 self.show_canvas, self.show_params,
                                 selected_track, self.pp_dir, self.pp_home,
                                 self.pp_profile, self.end,
                                 self.command_callback)

        elif track_type == "message":
            return MessagePlayer(self.show_id, self.showlist, self.root,
                                 self.show_canvas, self.show_params,
                                 selected_track, self.pp_dir, self.pp_home,
                                 self.pp_profile, self.end,
                                 self.command_callback)

        elif track_type == "menu":
            return MenuPlayer(self.show_id, self.showlist, self.root,
                              self.show_canvas, self.show_params,
                              selected_track, self.pp_dir, self.pp_home,
                              self.pp_profile, self.end, self.command_callback)

        else:
            return None

    # DUMMY, must be overidden by derived class
    def track_ready_callback(self, track_background):
        self.mon.err(self, "track_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    # called just before a track is shown to remove the  previous track from the screen
    # and if necessary close it
    def base_track_ready_callback(self, enable_show_background):
        self.mon.trace(self, '')
        # show the show background done for every track but quick operation
        if enable_show_background is True:
            self.base_show_show_background()
        else:
            self.base_withdraw_show_background()
        # !!!!!!!!! withdraw the background from the parent show
        if self.previous_shower != None:
            self.previous_shower.base_withdraw_show_background()
        # close the player from the previous track
        if self.previous_player is not None:
            self.mon.trace(
                self, ' - hiding previous: ' +
                self.mon.pretty_inst(self.previous_player))
            self.previous_player.hide()
            # print 'Not None  - previous state is',self.previous_player.get_play_state()
            if self.previous_player.get_play_state() == 'showing':
                # print 'showing so closing previous'
                # showing or frozen
                self.mon.trace(
                    self, ' - closing previous: ' +
                    self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_closed_callback_previous)
            else:
                self.mon.trace(self, ' - previous is none\n')
                self.previous_player = None
        self.canvas.update_idletasks()

    def _base_closed_callback_previous(self, status, message):
        self.mon.trace(
            self, ' -  previous is None  - was: ' +
            self.mon.pretty_inst(self.previous_player))
        self.previous_player = None

    # used by end_shower to get the last track of the subshow
    def base_end_shower(self):
        self.mon.trace(self, ' -  returned back to level: ' + str(self.level))
        # get the previous subshow and last track it played
        self.previous_shower, self.current_player = self.shower.base_subshow_ended_callback(
        )
        if self.previous_shower != None:
            self.subshow_kickback_signal = self.shower.subshow_kickback_signal
            # print 'get subshow kickback from subshow',self.subshow_kickback_signal
            self.previous_shower.base_withdraw_show_background()
            self.base_show_show_background()
        self.previous_player = None
        self.mon.trace(
            self, '- get previous_player from subshow: ' +
            self.mon.pretty_inst(self.current_player))
        self.shower = None

    # close or unload the current player when ending the show
    def base_close_or_unload(self):
        self.mon.trace(self, self.mon.pretty_inst(self.current_player))
        # need to test for None because player may be made None by subshow lower down the stack for terminate
        if self.current_player is not None:
            self.mon.trace(self, self.current_player.get_play_state())
            if self.current_player.get_play_state() in ('loaded', 'showing',
                                                        'show-failed'):
                if self.current_player.get_play_state() == 'loaded':
                    self.mon.trace(
                        self,
                        ' - unloading current from: ' + self.ending_reason)
                    self.current_player.unload()
                else:
                    self.mon.trace(
                        self, ' - closing current from: ' + self.ending_reason)
                    self.current_player.close(None)
            self._wait_for_end()
        else:
            # current_player is None because closed further down show stack
            self.mon.trace(
                self, ' - show ended with current_player=None because: ' +
                self.ending_reason)

            # if exiting pipresents then need to close previous show else get memotry leak
            # if not exiting pipresents the keep previous so it can be closed when showing the next track
            # print 'CURRENT PLAYER IS NONE' ,self.ending_reason
            if self.ending_reason == 'killed':
                self.base_close_previous()

            elif self.ending_reason == 'error':
                self.base_close_previous()

            elif self.ending_reason == 'exit':
                self.end('normal', "show quit by exit command")

            elif self.ending_reason == 'user-stop':
                self.end('normal', "show quit by stop operation")

            else:
                self.mon.fatal(
                    self, "Unhandled ending_reason: " + self.ending_reason)
                self.end('error',
                         "Unhandled ending_reason: " + self.ending_reason)

    def _base_closed_callback_current(self, status, message):
        self.mon.trace(
            self, ' current is None  - was: ' +
            self.mon.pretty_inst(self.current_player))

    # wait for unloading or closing to complete then end
    def _wait_for_end(self):
        self.mon.trace(self, self.mon.pretty_inst(self.current_player))
        if self.current_player is not None:
            self.mon.trace(
                self,
                ' - play state is ' + self.current_player.get_play_state())
            if self.current_player.play_state not in ('unloaded', 'closed',
                                                      'load-failed'):  ####
                self.canvas.after(50, self._wait_for_end)
            else:
                self.mon.trace(
                    self, ' - current closed ' +
                    self.mon.pretty_inst(self.current_player) + ' ' +
                    self.ending_reason)

                #why is some of thsi different to close and unload????????????? perhaps because current_player isn't none, just closed
                if self.ending_reason == 'killed':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'error':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'exit':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'change-medialist':
                    self.current_player.hide()
                    self.current_player = None
                    # self.base_close_previous()
                    # go to start of list via wait for trigger.
                    self.wait_for_trigger()

                elif self.ending_reason == 'show-timeout':
                    self.current_player.hide()
                    self.current_player = None
                    self.end('normal', "show timeout")

                elif self.ending_reason == 'user-stop':
                    if self.level != 0:
                        self.end('normal', "show quit by stop operation")
                    else:
                        self.current_player.hide()
                        self.current_player = None
                        self.base_close_previous()

                else:
                    self.mon.fatal(
                        self, "Unhandled ending_reason: " + self.ending_reason)
                    self.end('error',
                             "Unhandled ending_reason: " + self.ending_reason)
        else:
            self.mon.trace(
                self, ' - current is None ' +
                self.mon.pretty_inst(self.current_player) + ' ' +
                self.ending_reason)

# ***************************
# end of show
# ***************************

# dummy, normally overidden by derived class

    def end(self, reason, message):
        self.mon.err(self, "end not overidden")
        self.base_end('error', message)

    def base_end(self, reason, message):
        self.base_withdraw_show_background()
        self.base_delete_show_background()
        self.mon.trace(
            self, ' at level ' + str(self.level) + '\n - Current is ' +
            self.mon.pretty_inst(self.current_player) + '\n - Previous is ' +
            self.mon.pretty_inst(self.previous_player) + '\n with reason' +
            reason + '\n\n')
        self.mon.log(
            self, self.show_params['show-ref'] + ' Show Id: ' +
            str(self.show_id) + ": Ending Show")
        self.end_callback(self.show_id, reason, message)
        self = None

    def base_subshow_ended_callback(self):
        # called by end_shower of a parent show  to get the last track of the subshow and the subshow
        self.mon.trace(
            self, ' -  returns ' + self.mon.pretty_inst(self.current_player))
        return self, self.current_player

# ********************************
# Respond to external events
# ********************************

    def base_close_previous(self):
        self.mon.trace(self, '')
        # close the player from the previous track
        if self.previous_player is not None:
            self.mon.trace(
                self, ' - previous not None ' +
                self.mon.pretty_inst(self.previous_player))
            if self.previous_player.get_play_state() == 'showing':
                # showing or frozen
                self.mon.trace(
                    self, ' - closing previous ' +
                    self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_close_previous_callback)
            else:
                self.mon.trace(self, 'previous is not showing')
                self.previous_player.hide()
                self.previous_player = None
                self.end(self.ending_reason, '')
        else:
            self.mon.trace(self, ' - previous is None')
            self.end(self.ending_reason, '')

    def _base_close_previous_callback(self, status, message):
        self.mon.trace(
            self, ' -  previous is None  - was ' +
            self.mon.pretty_inst(self.previous_player))
        self.previous_player.hide()
        self.previous_player = None
        self.end(self.ending_reason, '')

    # exit received from external source
    def base_exit(self):
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Exit received")
        self.mon.trace(self, '')
        # set signal to exit the show when all  sub-shows and players have ended
        self.exit_signal = True
        # then stop subshow or tracks.
        if self.shower is not None:
            self.shower.exit()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal', 'exit by ShowManager')

    # show timeout callback received
    def base_show_timeout_stop(self):
        self.mon.trace(self, '')
        # set signal to exit the show when all  sub-shows and players have ended
        self.show_timeout_signal = True
        # then stop and shows or tracks.
        if self.shower is not None:
            self.shower.show_timeout_stop()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal', 'stopped by Show Timeout')

    # dummy, normally overidden by derived class
    def terminate(self):
        self.mon.err(self, "terminate not overidden")
        self.base_end('error', "terminate not overidden")

    # terminate Pi Presents
    def base_terminate(self):
        self.mon.trace(self, '')
        # set signal to stop the show when all  sub-shows and players have ended
        self.terminate_signal = True
        if self.shower is not None:
            self.shower.terminate()
        elif self.current_player is not None:
            self.ending_reason = 'killed'
            Show.base_close_or_unload(self)
        else:
            self.end('killed',
                     ' terminated with no shower or player to terminate')

# respond to input events

    def base_handle_input_event(self, symbol):
        self.mon.log(
            self, self.show_params['show-ref'] + ' Show Id: ' +
            str(self.show_id) + ": received input event: " + symbol)

        if self.shower is not None:
            self.shower.handle_input_event(symbol)
        else:
            self.handle_input_event_this_show(symbol)

    #dummy must be overridden in derived class
    def handle_input_event_this_show(self, symbol):
        self.mon.err(self, "input_pressed_this_show not overidden")
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_load_show_background(self):
        # load show background image
        if self.background_file != '':
            background_img_file = self.base_complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error', "Show background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize(
                        (window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                # print 'self.background ',self.background
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1,
                    self.show_canvas_y1,
                    image=self.background,
                    anchor=NW)
                self.canvas.itemconfig(self.background_obj, state='hidden')
                self.canvas.update_idletasks()
                # print '\nloaded background_obj: ',self.background_obj
                return 'normal', 'show background loaded'
        else:
            return 'normal', 'no backgound to load'

    def base_show_show_background(self):
        if self.background_obj is not None:
            # print 'show show background'
            self.canvas.itemconfig(self.background_obj, state='normal')
            # self.canvas.update_idletasks( )

    def base_withdraw_show_background(self):
        self.mon.trace(self, '')
        if self.background_obj is not None:
            # print 'withdraw background obj', self.background_obj
            self.canvas.itemconfig(self.background_obj, state='hidden')
            # self.canvas.update_idletasks( )

    def base_delete_show_background(self):
        if self.background_obj is not None:
            # print 'delete background obj'
            self.canvas.delete(self.background_obj)
            self.background = None
            # self.canvas.update_idletasks( )

# ******************************
# write statiscics
# *********************************

    def write_stats(self, command, show_params, next_track):
        # action, this ref, this name, type, ref, name, location
        if next_track['type'] == 'show':
            # get the show from the showlist
            index = self.showlist.index_of_show(next_track['sub-show'])
            if index < 0:
                self.mon.err(
                    self,
                    "Show not found in showlist: " + next_track['sub-show'])
                self.end('error',
                         'show not in showlist: ' + next_track['sub-show'])
            else:
                target = self.showlist.show(index)
                ref = target['show-ref']
                title = target['title']
                track_type = target['type']
        else:
            # its a track
            ref = next_track['track-ref']
            title = next_track['title']
            track_type = next_track['type']
        if next_track['type'] in ('show', 'message'):
            loc = ''
        else:
            loc = next_track['location']
        self.mon.stats(show_params['type'], show_params['show-ref'],
                       show_params['title'], command, track_type, ref, title,
                       loc)

# ******************************
# lookup controls
# *********************************

    def base_lookup_control(self, symbol, controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        # not found so must be a trigger
        return ''

# ******************************
# Eggtimer
# *********************************

    def display_eggtimer(self):
        text = self.show_params['eggtimer-text']
        if text != '':
            self.egg_timer = self.canvas.create_text(
                int(self.show_params['eggtimer-x']) + self.show_canvas_x1,
                int(self.show_params['eggtimer-y']) + self.show_canvas_y1,
                text=text,
                fill=self.show_params['eggtimer-colour'],
                font=self.show_params['eggtimer-font'],
                anchor='nw')

            self.canvas.update_idletasks()

    def delete_eggtimer(self):
        if self.egg_timer is not None:
            self.canvas.delete(self.egg_timer)
            self.egg_timer = None
            self.canvas.update_idletasks()

# ******************************
# Display Admin Messages
# *********************************

    def display_admin_message(self, text):

        self.admin_message = self.canvas.create_text(
            int(self.show_params['admin-x']) + self.show_canvas_x1,
            int(self.show_params['admin-y']) + self.show_canvas_y1,
            text=text,
            fill=self.show_params['admin-colour'],
            font=self.show_params['admin-font'],
            anchor='nw')

        self.canvas.update_idletasks()

    def delete_admin_message(self):
        if self.admin_message is not None:
            self.canvas.delete(self.admin_message)
            self.canvas.update_idletasks()

# ******************************
# utilities
# ******************************

    def base_complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file != '' and track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Track to load is: " + track_file)
        return track_file

    def calculate_duration(self, line):
        fields = line.split(':')
        if len(fields) == 1:
            secs = fields[0]
            minutes = '0'
            hours = '0'
        if len(fields) == 2:
            secs = fields[1]
            minutes = fields[0]
            hours = '0'
        if len(fields) == 3:
            secs = fields[2]
            minutes = fields[1]
            hours = fields[0]
        if not secs.isdigit() or not minutes.isdigit() or not hours.isdigit():
            return 'error', 'bad time: ' + line, 0
        else:
            return 'normal', '', 3600 * long(hours) + 60 * long(
                minutes) + long(secs)
Пример #3
0
class Player(object):

    # common bits of __init__(...)
    def __init__(self, show_id, showlist, root, canvas, show_params,
                 track_params, pp_dir, pp_home, pp_profile, end_callback,
                 command_callback):

        # create debugging log object
        self.mon = Monitor()

        self.mon.trace(self, '')

        # instantiate arguments
        self.show_id = show_id
        self.showlist = showlist
        self.root = root
        self.canvas = canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height = canvas['show-canvas-height']
        self.show_canvas_centre_x = canvas['show-canvas-centre-x']
        self.show_canvas_centre_y = canvas['show-canvas-centre-y']
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.end_callback = end_callback
        self.command_callback = command_callback

        # get background image from profile.
        self.background_file = ''
        if self.track_params['background-image'] != '':
            self.background_file = self.track_params['background-image']

        # get background colour from profile.
        if self.track_params['background-colour'] != '':
            self.background_colour = self.track_params['background-colour']
        else:
            self.background_colour = self.show_params['background-colour']

        # get animation instructions from profile
        self.animate_begin_text = self.track_params['animate-begin']
        self.animate_end_text = self.track_params['animate-end']

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim = PluginManager(self.show_id, self.root, self.canvas,
                                 self.show_params, self.track_params,
                                 self.pp_dir, self.pp_home, self.pp_profile)

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj = None
        self.show_text_obj = None
        self.track_text_obj = None
        self.hint_obj = None
        self.background = None
        self.freeze_at_end_required = 'no'  # overriden by videoplayer
        self.tick_timer = None
        self.terminate_signal = False
        self.play_state = ''

    def pre_load(self):
        # Control other shows at beginning
        self.show_control(self.track_params['show-control-begin'])
        pass

    # common bits of show(....)
    def pre_show(self):
        self.mon.trace(self, '')

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()

        # and whatecer the plugin has created
        self.pim.show_plugin()

        #ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)

        # create animation events
        reason, message = self.animate.animate(self.animate_begin_text,
                                               id(self))
        if reason == 'error':
            self.mon.err(self, message)
            self.play_state = 'show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error', message)
        else:
            # return to start playing the track.
            self.mon.log(
                self,
                ">show track received from show Id: " + str(self.show_id))
            return

    # to keep landscape happy
    def ready_callback(self, enable_show_background):
        self.mon.fatal(self, 'ready callback not overridden')
        self.end('error', 'ready callback not overridden')

    def finished_callback(self, reason, message):
        self.mon.fatal(self, 'finished callback not overridden')
        self.end('error', 'finished callback not overridden')

    def closed_callback(self, reason, message):
        self.mon.fatal(self, 'closed callback not overridden')
        self.end('error', 'closed callback not overridden')

# Control shows so pass the show control commands back to PiPresents via the command callback

    def show_control(self, show_control_text):
        lines = show_control_text.split('\n')
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line,
                                  source='track',
                                  show=self.show_params['show-ref'])

# *****************
# hide content and end animation, show control etc.
# called by ready calback and end
# *****************

    def hide(self):
        self.mon.trace(self, '')
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        self.hide_x_content()

        # stop the plugin
        if self.track_params['plugin'] != '':
            self.pim.stop_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params['show-control-end'])

        # clear events list for this track
        if self.track_params['animate-clear'] == 'yes':
            self.animate.clear_events_list(id(self))

        # create animation events for ending
        reason, message = self.animate.animate(self.animate_end_text, id(self))
        if reason == 'error':
            self.play_state = 'show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error', message)
        else:
            return

    def terminate(self):
        self.mon.trace(self, '')
        self.terminate_signal = True
        if self.play_state == 'showing':
            # call the derived class's stop method
            self.stop()
        else:
            self.end('killed', 'terminate with no track or show open')

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self, 'stop not overidden by derived class')
        self.play_state = 'show-failed'
        if self.finished_callback is not None:
            self.finished_callback('error',
                                   'stop not overidden by derived class')

    def get_play_state(self):
        return self.play_state

# *****************
# ending the player
# *****************

    def end(self, reason, message):
        self.mon.trace(self, '')
        # stop the plugin

        if self.terminate_signal is True:
            reason = 'killed'
            self.terminate_signal = False
            self.hide()

        self.end_callback(reason, message)
        self = None

# *****************
# displaying common things
# *****************

    def load_plugin(self):
        # load the plugin if required
        if self.track_params['plugin'] != '':
            reason, message, self.track = self.pim.load_plugin(
                self.track, self.track_params['plugin'])
            return reason, message

    def draw_plugin(self):
        # load the plugin if required
        if self.track_params['plugin'] != '':
            self.pim.draw_plugin()
            return

    def load_x_content(self, enable_menu):
        self.mon.trace(self, '')
        self.background_obj = None
        self.background = None
        self.track_text_obj = None
        self.show_text_obj = None
        self.hint_obj = None
        self.track_obj = None

        # background image
        if self.background_file != '':
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error', "Track background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize(
                        (window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1,
                    self.show_canvas_y1,
                    image=self.background,
                    anchor=NW)
                # print '\nloaded background_obj: ',self.background_obj

        # load the track content.  Dummy function below is overridden in players
        status, message = self.load_track_content()
        if status == 'error':
            return 'error', message

        # load show text if enabled
        if self.show_params['show-text'] != '' and self.track_params[
                'display-show-text'] == 'yes':

            x, y, anchor, justify = calculate_text_position(
                self.show_params['show-text-x'],
                self.show_params['show-text-y'], self.show_canvas_x1,
                self.show_canvas_y1, self.show_canvas_centre_x,
                self.show_canvas_centre_y, self.show_canvas_x2,
                self.show_canvas_y2, self.show_params['show-text-justify'])

            self.show_text_obj = self.canvas.create_text(
                x,
                y,
                anchor=anchor,
                justify=justify,
                text=self.show_params['show-text'],
                fill=self.show_params['show-text-colour'],
                font=self.show_params['show-text-font'])

        # load track text if enabled
        if self.track_params['track-text'] != '':

            x, y, anchor, justify = calculate_text_position(
                self.track_params['track-text-x'],
                self.track_params['track-text-y'], self.show_canvas_x1,
                self.show_canvas_y1, self.show_canvas_centre_x,
                self.show_canvas_centre_y, self.show_canvas_x2,
                self.show_canvas_y2, self.track_params['track-text-justify'])

            self.track_text_obj = self.canvas.create_text(
                x,
                y,
                anchor=anchor,
                justify=justify,
                text=self.track_params['track-text'],
                fill=self.track_params['track-text-colour'],
                font=self.track_params['track-text-font'])

        # load instructions if enabled
        if enable_menu is True:

            x, y, anchor, justify = calculate_text_position(
                self.show_params['hint-x'], self.show_params['hint-y'],
                self.show_canvas_x1, self.show_canvas_y1,
                self.show_canvas_centre_x, self.show_canvas_centre_y,
                self.show_canvas_x2, self.show_canvas_y2,
                self.show_params['hint-justify'])

            self.hint_obj = self.canvas.create_text(
                x,
                y,
                justify=justify,
                text=self.show_params['hint-text'],
                fill=self.show_params['hint-colour'],
                font=self.show_params['hint-font'],
                anchor=anchor)

        self.display_show_canvas_rectangle()

        self.pim.draw_plugin()

        self.canvas.tag_raise('pp-click-area')
        self.canvas.itemconfig(self.background_obj, state='hidden')
        self.canvas.itemconfig(self.show_text_obj, state='hidden')
        self.canvas.itemconfig(self.track_text_obj, state='hidden')
        self.canvas.itemconfig(self.hint_obj, state='hidden')
        self.canvas.update_idletasks()
        return 'normal', 'x-content loaded'

    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass

    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer

    def load_track_content(self):
        return 'normal', 'player has no track content to load'

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self, '')
        # background colour
        if self.background_colour != '':
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj, state='normal')
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj, state='normal')
        self.canvas.itemconfig(self.track_text_obj, state='normal')
        self.canvas.itemconfig(self.hint_obj, state='normal')
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params[
                'display-show-background'] == 'yes':
            self.enable_show_background = True
        else:
            self.enable_show_background = False
        # print 'ENABLE SB',self.enable_show_background

    def hide_x_content(self):
        self.mon.trace(self, '')
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj, state='hidden')
        self.canvas.itemconfig(self.show_text_obj, state='hidden')
        self.canvas.itemconfig(self.track_text_obj, state='hidden')
        self.canvas.itemconfig(self.hint_obj, state='hidden')
        # self.canvas.update_idletasks( )

        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background = None
        # self.canvas.update_idletasks( )

# ****************
# utilities
# *****************

    def get_links(self):
        return self.track_params['links']

    # produce an absolute path from the relative one in track paramters
    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        elif track_file[0] == "@":
            track_file = self.pp_profile + track_file[1:]
        return track_file

    # get a text string from resources.cfg
    def resource(self, section, item):
        value = self.rr.get(section, item)
        return value  # False if not found
Пример #4
0
class PiPresents(object):
    def pipresents_version(self):
        vitems = self.pipresents_issue.split('.')
        if len(vitems) == 2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000 * int(vitems[0]) + 100 * int(vitems[1])
        else:
            return 1000 * int(vitems[0]) + 100 * int(vitems[1]) + int(
                vitems[2])

    def __init__(self):
        # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL)
        self.pipresents_issue = "1.4.4"
        self.pipresents_minorissue = '1.4.4a'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45  # proportion of width
        self.nonfull_window_height = 0.7  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner

        StopWatch.global_enable = False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM, self.handle_sigterm)

        # ****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()
        # print (self.options)

        # get Pi Presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            if self.options['manager'] is False:
                tkinter.messagebox.showwarning("Pi Presents",
                                               "Bad Application Directory")
            exit(102)

        # Initialise logging and tracing
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes = [
            'PiPresents', 'HyperlinkShow', 'RadioButtonShow', 'ArtLiveShow',
            'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow', 'GapShow',
            'Show', 'ArtShow', 'AudioPlayer', 'BrowserPlayer', 'ImagePlayer',
            'MenuPlayer', 'MessagePlayer', 'VideoPlayer', 'Player',
            'MediaList', 'LiveList', 'ShowList', 'PathManager',
            'ControlsManager', 'ShowManager', 'PluginManager',
            'IOPluginManager', 'MplayerDriver', 'OMXDriver', 'UZBLDriver',
            'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver',
            'CounterManager', 'BeepsManager', 'Network', 'Mailer'
        ]

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        # Monitor.classes=['OSCDriver']

        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched(
            self, None,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue +
            ' at ' + time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log(
            self,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue +
            ' at ' + time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as ifile:
            self.mon.log(self, '\nRaspbian: ' + ifile.read())

        self.mon.log(
            self,
            '\n' + check_output(["omxplayer", "-v"], universal_newlines=True))
        self.mon.log(
            self,
            '\nGPU Memory: ' + check_output(["vcgencmd", "get_mem", "gpu"],
                                            universal_newlines=True))

        if os.geteuid() == 0:
            print('Do not run Pi Presents with sudo')
            self.mon.log(self, 'Do not run Pi Presents with sudo')
            self.mon.finish()
            sys.exit(102)

        if "DESKTOP_SESSION" not in os.environ:
            print('Pi Presents must be run from the Desktop')
            self.mon.log(self, 'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self, 'Desktop is ' + os.environ['DESKTOP_SESSION'])

        # optional other classes used
        self.root = None
        self.ppio = None
        self.tod = None
        self.animate = None
        self.ioplugin_manager = None
        self.oscdriver = None
        self.osc_enabled = False
        self.tod_enabled = False
        self.email_enabled = False

        user = os.getenv('USER')

        if user is None:
            tkinter.messagebox.showwarning(
                "You must be logged in to run Pi Presents")
            exit(102)

        if user != 'pi':
            self.mon.warn(self, "You must be logged as pi to use GPIO")

        self.mon.log(self, 'User is: ' + user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # check network is available
        self.network_connected = False
        self.network_details = False
        self.interface = ''
        self.ip = ''
        self.unit = ''

        # sets self.network_connected and self.network_details
        self.init_network()

        # start the mailer and send email when PP starts
        self.email_enabled = False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime(
                    "%Y-%m-%d %H:%M")
                message = time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\nUnit: ' + self.unit + '   Profile: ' + self.options[
                    'profile'] + '\n ' + self.interface + '\n ' + self.ip
                self.send_email('start', subject, message)

        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path = "/pp_profiles/" + self.options['profile']
        else:
            self.mon.err(self, "Profile not specified in command ")
            self.end('error',
                     'Profile not specified with the commands -p option')

    # get directory containing pp_home from the command,
        if self.options['home'] == "":
            home = os.sep + 'home' + os.sep + user + os.sep + "pp_home"
        else:
            home = self.options['home'] + os.sep + "pp_home"
        self.mon.log(self, "pp_home directory is: " + home)

        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found = False
        for i in range(1, 10):
            self.mon.log(self,
                         "Trying pp_home at: " + home + " (" + str(i) + ')')
            if os.path.exists(home):
                found = True
                self.pp_home = home
                break
            time.sleep(1)
        if found is True:
            self.mon.log(
                self,
                "Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self, "Failed to find pp_home directory at " + home)
            self.end('error', "Failed to find pp_home directory at " + home)

        # check profile exists
        self.pp_profile = self.pp_home + self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self, None,
                           "Running profile: " + self.pp_profile_path)
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.mon.err(
                self, "Failed to find requested profile: " + self.pp_profile)
            self.end('error',
                     "Failed to find requested profile: " + self.pp_profile)

        self.mon.start_stats(self.options['profile'])

        if self.options['verify'] is True:
            self.mon.err(self,
                         "Validation option not supported - use the editor")
            self.end('error',
                     'Validation option not supported - use the editor')

        # initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', "showlist not found at " + self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(
                self,
                "Version of showlist " + self.showlist.profile_version_string +
                " is not  same as Pi Presents")
            self.end(
                'error',
                "Version of showlist " + self.showlist.profile_version_string +
                " is not  same as Pi Presents")

        # get the 'start' show from the showlist
        index = self.showlist.index_of_start_show()
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', "Show [start] not found in showlist")

# ********************
# SET UP THE GUI
# ********************
# turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        # find connected displays and create a canvas for each display
        self.dm = DisplayManager()
        status, message, self.root = self.dm.init(self.options,
                                                  self.handle_user_abort)
        if status != 'normal':
            self.mon.err(self, message)
            self.end('error', message)

        self.mon.log(
            self,
            str(DisplayManager.num_displays) + ' Displays are connected:')

        for display_name in DisplayManager.display_map:
            status, message, display_id, canvas_obj = self.dm.id_of_canvas(
                display_name)
            if status != 'normal':
                continue
            width, height = self.dm.real_display_dimensions(display_id)
            self.mon.log(
                self, '   - ' + self.dm.name_of_display(display_id) + ' Id: ' +
                str(display_id) + ' ' + str(width) + '*' + str(height))
            canvas_obj.config(bg=self.starter_show['background-colour'])

# ****************************************
# INITIALISE THE TOUCHSCREEN DRIVER
# ****************************************

# each driver takes a set of inputs, binds them to symboic names
# and sets up a callback which returns the symbolic name when an input event occurs

        self.sr = ScreenDriver()
        # read the screen click area config file
        reason, message = self.sr.read(pp_dir, self.pp_home, self.pp_profile)
        if reason == 'error':
            self.end('error', 'cannot find, or error in screen.cfg')

        # create click areas on the canvases, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason, message = self.sr.make_click_areas(self.handle_input_event)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False
        self.reboot_required = False
        self.terminate_required = False
        self.exitpipresents_required = False

        # initialise the Beeps Manager
        self.beepsmanager = BeepsManager()
        self.beepsmanager.init(self.pp_home, self.pp_profile)

        # initialise the I/O plugins by importing their drivers
        self.ioplugin_manager = IOPluginManager()
        reason, message = self.ioplugin_manager.init(self.pp_dir,
                                                     self.pp_profile,
                                                     self.root,
                                                     self.handle_input_event,
                                                     self.pp_home)
        if reason == 'error':
            # self.mon.err(self,message)
            self.end('error', message)

        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir, self.pp_home, self.pp_profile, self.root,
                          200, self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id = -1
        self.show_manager = ShowManager(show_id, self.showlist,
                                        self.starter_show, self.root,
                                        self.pp_dir, self.pp_profile,
                                        self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback,
                               self.handle_command, self.showlist)
        # Register all the shows in the showlist
        reason, message = self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled = False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' +
                              os.sep + 'osc.cfg'):
                self.oscdriver = OSCDriver()
                reason, message = self.oscdriver.init(
                    self.pp_profile, self.unit, self.interface, self.ip,
                    self.handle_command, self.handle_input_event,
                    self.e_osc_handle_animate)
                if reason == 'error':
                    self.mon.err(self, message)
                    self.end('error', message)
                else:
                    self.osc_enabled = True
                    self.root.after(1000, self.oscdriver.start_server())

        # initialise ToD scheduler calculating schedule for today
        self.tod = TimeOfDay()
        reason, message, self.tod_enabled = self.tod.init(
            pp_dir, self.pp_home, self.pp_profile, self.showlist, self.root,
            self.handle_command)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # warn if the network not available when ToD required
        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(
                self,
                'Network not connected  so Time of Day scheduler may be using the internal clock'
            )

        # init the counter manager
        self.counter_manager = CounterManager()
        if self.starter_show['counters-store'] == 'yes':
            store_enable = True
        else:
            store_enable = False
        reason, message = self.counter_manager.init(
            self.pp_profile + '/counters.cfg', store_enable,
            self.options['loadcounters'],
            self.starter_show['counters-initial'])
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # warn about start shows and scheduler

        if self.starter_show['start-show'] == '' and self.tod_enabled is False:
            self.mon.sched(
                self, None,
                "No Start Shows in Start Show and no shows scheduled")
            self.mon.warn(
                self, "No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] != '' and self.tod_enabled is True:
            self.mon.sched(
                self, None,
                "Start Shows in Start Show and shows scheduled - conflict?")
            self.mon.warn(
                self,
                "Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod.poll()

        # start the I/O plugins input event generation
        self.ioplugin_manager.start()

        # start Tkinters event loop
        self.root.mainloop()

# *********************
#  RUN START SHOWS
# ********************

    def run_start_shows(self):
        self.mon.trace(self, 'run start shows')
        # parse the start shows field and start the initial shows
        show_refs = self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason, message = self.show_manager.control_a_show(
                show_ref, 'open')
            if reason == 'error':
                self.mon.err(self, message)

# *********************
# User inputs
# ********************

    def e_osc_handle_animate(self, line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_animate(arg))

    def osc_handle_animate(self, line):
        self.mon.log(self, "animate command received: " + line)
        #osc sends output events as a string
        reason, message, delay, name, param_type, param_values = self.animate.parse_animate_fields(
            line)
        if reason == 'error':
            self.mon.err(self, message)
            self.end(reason, message)
        self.handle_output_event(name, param_type, param_values, 0)

    # output events are animate commands
    def handle_output_event(self, symbol, param_type, param_values, req_time):
        reason, message = self.ioplugin_manager.handle_output_event(
            symbol, param_type, param_values, req_time)
        if reason == 'error':
            self.mon.err(self, message)
            self.end(reason, message)

    # all input events call this callback providing a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self, symbol, source):
        self.mon.log(self, "event received: " + symbol + ' from ' + source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()

        elif symbol == 'pp-shutdown':
            self.mon.err(
                self,
                'pp-shutdown removed in version 1.3.3a, see Release Notes')
            self.end(
                'error',
                'pp-shutdown removed in version 1.3.3a, see Release Notes')

        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1, self.shutdownnow_pressed)
            return

        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            reason, message = self.show_manager.exit_all_shows()
        else:
            # pass the input event to all registered shows
            for show in self.show_manager.shows:
                show_obj = show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)

    # commands are generated by tracks and shows
    # they can open or close shows, generate input events and do special tasks
    # commands also generate osc outputs to other computers
    # handles one command provided as a line of text

    def handle_command(self, command_text, source='', show=''):
        # print 'PIPRESENTS ',command_text,'\n   Source',source,'from',show
        self.mon.log(self, "command received: " + command_text)
        if command_text.strip() == "":
            return

        fields = command_text.split()

        if fields[0] in ('osc', 'OSC'):
            if self.osc_enabled is True:
                status, message = self.oscdriver.parse_osc_command(fields[1:])
                if status == 'warn':
                    self.mon.warn(self, message)
                if status == 'error':
                    self.mon.err(self, message)
                    self.end('error', message)
                return
            else:
                return

        if fields[0] == 'counter':
            status, message = self.counter_manager.parse_counter_command(
                fields[1:])
            if status == 'error':
                self.mon.err(self, message)
                self.end('error', message)
            return

        if fields[0] == 'beep':
            # cheat, field 0 will always be beep
            message, fields = self.beepsmanager.parse_beep(command_text)
            if message != '':
                self.mon.err(self, message)
                self.end('error', message)
                return
            location = self.beepsmanager.complete_path(fields[1])
            if not os.path.exists(location):
                message = 'Beep file does not exist: ' + location
                self.mon.err(self, message)
                self.end('error', message)
                return
            else:
                self.beepsmanager.do_beep(location)
            return

        show_command = fields[0]
        if len(fields) > 1:
            show_ref = fields[1]
        else:
            show_ref = ''
        if show_command in ('open', 'close', 'closeall', 'openexclusive'):
            self.mon.sched(self, TimeOfDay.now,
                           command_text + ' received from show:' + show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason, message = self.show_manager.control_a_show(
                    show_ref, show_command)
            else:
                return

        elif show_command == 'monitor':
            self.handle_monitor_command(show_ref)
            return

        elif show_command == 'cec':
            self.handle_cec_command(show_ref)
            return

        elif show_command == 'event':
            self.handle_input_event(show_ref, 'Show Control')
            return

        elif show_command == 'exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            else:
                reason, message = self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1, self.shutdownnow_pressed)
            return

        elif show_command == 'reboot':
            # need root.after to get out of st thread
            self.root.after(1, self.reboot_pressed)
            return

        else:
            reason = 'error'
            message = 'command not recognised: ' + show_command

        if reason == 'error':
            self.mon.err(self, message)
        return

    def handle_monitor_command(self, command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')

    def handle_cec_command(self, command):
        if command == 'on':
            os.system('echo "on 0" | cec-client -s')
        elif command == 'standby':
            os.system('echo "standby 0" | cec-client -s')

        elif command == 'scan':
            os.system('echo scan | cec-client -s -d 1')

    # deal with differnt commands/input events

    def shutdownnow_pressed(self):
        self.shutdown_required = True
        if self.show_manager.all_shows_exited() is True:
            self.all_shows_ended_callback('normal', 'no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def reboot_pressed(self):
        self.reboot_required = True
        if self.show_manager.all_shows_exited() is True:
            self.all_shows_ended_callback('normal', 'no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def handle_sigterm(self, signum, fframe):
        self.mon.log(self, 'SIGTERM received - ' + str(signum))
        self.terminate()

    def handle_user_abort(self):
        self.mon.log(self, 'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required = True
        needs_termination = False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination = True
                self.mon.log(
                    self,
                    "Sent terminate to show " + show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed', 'killed - no termination of shows required')

# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal', 'no shows running')

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self, reason, message):
        for display_name in DisplayManager.display_map:
            status, message, display_id, canvas_obj = self.dm.id_of_canvas(
                display_name)
            if status != 'normal':
                continue
            canvas_obj.config(bg=self.starter_show['background-colour'])
        if reason in (
                'killed', 'error'
        ) or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True:
            self.end(reason, message)

    def end(self, reason, message):
        self.mon.log(self, "Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip
                self.send_email(reason, subject, message)
            self.mon.sched(self, None, "Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")

            # close logging files
            self.mon.finish()
            print('Uncollectable Garbage', gc.collect())
            # objgraph.show_backrefs(objgraph.by_type('Canvas'),filename='backrefs.png')
            sys.exit(101)

        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: ' + message + '\n' + time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip
                self.send_email(reason, subject, message_text)
            self.mon.sched(self, None,
                           "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")

            # close logging files
            self.mon.finish()
            print('uncollectable garbage', gc.collect())
            sys.exit(102)

        else:
            self.mon.sched(self, None, "Pi Presents  exiting normally, bye\n")
            self.mon.log(self, "Pi Presents  exiting normally, bye")

            # close logging files
            self.mon.finish()
            if self.reboot_required is True:
                # print 'REBOOT'
                call(['sudo', 'reboot'])
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call(['sudo', 'shutdown', 'now', 'SHUTTING DOWN'])
            print('uncollectable garbage', gc.collect())
            sys.exit(100)

    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset", "s", "on"])
            call(["xset", "s", "+dpms"])

        # tidy up animation
        if self.animate is not None:
            self.animate.terminate()

        # tidy up i/o plugins
        if self.ioplugin_manager != None:
            self.ioplugin_manager.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()

        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()


# *******************************
# Connecting to network and email
# *******************************

    def init_network(self):

        timeout = int(self.options['nonetwork'])
        if timeout == 0:
            self.network_connected = False
            self.unit = ''
            self.ip = ''
            self.interface = ''
            return

        self.network = Network()
        self.network_connected = False

        # try to connect to network
        self.mon.log(self,
                     'Waiting up to ' + str(timeout) + ' seconds for network')
        success = self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(
                self, 'Failed to connect to network after ' + str(timeout) +
                ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected = True
        self.mon.sched(
            self, None, 'Time after network check is ' +
            time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log(
            self, 'Time after network check is ' +
            time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details = False
        network_options_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(
                self, "pp_web.cfg not found at " + network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit = self.network.unit

        # get interface and IP details of preferred interface
        self.interface, self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected = False
            return
        self.network_details = True
        self.mon.log(
            self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +
            self.ip)

    def init_mailer(self):

        self.email_enabled = False
        email_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self, 'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self, 'Found pp_email.cfg at ' + email_file_path)
        self.mailer = Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled = True
            self.mon.log(self, 'Email Enabled')

    def try_connect(self):
        tries = 1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(
                    self, 'Failed to connect to email SMTP server ' +
                    str(tries) + '\n ' + str(error))
                tries += 1
                if tries > 5:
                    self.mon.log(
                        self, 'Failed to connect to email SMTP server after ' +
                        str(tries))
                    return False

    def send_email(self, reason, subject, message):
        if self.try_connect() is False:
            return False
        else:
            success, error = self.mailer.send(subject, message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success, error = self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,
                                 'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self, 'Sent email for ' + reason)
                success, error = self.mailer.disconnect()
                if success is False:
                    self.mon.log(
                        self,
                        'Failed disconnect from email server ' + str(error))
                return True
Пример #5
0
class ShowManager(object):
    """
    ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them
    concurrent shows are always top level (level 0) shows:
    They can be opened/closed  by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC
    
   Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance.
   However a workaround is to make the secong instance a subshow of a mediashow with a different reference.

    """

    # Declare class variables
    shows = []
    canvas = None  #canvas for all shows
    shutdown_required = False
    SHOW_TEMPLATE = ['', None]
    SHOW_REF = 0  # show-reference  - name of the show as in editor
    SHOW_OBJ = 1  # the python object
    showlist = []

    # Initialise class variables, first time through only in pipresents.py

    def init(self, canvas, all_shows_ended_callback, command_callback,
             showlist):
        ShowManager.all_shows_ended_callback = all_shows_ended_callback
        ShowManager.shows = []
        ShowManager.shutdown_required = False
        ShowManager.canvas = canvas
        ShowManager.command_callback = command_callback
        ShowManager.showlist = showlist

# **************************************
# functions to manipulate show register
# **************************************

    def register_shows(self):
        for show in ShowManager.showlist.shows():
            if show['show-ref'] != 'start':
                reason, message = self.register_show(show['show-ref'])
                if reason == 'error':
                    return reason, message
        return 'normal', 'shows regiistered'

    def register_show(self, ref):
        registered = self.show_registered(ref)
        if registered == -1:
            ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE))
            index = len(ShowManager.shows) - 1
            ShowManager.shows[index][ShowManager.SHOW_REF] = ref
            ShowManager.shows[index][ShowManager.SHOW_OBJ] = None
            self.mon.trace(
                self, ' - register show: show_ref = ' + ref + ' index = ' +
                str(index))
            return 'normal', 'show registered'
        else:
            # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref )
            return 'error', ' more than one show in showlist with show-ref: ' + ref

    # is the show registered?
    # can be used to return the index to the show
    def show_registered(self, show_ref):
        index = 0
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_REF] == show_ref:
                return index
            index += 1
        return -1

    # needs calling program to check that the show is not already running
    def set_running(self, index, show_obj):
        ShowManager.shows[index][ShowManager.SHOW_OBJ] = show_obj
        self.mon.trace(
            self,
            'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] +
            ' show_id= ' + str(index))

    # is the show running?
    def show_running(self, index):
        if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None:
            return ShowManager.shows[index][ShowManager.SHOW_OBJ]
        else:
            return None

    def set_exited(self, index):
        ShowManager.shows[index][ShowManager.SHOW_OBJ] = None
        self.mon.trace(
            self,
            'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] +
            ' show_id= ' + str(index))

    # are all shows exited?
    def all_shows_exited(self):
        all_exited = True
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_OBJ] is not None:
                all_exited = False
        return all_exited

    # fromat for printing
    def pretty_shows(self):
        shows = '\n'
        for show in ShowManager.shows:
            shows += show[ShowManager.SHOW_REF] + '\n'
        return shows


# *********************************
# show control
# *********************************

# show manager can be initialised by a player, shower or by pipresents.py
# if by pipresents.py then show_id=-1

    def __init__(self, show_id, showlist, show_params, root, canvas, pp_dir,
                 pp_profile, pp_home):
        self.show_id = show_id
        self.showlist = showlist
        self.show_params = show_params
        self.root = root
        self.show_canvas = canvas
        self.pp_dir = pp_dir
        self.pp_profile = pp_profile
        self.pp_home = pp_home

        self.mon = Monitor()

    def control_a_show(self, show_ref, show_command):
        if show_command == 'open':
            return self.start_show(show_ref)
        elif show_command == 'close':
            return self.exit_show(show_ref)
        elif show_command == 'closeall':
            return self.exit_all_shows()
        elif show_command == 'openexclusive':
            return self.open_exclusive(show_ref)
        else:
            return 'error', 'command not recognised ' + show_command

    def open_exclusive(self, show_ref):
        self.exit_all_shows()
        self.exclusive_show = show_ref
        self.wait_for_openexclusive()
        return 'normal', 'opened exclusive'

    def wait_for_openexclusive(self):
        if self.all_shows_exited() is False:
            ShowManager.canvas.after(1, self.wait_for_openexclusive)
            return
        self.start_show(self.exclusive_show)

    def exit_all_shows(self):
        for show in ShowManager.shows:
            self.exit_show(show[ShowManager.SHOW_REF])
        return 'normal', 'exited all shows'

    # kick off the exit sequence of a show by calling the shows exit method.
    # it will result in all the shows in a stack being closed and end_play_show being called
    def exit_show(self, show_ref):
        index = self.show_registered(show_ref)
        self.mon.log(
            self,
            "De-registering show " + show_ref + ' show index:' + str(index))
        show_obj = self.show_running(index)
        if show_obj is not None:
            self.mon.log(
                self, "Exiting show " + show_ref + ' show index:' + str(index))
            show_obj.exit()
        return 'normal', 'exited a concurrent show'

    def start_show(self, show_ref):
        index = self.show_registered(show_ref)
        if index < 0:
            return 'error', "Show not found in showlist: " + show_ref
        show_index = self.showlist.index_of_show(show_ref)
        show = self.showlist.show(show_index)
        reason, message, show_canvas = self.compute_show_canvas(show)
        if reason == 'error':
            return reason, message
        # print 'STARTING TOP LEVEL SHOW',show_canvas
        self.mon.sched(
            self, TimeOfDay.now, 'Starting Show: ' + show_ref +
            ' from show: ' + self.show_params['show-ref'])
        self.mon.log(
            self, 'Starting Show: ' + show_ref + ' from: ' +
            self.show_params['show-ref'])
        if self.show_running(index):
            self.mon.sched(
                self, TimeOfDay.now,
                "show already running so ignoring command: " + show_ref)
            self.mon.warn(
                self, "show already running so ignoring command: " + show_ref)
            return 'normal', 'this concurrent show already running'
        show_obj = self.init_show(index, show, show_canvas)
        if show_obj is None:
            return 'error', "unknown show type in start concurrent show - " + show[
                'type']
        else:
            self.set_running(index, show_obj)
            # params - end_callback, show_ready_callback, parent_kickback_signal, level
            show_obj.play(self._end_play_show, None, False, 0, [])
            return 'normal', 'concurrent show started'

    # used by shows to create subshows or child shows
    def init_subshow(self, show_id, show, show_canvas):
        return self.init_show(show_id, show, show_canvas)

    def _end_play_show(self, index, reason, message):
        show_ref_to_exit = ShowManager.shows[index][ShowManager.SHOW_REF]
        show_to_exit = ShowManager.shows[index][ShowManager.SHOW_OBJ]
        self.mon.sched(self, TimeOfDay.now, 'Closed show: ' + show_ref_to_exit)
        self.mon.log(
            self, 'Exited from show: ' + show_ref_to_exit + ' ' + str(index))
        self.mon.log(self, 'Exited with Reason = ' + reason)
        self.mon.trace(
            self,
            ' Show is: ' + show_ref_to_exit + ' show index ' + str(index))
        # closes the video/audio from last track then closes the track
        # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player
        self.set_exited(index)
        if self.all_shows_exited() is True:
            ShowManager.all_shows_ended_callback(reason, message)
        return reason, message

    # common function to initilaise the show by type
    def init_show(
        self,
        show_id,
        selected_show,
        show_canvas,
    ):
        if selected_show['type'] == "mediashow":
            return MediaShow(show_id, selected_show, self.root, show_canvas,
                             self.showlist, self.pp_dir, self.pp_home,
                             self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "liveshow":
            return LiveShow(show_id, selected_show, self.root, show_canvas,
                            self.showlist, self.pp_dir, self.pp_home,
                            self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "radiobuttonshow":
            return RadioButtonShow(show_id, selected_show, self.root,
                                   show_canvas, self.showlist, self.pp_dir,
                                   self.pp_home, self.pp_profile,
                                   ShowManager.command_callback)

        elif selected_show['type'] == "hyperlinkshow":
            return HyperlinkShow(show_id, selected_show, self.root,
                                 show_canvas, self.showlist, self.pp_dir,
                                 self.pp_home, self.pp_profile,
                                 ShowManager.command_callback)

        elif selected_show['type'] == "menu":
            return MenuShow(show_id, selected_show, self.root, show_canvas,
                            self.showlist, self.pp_dir, self.pp_home,
                            self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "artmediashow":
            return ArtMediaShow(show_id, selected_show, self.root, show_canvas,
                                self.showlist, self.pp_dir, self.pp_home,
                                self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "artliveshow":
            return ArtLiveShow(show_id, selected_show, self.root, show_canvas,
                               self.showlist, self.pp_dir, self.pp_home,
                               self.pp_profile, ShowManager.command_callback)
        else:
            return None

    def compute_show_canvas(self, show_params):
        canvas = {}
        canvas['canvas-obj'] = ShowManager.canvas
        status, message, self.show_canvas_x1, self.show_canvas_y1, self.show_canvas_x2, self.show_canvas_y2 = self.parse_show_canvas(
            show_params['show-canvas'])
        if status == 'error':
            # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas'])
            return 'error', 'show canvas error: ' + message + ' in ' + show_params[
                'show-canvas'], canvas
        else:
            self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1
            self.show_canvas_height = self.show_canvas_y2 - self.show_canvas_y1
            self.show_canvas_centre_x = self.show_canvas_width / 2
            self.show_canvas_centre_y = self.show_canvas_height / 2
            canvas['show-canvas-x1'] = self.show_canvas_x1
            canvas['show-canvas-y1'] = self.show_canvas_y1
            canvas['show-canvas-x2'] = self.show_canvas_x2
            canvas['show-canvas-y2'] = self.show_canvas_y2
            canvas['show-canvas-width'] = self.show_canvas_width
            canvas['show-canvas-height'] = self.show_canvas_height
            canvas['show-canvas-centre-x'] = self.show_canvas_centre_x
            canvas['show-canvas-centre-y'] = self.show_canvas_centre_y
            return 'normal', '', canvas

    def parse_show_canvas(self, text):
        fields = text.split()
        # blank so show canvas is the whole screen
        if len(fields) < 1:
            return 'normal', '', 0, 0, int(self.canvas['width']), int(
                self.canvas['height'])

        elif len(fields) in (1, 4):
            # window is specified
            status, message, x1, y1, x2, y2 = parse_rectangle(text)
            if status == 'error':
                return 'error', message, 0, 0, 0, 0
            else:
                return 'normal', '', x1, y1, x2, y2
        else:
            # error
            return 'error', 'Wrong number of fields in Show canvas: ' + text, 0, 0, 0, 0
Пример #6
0
class PiPresents(object):

    def pipresents_version(self):
        vitems=self.pipresents_issue.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])


    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3.2"
        self.pipresents_minorissue = '1.3.2a'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner


        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit(102)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'GapShow','Show','ArtShow',
                            'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player',
                            'MediaList','LiveList','ShowList',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver',
                            'Network','Mailer'
                            ]
        

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as file:
            self.mon.log(self,'\nRaspbian: '+file.read())

        self.mon.log(self,'\n'+check_output(["omxplayer", "-v"]))
        self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"]))
        
        if "DESKTOP_SESSION" not in os.environ:
            print 'Pi Presents must be run from the Desktop'
            self.mon.log(self,'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION'])
        
        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
        self.email_enabled=False


        if os.geteuid() == 0:
            self.mon.err(self,'Do not run Pi Presents with sudo')
            self.end('error','Do not run Pi Presents with sudo')

        
        user=os.getenv('USER')

        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work



        # check network is available
        self.network_connected=False
        self.network_details=False
        self.interface=''
        self.ip=''
        self.unit=''
        
        # sets self.network_connected and self.network_details
        self.init_network()

        
        # start the mailer and send email when PP starts
        self.email_enabled=False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M")
                message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + '   Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip 
                self.send_email('start',subject,message) 

         
        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.mon.err(self,"Profile not specified in command ")
            self.end('error','Profile not specified with the commands -p option')
        
       # get directory containing pp_home from the command,
        if self.options['home']  == "":
            home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)


        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found is True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self,"Failed to find pp_home directory at " + home)
            self.end('error',"Failed to find pp_home directory at " + home)


        # check profile exists
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self,"Running profile: " + self.pp_profile_path)
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error',"Failed to find requested profile: "+ self.pp_profile)

        self.mon.start_stats(self.options['profile'])
        
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

         
        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error',"showlist not found at "+self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")
            self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error',"Show [start] not found in showlist")


# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.starter_show['background-colour'])

        self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvas cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.starter_show['background-colour'])


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find, or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find, or error in screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.terminate_required=False
        self.exitpipresents_required=False

        # delete omxplayer dbus files
        # if os.path.exists("/tmp/omxplayerdbus.{}".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}".format(user))
        # if os.path.exists("/tmp/omxplayerdbus.{}.pid".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}.pid".format(user))
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
                self.oscdriver=OSCDriver()
                reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end('error',message)
                else:
                    self.osc_enabled=True
                    self.root.after(1000,self.oscdriver.start_server())

        
        # enable ToD scheduler if schedule exists      
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):                
            self.tod_enabled = True
        else:
            self.tod_enabled=False

        # warn if the network not available when ToD required

        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(self,'Network not connected  so Time of Day scheduler may be using the internal clock')

        # warn about start shows and scheduler

        if self.starter_show['start-show']=='' and self.tod_enabled is False:
            self.mon.sched(self,"No Start Shows in Start Show and no shows scheduled") 
            self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] !='' and self.tod_enabled is True:
            self.mon.sched(self,"Start Shows in Start Show and shows scheduled - conflict?") 
            self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()           

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod.poll()            


        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --screensize comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in --screensize',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************
    # handles one command provided as a line of text
    
    def handle_command(self,command_text,source='',show=''):
        # print 'PIPRESENTS ',command_text,source,'from',show
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        if command_text[0]=='/': 
            if self.osc_enabled is True:
                self.oscdriver.send_command(command_text)
            return
        
        fields= command_text.split()
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''

        if show_command in ('open','close'):
            self.mon.sched(self, command_text + ' received from show:'+show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
        elif show_command =='monitor':
            self.handle_monitor_command(show_ref)
            return
        elif show_command == 'event':
            self.handle_input_event(show_ref,'Show Control')
            return
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return


    def handle_monitor_command(self,command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')           
                      
    

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal','no shows running')

    def e_shutdown_pressed(self):
        self.shutdown_pressed('now')


    def e_osc_handle_output_event(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg))

    def  osc_handle_output_event(self,line):
        self.mon.log(self,"output event received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

               
    def handle_output_event(self,symbol,param_type,param_values,req_time):
        if self.gpio_enabled is True:
            reason,message=self.gpiodriver.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)
        else:
            self.mon.warn(self,'GPIO not enabled')


    # all input events call this callback with a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # events for shows affect the show and could cause it to exit.
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()           


    def on_shutdown_delay(self):
        # 5 second delay is up, if shutdown button still pressed then shutdown
        if self.gpiodriver.shutdown_pressed() is True:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()


    def handle_sigterm(self,signum,frame):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required=True
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.starter_show['background-colour'])
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        # gc.collect()
        # print gc.garbage
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message)
            self.mon.sched(self, "Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")
                          
            # close logging files 
            self.mon.finish()
            sys.exit(101)
                          
        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message_text)   
            self.mon.sched(self, "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")
                          
            # close logging files 
            self.mon.finish()
            sys.exit(102)

        else:           
            self.mon.sched(self,"Pi Presents  exiting normally, bye\n")
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            
            # close logging files 
            self.mon.finish()
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call (['sudo','shutdown','now','SHUTTING DOWN'])
            sys.exit(100)



    def init_network(self):

        timeout=int(self.options['nonetwork'])
        if timeout== 0:
            self.network_connected=False
            self.unit=''
            self.ip=''
            self.interface=''
            return
        
        self.network=Network()
        self.network_connected=False

        # try to connect to network
        self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network')
        success=self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected=True
        self.mon.sched (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details=False
        network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit=self.network.unit

        # get interface and IP details of preferred interface
        self.interface,self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected=False
            return
        self.network_details=True
        self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip)


    def init_mailer(self):

        self.email_enabled=False
        email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self,'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self,'Found pp_email.cfg at ' + email_file_path)
        self.mailer=Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled=True
            self.mon.log (self,'Email Enabled')



    def send_email(self,reason,subject,message):
        if self.try_connect() is False:
            return False
        else:
            success,error = self.mailer.send(subject,message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self,'Sent email for ' + reason)
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect from email server ' + str(error))
                return True


    def try_connect(self):
        tries=1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) +  '\n ' +str(error))
                tries +=1
                if tries >5:
                    self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries))
                    return False

                
    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation and gpio
        if self.animate is not None:
            self.animate.terminate()
            
        if self.gpio_enabled==True:
            self.gpiodriver.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()
Пример #7
0
class PiPresents(object):

    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3"
        self.pipresents_minorissue = '1.3.1g'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner
        self.pp_background='black'

        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory:\n{0}".format(pp_dir))
            exit(103)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()
        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents', 'pp_paths',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver'
                            ]

        # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)    
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue)
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        if os.geteuid() !=0:
            user=os.getenv('USER')
        else:
            user = os.getenv('SUDO_USER')
        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
         
        # get home path from -o option
        self.pp_home = pp_paths.get_home(self.options['home'])
        if self.pp_home is None:
            self.end('error','Failed to find pp_home')

        # get profile path from -p option
        # pp_profile is the full path to the directory that contains 
        # pp_showlist.json and other files for the profile
        self.pp_profile = pp_paths.get_profile_dir(self.pp_home, self.options['profile'])
        if self.pp_profile is None:
            self.end('error','Failed to find profile')

        # check profile exists
        if os.path.exists(self.pp_profile):
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error','Failed to find profile')

        self.mon.start_stats(self.options['profile'])
        
        # check 'verify' option
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error','showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) != float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self.end('error','wrong version of profile')


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error','start show not found')

        if self.starter_show['start-show']=='':
             self.mon.warn(self,"No Start Shows in Start Show")       

# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.pp_background)

        self.mon.log(self, 'native screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixcels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'commanded screen dimensions are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixcels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter 1>&- 2>&- &') # Suppress 'someone created a subwindow' complaints from unclutter
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvs cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.pp_background)


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.exitpipresents_required=False
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
            self.oscdriver=OSCDriver()
            reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.osc_enabled=True
                self.root.after(1000,self.oscdriver.start_server())

        # and run the start shows
        self.run_start_shows()

       # set up the time of day scheduler including catchup         
        self.tod_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):
            # kick off the time of day scheduler which may run additional shows
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod_enabled = True


        # then start the time of day scheduler
        if self.tod_enabled is True:
            self.tod.poll()

        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --fullscreen comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in ---fullscreen',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************
    # handles one command provided as a line of text
    def handle_command(self,command_text):
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        if command_text[0]=='/': 
            if self.osc_enabled is True:
                self.oscdriver.send_command(command_text)
            return
        
        fields= command_text.split()
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''
            
        if show_command in ('open','close'):
            if self.shutdown_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return

    def e_all_shows_ended_callback(self):
            self.all_shows_ended_callback('normal','no shows running')

    def e_shutdown_pressed(self):
            self.shutdown_pressed('now')


    def e_osc_handle_output_event(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg))

    def  osc_handle_output_event(self,line):
        self.mon.log(self,"output event received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

               
    def handle_output_event(self,symbol,param_type,param_values,req_time):
        if self.gpio_enabled is True:
            reason,message=self.gpiodriver.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)
        else:
            self.mon.warn(self,'GPIO not enabled')


    # all input events call this callback with a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # events for shows affect the show and could cause it to exit.
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()           


    def on_shutdown_delay(self):
        # 5 second delay is up, if shutdown button still pressed then shutdown
        if self.gpiodriver.shutdown_pressed() is True:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()


    def handle_sigterm(self,signum,frame):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.pp_background)
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        # gc.collect()
        # print gc.garbage
        if reason == 'killed':
            self.mon.log(self, "Pi Presents Aborted, au revoir")
            # close logging files 
            self.mon.finish()
            sys.exit(101)     
        elif reason == 'error':
            self.mon.log(self, "Pi Presents closing because of error, sorry")
            # close logging files 
            self.mon.finish()
            sys.exit(102)            
        else:
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            # close logging files 
            self.mon.finish()
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call(['sudo', 'shutdown', '-h', '-t 5','now'])
                sys.exit(100)
            else:
                sys.exit(100)


    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation and gpio
        if self.animate is not None:
            self.animate.terminate()
            
        if self.gpio_enabled==True:
            self.gpiodriver.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()
Пример #8
0
class PiPresents(object):
    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES
                     | gc.DEBUG_OBJECTS | gc.DEBUG_SAVEALL)
        self.pipresents_issue = "1.3"
        self.pipresents_minorissue = '1.3.1g'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45  # proportion of width
        self.nonfull_window_height = 0.7  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner
        self.pp_background = 'black'

        StopWatch.global_enable = False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM, self.handle_sigterm)

        # ****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()

        # get Pi Presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            if self.options['manager'] is False:
                tkMessageBox.showwarning(
                    "Pi Presents",
                    "Bad Application Directory:\n{0}".format(pp_dir))
            exit(103)

        # Initialise logging and tracing
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes = [
            'PiPresents', 'pp_paths', 'HyperlinkShow', 'RadioButtonShow',
            'ArtLiveShow', 'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow',
            'PathManager', 'ControlsManager', 'ShowManager', 'PluginManager',
            'MplayerDriver', 'OMXDriver', 'UZBLDriver', 'KbdDriver',
            'GPIODriver', 'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver'
        ]

        # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','OMXDriver']

        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.log(
            self,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue)
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])
        if os.geteuid() != 0:
            user = os.getenv('USER')
        else:
            user = os.getenv('SUDO_USER')
        self.mon.log(self, 'User is: ' + user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # optional other classes used
        self.root = None
        self.ppio = None
        self.tod = None
        self.animate = None
        self.gpiodriver = None
        self.oscdriver = None
        self.osc_enabled = False
        self.gpio_enabled = False
        self.tod_enabled = False

        # get home path from -o option
        self.pp_home = pp_paths.get_home(self.options['home'])
        if self.pp_home is None:
            self.end('error', 'Failed to find pp_home')

        # get profile path from -p option
        # pp_profile is the full path to the directory that contains
        # pp_showlist.json and other files for the profile
        self.pp_profile = pp_paths.get_profile_dir(self.pp_home,
                                                   self.options['profile'])
        if self.pp_profile is None:
            self.end('error', 'Failed to find profile')

        # check profile exists
        if os.path.exists(self.pp_profile):
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.mon.err(
                self, "Failed to find requested profile: " + self.pp_profile)
            self.end('error', 'Failed to find profile')

        self.mon.start_stats(self.options['profile'])

        # check 'verify' option
        if self.options['verify'] is True:
            val = Validator()
            if val.validate_profile(None, pp_dir, self.pp_home,
                                    self.pp_profile, self.pipresents_issue,
                                    False) is False:
                self.mon.err(self, "Validation Failed")
                self.end('error', 'Validation Failed')

        # initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', 'showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) != float(self.pipresents_issue):
            self.mon.err(
                self, "Version of profile " + self.showlist.sissue() +
                " is not  same as Pi Presents, must exit")
            self.end('error', 'wrong version of profile')

        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', 'start show not found')

        if self.starter_show['start-show'] == '':
            self.mon.warn(self, "No Start Shows in Start Show")

# ********************
# SET UP THE GUI
# ********************
# turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        self.root = Tk()

        self.title = 'Pi Presents - ' + self.pp_profile
        self.icon_text = 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.pp_background)

        self.mon.log(
            self, 'native screen dimensions are ' +
            str(self.root.winfo_screenwidth()) + ' x ' +
            str(self.root.winfo_screenheight()) + ' pixcels')
        if self.options['screensize'] == '':
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason, message, self.screen_width, self.screen_height = self.parse_screen(
                self.options['screensize'])
            if reason == 'error':
                self.mon.err(self, message)
                self.end('error', message)

        self.mon.log(
            self, 'commanded screen dimensions are ' + str(self.screen_width) +
            ' x ' + str(self.screen_height) + ' pixcels')

        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width = int(self.root.winfo_screenwidth() *
                                    self.nonfull_window_width)
            self.window_height = int(self.root.winfo_screenheight() *
                                     self.nonfull_window_height)
            self.window_x = self.nonfull_window_x
            self.window_y = self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
        else:
            self.window_width = self.screen_width
            self.window_height = self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system(
                'unclutter 1>&- 2>&- &'
            )  # Suppress 'someone created a subwindow' complaints from unclutter
            self.window_x = 0
            self.window_y = 0
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
            self.root.attributes('-zoomed', '1')

        # canvs cover the whole screen whatever the size of the window.
        self.canvas_height = self.screen_height
        self.canvas_width = self.screen_width

        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.pp_background)

        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=1,
                               highlightcolor='yellow')

        self.canvas.place(x=0, y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()

        # ****************************************
        # INITIALISE THE INPUT DRIVERS
        # ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd = KbdDriver()
        if kbd.read(pp_dir, self.pp_home, self.pp_profile) is False:
            self.end('error', 'cannot find or error in keys.cfg')
        kbd.bind_keys(self.root, self.handle_input_event)

        self.sr = ScreenDriver()
        # read the screen click area config file
        reason, message = self.sr.read(pp_dir, self.pp_home, self.pp_profile)
        if reason == 'error':
            self.end('error', 'cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason, message = self.sr.make_click_areas(self.canvas,
                                                   self.handle_input_event)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False
        self.exitpipresents_required = False

        # kick off GPIO if enabled by command line option
        self.gpio_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' + os.sep +
                          'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver = GPIODriver()
            reason, message = self.gpiodriver.init(pp_dir, self.pp_home,
                                                   self.pp_profile,
                                                   self.canvas, 50,
                                                   self.handle_input_event)
            if reason == 'error':
                self.end('error', message)
            else:
                self.gpio_enabled = True
                # and start polling gpio
                self.gpiodriver.poll()

        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir, self.pp_home, self.pp_profile, self.canvas,
                          200, self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id = -1
        self.show_manager = ShowManager(show_id, self.showlist,
                                        self.starter_show, self.root,
                                        self.canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas, self.all_shows_ended_callback,
                               self.handle_command, self.showlist)
        # Register all the shows in the showlist
        reason, message = self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' + os.sep +
                          'osc.cfg'):
            self.oscdriver = OSCDriver()
            reason, message = self.oscdriver.init(
                self.pp_profile, self.handle_command, self.handle_input_event,
                self.e_osc_handle_output_event)
            if reason == 'error':
                self.end('error', message)
            else:
                self.osc_enabled = True
                self.root.after(1000, self.oscdriver.start_server())

        # and run the start shows
        self.run_start_shows()

        # set up the time of day scheduler including catchup
        self.tod_enabled = False
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):
            # kick off the time of day scheduler which may run additional shows
            self.tod = TimeOfDay()
            self.tod.init(pp_dir, self.pp_home, self.pp_profile, self.root,
                          self.handle_command)
            self.tod_enabled = True

        # then start the time of day scheduler
        if self.tod_enabled is True:
            self.tod.poll()

        # start Tkinters event loop
        self.root.mainloop()

    def parse_screen(self, size_text):
        fields = size_text.split('*')
        if len(fields) != 2:
            return 'error', 'do not understand --fullscreen comand option', 0, 0
        elif fields[0].isdigit() is False or fields[1].isdigit() is False:
            return 'error', 'dimensions are not positive integers in ---fullscreen', 0, 0
        else:
            return 'normal', '', int(fields[0]), int(fields[1])

# *********************
#  RUN START SHOWS
# ********************

    def run_start_shows(self):
        self.mon.trace(self, 'run start shows')
        # parse the start shows field and start the initial shows
        show_refs = self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason, message = self.show_manager.control_a_show(
                show_ref, 'open')
            if reason == 'error':
                self.mon.err(self, message)

# *********************
# User inputs
# ********************
# handles one command provided as a line of text

    def handle_command(self, command_text):
        self.mon.log(self, "command received: " + command_text)
        if command_text.strip() == "":
            return

        if command_text[0] == '/':
            if self.osc_enabled is True:
                self.oscdriver.send_command(command_text)
            return

        fields = command_text.split()
        show_command = fields[0]
        if len(fields) > 1:
            show_ref = fields[1]
        else:
            show_ref = ''

        if show_command in ('open', 'close'):
            if self.shutdown_required is False:
                reason, message = self.show_manager.control_a_show(
                    show_ref, show_command)
            else:
                return
        elif show_command == 'exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            else:
                reason, message = self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1, self.e_shutdown_pressed)
            return
        else:
            reason = 'error'
            message = 'command not recognised: ' + show_command

        if reason == 'error':
            self.mon.err(self, message)
        return

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal', 'no shows running')

    def e_shutdown_pressed(self):
        self.shutdown_pressed('now')

    def e_osc_handle_output_event(self, line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg))

    def osc_handle_output_event(self, line):
        self.mon.log(self, "output event received: " + line)
        #osc sends output events as a string
        reason, message, delay, name, param_type, param_values = self.animate.parse_animate_fields(
            line)
        if reason == 'error':
            self.mon.err(self, message)
            self.end(reason, message)
        self.handle_output_event(name, param_type, param_values, 0)

    def handle_output_event(self, symbol, param_type, param_values, req_time):
        if self.gpio_enabled is True:
            reason, message = self.gpiodriver.handle_output_event(
                symbol, param_type, param_values, req_time)
            if reason == 'error':
                self.mon.err(self, message)
                self.end(reason, message)
        else:
            self.mon.warn(self, 'GPIO not enabled')

    # all input events call this callback with a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self, symbol, source):
        self.mon.log(self, "event received: " + symbol + ' from ' + source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()

        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')

        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1, self.e_shutdown_pressed)
            return

        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            reason, message = self.show_manager.exit_all_shows()
        else:
            # events for shows affect the show and could cause it to exit.
            for show in self.show_manager.shows:
                show_obj = show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)

    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000, self.on_shutdown_delay)
        else:
            self.shutdown_required = True
            if self.show_manager.all_shows_exited() is True:
                self.all_shows_ended_callback('normal', 'no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()

    def on_shutdown_delay(self):
        # 5 second delay is up, if shutdown button still pressed then shutdown
        if self.gpiodriver.shutdown_pressed() is True:
            self.shutdown_required = True
            if self.show_manager.all_shows_exited() is True:
                self.all_shows_ended_callback('normal', 'no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()

    def handle_sigterm(self, signum, frame):
        self.mon.log(self, 'SIGTERM received - ' + str(signum))
        self.terminate()

    def handle_user_abort(self):
        self.mon.log(self, 'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        needs_termination = False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination = True
                self.mon.log(
                    self,
                    "Sent terminate to show " + show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed', 'killed - no termination of shows required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

# callback from ShowManager when all shows have ended

    def all_shows_ended_callback(self, reason, message):
        self.canvas.config(bg=self.pp_background)
        if reason in (
                'killed', 'error'
        ) or self.shutdown_required is True or self.exitpipresents_required is True:
            self.end(reason, message)

    def end(self, reason, message):
        self.mon.log(self, "Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        # gc.collect()
        # print gc.garbage
        if reason == 'killed':
            self.mon.log(self, "Pi Presents Aborted, au revoir")
            # close logging files
            self.mon.finish()
            sys.exit(101)
        elif reason == 'error':
            self.mon.log(self, "Pi Presents closing because of error, sorry")
            # close logging files
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self, "Pi Presents  exiting normally, bye")
            # close logging files
            self.mon.finish()
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call(['sudo', 'shutdown', '-h', '-t 5', 'now'])
                sys.exit(100)
            else:
                sys.exit(100)

    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset", "s", "on"])
            call(["xset", "s", "+dpms"])

        # tidy up animation and gpio
        if self.animate is not None:
            self.animate.terminate()

        if self.gpio_enabled == True:
            self.gpiodriver.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()

        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()
Пример #9
0
class ShowManager(object):
    """
    ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them
    concurrent shows are always top level (level 0) shows:
    They can be opened/closed  by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC
    
   Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance.
   However a workaround is to make the secong instance a subshow of a mediashow with a different reference.

    """
    
    # Declare class variables
    shows=[]
    canvas=None    #canvas for all shows
    shutdown_required=False
    SHOW_TEMPLATE=['',None]
    SHOW_REF= 0   # show-reference  - name of the show as in editor
    SHOW_OBJ = 1   # the python object
    showlist=[]


    # Initialise class variables, first time through only in pipresents.py

    def init(self,canvas,all_shows_ended_callback,command_callback,showlist):
        ShowManager.all_shows_ended_callback=all_shows_ended_callback
        ShowManager.shows=[]
        ShowManager.shutdown_required=False
        ShowManager.canvas=canvas
        ShowManager.command_callback = command_callback
        ShowManager.showlist = showlist

# **************************************
# functions to manipulate show register
# **************************************

    def register_shows(self):
        for show in ShowManager.showlist.shows():
            if show['show-ref'] != 'start':
                reason,message=self.register_show(show['show-ref'])
                if reason =='error':
                    return reason,message
        return 'normal','shows regiistered'
            

    def register_show(self,ref):
        registered=self.show_registered(ref)
        if registered == -1:
            ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE))
            index=len(ShowManager.shows)-1
            ShowManager.shows[index][ShowManager.SHOW_REF]=ref
            ShowManager.shows[index][ShowManager.SHOW_OBJ]=None
            self.mon.trace(self,' - register show: show_ref = ' + ref + ' index = ' + str(index))
            return'normal','show registered'
        else:
            # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref )
            return 'error', ' more than one show in showlist with show-ref: ' + ref 
        
    # is the show registered?
    # can be used to return the index to the show
    def show_registered(self,show_ref):
        index=0
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_REF] == show_ref:
                return index
            index+=1
        return -1

    # needs calling program to check that the show is not already running
    def set_running(self,index,show_obj):
        ShowManager.shows[index][ShowManager.SHOW_OBJ]=show_obj
        self.mon.trace(self, 'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index))


    # is the show running?
    def show_running(self,index):
        if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None:
            return ShowManager.shows[index][ShowManager.SHOW_OBJ]
        else:
            return None

    def set_exited(self,index):
        ShowManager.shows[index][ShowManager.SHOW_OBJ]=None
        self.mon.trace(self,'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index))


    # are all shows exited?
    def all_shows_exited(self):
        all_exited=True
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_OBJ] is not None:
                all_exited=False
        return all_exited


    # fromat for printing
    def pretty_shows(self):
        shows='\n'
        for show in ShowManager.shows:
            shows += show[ShowManager.SHOW_REF] +'\n'
        return shows
            
# *********************************
# show control
# *********************************

    # show manager can be initialised by a player, shower or by pipresents.py
    # if by pipresents.py then show_id=-1
    def __init__(self,show_id,showlist,show_params,root,canvas,pp_dir,pp_profile,pp_home):
        self.show_id=show_id
        self.showlist=showlist
        self.show_params=show_params
        self.root=root
        self.show_canvas=canvas
        self.pp_dir=pp_dir
        self.pp_profile=pp_profile
        self.pp_home=pp_home


        self.mon=Monitor()

    def control_a_show(self,show_ref,show_command):
        if show_command == 'open':
            return self.start_show(show_ref)
        elif show_command  == 'close':
            return self.exit_show(show_ref)
        else:
            return 'error','command not recognised '+ show_command


    def exit_all_shows(self):
        for show in ShowManager.shows:
            self.exit_show(show[ShowManager.SHOW_REF])
        return 'normal','exited all shows'


    # kick off the exit sequence of a show by calling the shows exit method.
    # it will result in all the shows in a stack being closed and end_play_show being called
    def exit_show(self,show_ref):
        index=self.show_registered(show_ref)
        self.mon.log(self,"Exiting show "+ show_ref + ' show index:' + str(index))
        show_obj=self.show_running(index)
        if show_obj is not None:
            show_obj.exit()
        return 'normal','exited a concurrent show'
            

    def start_show(self,show_ref):
        index=self.show_registered(show_ref)
        if index <0:
            return 'error',"Show not found in showlist: "+ show_ref
        show_index = self.showlist.index_of_show(show_ref)
        show=self.showlist.show(show_index)
        reason,message,show_canvas=self.compute_show_canvas(show)
        if reason == 'error':
            return reason,message
        # print 'STARTING TOP LEVEL SHOW',show_canvas
        self.mon.log(self,'Starting Show: ' + show_ref  + ' from: ' + self.show_params['show-ref'])
        if self.show_running(index):
            self.mon.warn(self,"show already running so ignoring command: "+show_ref)
            return 'normal','this concurrent show already running'
        show_obj = self.init_show(index,show,show_canvas)
        if show_obj is None:
            return 'error',"unknown show type in start concurrent show - "+ show['type']
        else:
            self.set_running(index,show_obj)
            # params - end_callback, show_ready_callback, parent_kickback_signal, level
            show_obj.play(self._end_play_show,None,False,0,[])
            return 'normal','concurrent show started'

 
    # used by shows to create subshows or child shows
    def init_subshow(self,show_id,show,show_canvas):
        return self.init_show(show_id,show,show_canvas)



    def _end_play_show(self,index,reason,message):
        show_ref_to_exit =ShowManager.shows[index][ShowManager.SHOW_REF]
        show_to_exit =ShowManager.shows[index][ShowManager.SHOW_OBJ]
        self.mon.log(self,'Exited from show: ' + show_ref_to_exit + ' ' + str(index) )
        self.mon.log(self,'Exited with Reason = ' + reason)
        self.mon.trace(self,' Show is: ' + show_ref_to_exit + ' show index ' + str(index))
        # closes the video/audio from last track then closes the track
        # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player
        self.set_exited(index)
        if self.all_shows_exited() is True:
            ShowManager.all_shows_ended_callback(reason,message)
        return reason,message


    # common function to initilaise the show by type
    def init_show(self,show_id,selected_show,show_canvas,):
        if selected_show['type'] == "mediashow":
            return MediaShow(show_id,
                             selected_show,
                             self.root,
                             show_canvas,
                             self.showlist,
                             self.pp_dir,
                             self.pp_home,
                             self.pp_profile,
                             ShowManager.command_callback)

        elif selected_show['type'] == "liveshow":    
            return LiveShow(show_id,
                            selected_show,
                            self.root,
                            show_canvas,
                            self.showlist,
                            self.pp_dir,
                            self.pp_home,
                            self.pp_profile,
                            ShowManager.command_callback)


        elif selected_show['type'] == "radiobuttonshow":
            return RadioButtonShow(show_id,
                                   selected_show,
                                   self.root,
                                   show_canvas,
                                   self.showlist,
                                   self.pp_dir,
                                   self.pp_home,
                                   self.pp_profile,
                                   ShowManager.command_callback)

        elif selected_show['type'] == "hyperlinkshow":
            return HyperlinkShow(show_id,
                                 selected_show,
                                 self.root,
                                 show_canvas,
                                 self.showlist,
                                 self.pp_dir,
                                 self.pp_home,
                                 self.pp_profile,
                                 ShowManager.command_callback)
        
        elif selected_show['type'] == "menu":
            return MenuShow(show_id,
                            selected_show,
                            self.root,
                            show_canvas,
                            self.showlist,
                            self.pp_dir,
                            self.pp_home,
                            self.pp_profile,
                            ShowManager.command_callback)
        
        elif selected_show['type'] == "artmediashow":    
            return ArtMediaShow(show_id,
                                selected_show,
                                self.root,
                                show_canvas,
                                self.showlist,
                                self.pp_dir,
                                self.pp_home,
                                self.pp_profile,
                                ShowManager.command_callback)
        
        elif selected_show['type'] == "artliveshow":    
            return ArtLiveShow(show_id,
                               selected_show,
                               self.root,
                               show_canvas,
                               self.showlist,
                               self.pp_dir,
                               self.pp_home,
                               self.pp_profile,
                               ShowManager.command_callback)
        else:
            return None


    def compute_show_canvas(self,show_params):
        canvas={}
        canvas['canvas-obj']= ShowManager.canvas
        status,message,self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2,self.show_canvas_y2= self.parse_show_canvas(show_params['show-canvas'])
        if status  == 'error':
            # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas'])
            return 'error','show canvas error: ' + message + ' in ' + show_params['show-canvas'],canvas
        else:
            self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1
            self.show_canvas_height=self.show_canvas_y2 - self.show_canvas_y1
            self.show_canvas_centre_x= self.show_canvas_width/2
            self.show_canvas_centre_y= self.show_canvas_height/2
            canvas['show-canvas-x1'] = self.show_canvas_x1
            canvas['show-canvas-y1'] = self.show_canvas_y1
            canvas['show-canvas-x2'] = self.show_canvas_x2
            canvas['show-canvas-y2'] = self.show_canvas_y2
            canvas['show-canvas-width']  = self.show_canvas_width
            canvas['show-canvas-height'] = self.show_canvas_height
            canvas['show-canvas-centre-x'] = self.show_canvas_centre_x 
            canvas['show-canvas-centre-y'] = self.show_canvas_centre_y
            return 'normal','',canvas



    def parse_show_canvas(self,text):
        fields = text.split()
        # blank so show canvas is the whole screen
        if len(fields) < 1:
            return 'normal','',0,0,int(self.canvas['width']),int(self.canvas['height'])
             
        elif len(fields) == 4:
            # window is specified
            if not (fields[0].isdigit() and fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit()):
                return 'error','coordinates are not positive integers',0,0,0,0
            return 'normal','',int(fields[0]),int(fields[1]),int(fields[2]),int(fields[3])
        else:
            # error
            return 'error','illegal Show canvas dimensions '+ text,0,0,0,0
Пример #10
0
class Player(object):

    # common bits of __init__(...)
    def __init__(self,
                 show_id,
                 showlist,
                 root,
                 canvas,
                 show_params,
                 track_params,
                 pp_dir,
                 pp_home,
                 pp_profile,
                 end_callback,
                 command_callback):

        # create debugging log object
        self.mon=Monitor()

        self.mon.trace(self,'')

        # instantiate arguments
        self.show_id=show_id
        self.showlist=showlist
        self.root=root
        self.canvas=canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height= canvas['show-canvas-height']
        self.show_canvas_centre_x= canvas['show-canvas-centre-x']
        self.show_canvas_centre_y= canvas['show-canvas-centre-y']
        self.show_params=show_params
        self.track_params=track_params
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile
        self.end_callback=end_callback
        self.command_callback=command_callback
                
        # get background image from profile.
        self.background_file=''
        if self.track_params['background-image'] != '':
            self.background_file= self.track_params['background-image']

            
        # get background colour from profile.
        if self.track_params['background-colour'] != '':
            self.background_colour= self.track_params['background-colour']
        else:
            self.background_colour= self.show_params['background-colour']
        
        # get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim=PluginManager(self.show_id,self.root,self.canvas,self.show_params,self.track_params,self.pp_dir,self.pp_home,self.pp_profile) 

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj=None
        self.show_text_obj=None
        self.track_text_obj=None
        self.hint_obj=None
        self.background=None
        self.freeze_at_end_required='no' # overriden by videoplayer
        self.tick_timer=None
        self.terminate_signal=False
        self.play_state=''



    def pre_load(self):

        pass

              
    # common bits of show(....) 
    def pre_show(self):
        self.mon.trace(self,'')

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()
  
        #ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)


        # Control other shows and do counters and osc at beginning
        self.show_control(self.track_params['show-control-begin'])

        # and show whatever the plugin has created
        self.show_plugin()
        
        # create animation events
        reason,message=self.animate.animate(self.animate_begin_text,id(self))
        if reason  ==  'error':
            self.mon.err(self,message)
            self.play_state='show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error',message)
        else:
            # return to start playing the track.
            self.mon.log(self,">show track received from show Id: "+ str(self.show_id))
            return


    # to keep landscape happy
    def ready_callback(self,enable_show_background):
        self.mon.fatal(self,'ready callback not overridden')
        self.end('error','ready callback not overridden')

    def finished_callback(self,reason,message):
        self.mon.fatal(self,'finished callback not overridden')
        self.end('error','finished callback not overridden')

    def closed_callback(self,reason,message):
        self.mon.fatal(self,'closed callback not overridden')
        self.end('error','closed callback not overridden')


# Control shows so pass the show control commands back to PiPresents via the command callback
    def show_control(self,show_control_text): 
        lines = show_control_text.split('\n')
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line, source='track',show=self.show_params['show-ref'])



# *****************
# hide content and end animation, show control etc.
# called by ready calback and end
# *****************

    def hide(self):
        self.mon.trace(self,'')
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer=None
        
        self.hide_x_content()
        
        # stop the plugin
        self.hide_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params['show-control-end'])
        
        # clear events list for this track
        if self.track_params['animate-clear'] == 'yes':
            self.animate.clear_events_list(id(self))
                
        # create animation events for ending
        # !!!!! TEMPORARY FIX
        reason,message=self.animate.animate(self.animate_end_text,id(self))
        if reason == 'error':
            self.mon.err(self,message)
            # self.play_state='show-failed'
            # if self.finished_callback is not None:
                # self.finished_callback('error',message)
        else:
            return


    def terminate(self):
        self.mon.trace(self,'')
        self.terminate_signal=True
        if self.play_state == 'showing':
            # call the derived class's stop method
            self.stop()
        else:
            self.end('killed','terminate with no track or show open')

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self,'stop not overidden by derived class')
        self.play_state='show-failed'
        if self.finished_callback is not None:
            self.finished_callback('error','stop not overidden by derived class')


    def get_play_state(self):
        return self.play_state
  
# *****************
# ending the player
# *****************

    def end(self,reason,message):
        self.mon.trace(self,'')
        # stop the plugin

        if self.terminate_signal is True:
            reason='killed'
            self.terminate_signal=False
            self.hide()

        self.end_callback(reason,message)
        self=None


# *****************
# displaying common things
# *****************

    def load_plugin(self):
        # called in load before load_x_content modify the track here
        if self.track_params['plugin'] != '':
            reason,message,self.track = self.pim.load_plugin(self.track,self.track_params['plugin'])
            return reason,message
        
    def show_plugin(self):
        # called at show time, write to the track here if you need it after show_control_begin (counters)
        if self.track_params['plugin'] != '':
            self.pim.show_plugin()

    def hide_plugin(self):
        # called at the end of the track
        if self.track_params['plugin'] != '':
            self.pim.stop_plugin()

    def load_x_content(self,enable_menu):
        self.mon.trace(self,'')
        self.background_obj=None
        self.background=None
        self.track_text_obj=None
        self.show_text_obj=None
        self.hint_obj=None
        self.track_obj=None

        
        # background image
        if self.background_file != '':
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error',"Track background file not found "+ background_img_file
            else:
                try:
                    pil_background_img=Image.open(background_img_file)
                except:
                    pil_background_img=None
                    self.background=None
                    self.background_obj=None
                    return 'error','Track background, not a recognised image format '+ background_img_file                
                # print 'pil_background_img ',pil_background_img
                image_width,image_height=pil_background_img.size
                window_width=self.show_canvas_width
                window_height=self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img=pil_background_img.resize((window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(self.show_canvas_x1,
                                                               self.show_canvas_y1,
                                                               image=self.background,
                                                               anchor=NW)
                # print '\nloaded background_obj: ',self.background_obj


        # load the track content.  Dummy function below is overridden in players          
        status,message=self.load_track_content()
        if status == 'error':
            return 'error',message
                          
        # load show text if enabled
        if self.show_params['show-text'] !=  '' and self.track_params['display-show-text'] == 'yes':

            x,y,anchor,justify=calculate_text_position(self.show_params['show-text-x'],self.show_params['show-text-y'],
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,self.show_params['show-text-justify'])
 
            self.show_text_obj=self.canvas.create_text(x,y,
                                                       anchor=anchor,
                                                       justify=justify,
                                                       text=self.show_params['show-text'],
                                                       fill=self.show_params['show-text-colour'],
                                                       font=self.show_params['show-text-font'])


        # load track text if enabled

        if self.track_params['track-text-x'] =='':
            track_text_x= self.show_params['track-text-x']
        else:
            track_text_x= self.track_params['track-text-x']


        if self.track_params['track-text-y'] =='':
            track_text_y= self.show_params['track-text-y']
        else:
            track_text_y= self.track_params['track-text-y']

        if self.track_params['track-text-justify'] =='':
            track_text_justify= self.show_params['track-text-justify']
        else:
            track_text_justify= self.track_params['track-text-justify']

        if self.track_params['track-text-font'] =='':
            track_text_font= self.show_params['track-text-font']
        else:
            track_text_font= self.track_params['track-text-font']
            

        if self.track_params['track-text-colour'] =='':
            track_text_colour= self.show_params['track-text-colour']
        else:
            track_text_colour= self.track_params['track-text-colour']
            
            
        if self.track_params['track-text'] !=  '':

            x,y,anchor,justify=calculate_text_position(track_text_x,track_text_y,
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,track_text_justify)
 
            
            self.track_text_obj=self.canvas.create_text(x,y,
                                                        anchor=anchor,
                                                        justify=justify,
                                                        text=self.track_params['track-text'],
                                                        fill=track_text_colour,
                                                        font=track_text_font)

        # load instructions if enabled
        if enable_menu is  True:

            x,y,anchor,justify=calculate_text_position(self.show_params['hint-x'],self.show_params['hint-y'],
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,self.show_params['hint-justify'])
 

            self.hint_obj=self.canvas.create_text(x,y,
                                                  justify=justify,
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                  font=self.show_params['hint-font'],
                                                  anchor=anchor)

        self.display_show_canvas_rectangle()

        self.canvas.tag_raise('pp-click-area')
        self.canvas.itemconfig(self.background_obj,state='hidden')
        self.canvas.itemconfig(self.show_text_obj,state='hidden')
        self.canvas.itemconfig(self.track_text_obj,state='hidden')
        self.canvas.itemconfig(self.hint_obj,state='hidden')
        self.canvas.update_idletasks( )
        return 'normal','x-content loaded'


    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass


    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer
        
    def load_track_content(self):
        return 'normal','player has no track content to load'

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self,'')
        # background colour
        if  self.background_colour != '':
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj,state='normal')
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj,state='normal')
        self.canvas.itemconfig(self.track_text_obj,state='normal')
        self.canvas.itemconfig(self.hint_obj,state='normal')
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params['display-show-background']=='yes':
            self.enable_show_background=True
        else:
            self.enable_show_background=False
        # print 'ENABLE SB',self.enable_show_background


    def hide_x_content(self):
        self.mon.trace(self,'')
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj,state='hidden')
        self.canvas.itemconfig(self.show_text_obj,state='hidden')
        self.canvas.itemconfig(self.track_text_obj,state='hidden')
        self.canvas.itemconfig(self.hint_obj,state='hidden')
        # self.canvas.update_idletasks( )
        
        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background=None
        # self.canvas.update_idletasks( )


# ****************
# utilities
# *****************

    def get_links(self):
        return self.track_params['links']

    # produce an absolute path from the relative one in track paramters
    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file=self.pp_home+track_file[1:]
        elif track_file[0] == "@":
            track_file=self.pp_profile+track_file[1:]
        return track_file
        
    # get a text string from resources.cfg
    def resource(self,section,item):
        value=self.rr.get(section,item)
        return value # False if not found
Пример #11
0
class Show(object):


    # ******************************
    # init a show
    # ******************************

    def base__init__(self,
                     show_id,
                     show_params,
                     root,
                     canvas,
                     showlist,
                     pp_dir,
                     pp_home,
                     pp_profile,
                     command_callback):

        # instantiate arguments
        self.show_id=show_id
        self.show_params=show_params
        self.root=root
        self.show_canvas=canvas
        self.canvas=canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height= canvas['show-canvas-height']
        self.show_canvas_centre_x= canvas['show-canvas-centre-x']
        self.show_canvas_centre_y= canvas['show-canvas-centre-y']
        self.showlist=showlist
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile
        self.command_callback=command_callback

        # init things that will then be reinitialised by derived classes
        self.medialist=None

        # set up logging 
        self.mon=Monitor()
        self.mon.set_log_level(16)


        # create and instance of TimeOfDay scheduler so we can add events
        self.tod=TimeOfDay()
        
        # create an  instance of showmanager so we can init child/subshows
        self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.show_canvas,self.pp_dir,self.pp_profile,self.pp_home)


        # init variables
        self.current_player=None
        self.previous_player=None
        self.shower=None
        self.previous_shower=None
        self.user_stop_signal= False
        self.exit_signal=False
        self.terminate_signal=False
        self.show_timeout_signal=False
        self.egg_timer=None
        self.admin_message=None
        self.ending_reason=''
        self.background_obj=None
        self.background_file=''
        self.level=0
        self.subshow_kickback_signal=False
        self.kickback_for_next_track=False
        
        # get background image from profile.
        # print 'background', self.show_params['background-image']
        if self.show_params['background-image'] != '':
            self.background_file= self.show_params['background-image']

    def base_play(self,end_callback,show_ready_callback, parent_kickback_signal,level,controls_list):

        """ starts the common parts of the show
              end_callback - function to be called when the show exits- callback gets last player of subshow
              show_ready_callback - callback at start to get previous_player
              top is True when the show is top level (run from [start] or by show command from another show)
              direction_command - 'forward' or 'backward' direction to play a subshow
        """
        # instantiate the arguments
        self.end_callback=end_callback
        self.show_ready_callback=show_ready_callback
        self.parent_kickback_signal=parent_kickback_signal
        self.level=level
        # not needed as controls list is not passed down to subshows.
        # self.controls_list=controls_list
        self.mon.trace(self,self.show_params['show-ref'] + ' at level ' + str(self.level))
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Starting show")

        # check  data files are available.
        if self.show_params['medialist'] == '':
            self.mon.err(self,"Blank Medialist in : "+ self.show_params['title'])
            self.end('error',"Blank Medialist")
        self.medialst_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.medialst_file):
            self.mon.err(self,"Medialist file not found: "+ self.medialst_file)
            self.end('error',"Medialist file not found")

        # read the medialist for the show
        if self.medialist.open_list(self.medialst_file,self.showlist.sissue()) is False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self.end('error',"Version of medialist different to Pi Presents")

        if self.show_ready_callback is not None:
            # get the previous player from calling show its stored in current because its going to be shuffled before use
            self.previous_shower, self.current_player=self.show_ready_callback()
            self.mon.trace(self,' - previous shower and player is ' + self.mon.pretty_inst(self.previous_shower)+ ' ' + self.mon.pretty_inst(self.current_player))

        #load the show background
        reason,message=Show.base_load_show_background(self)
        if reason=='error':
            self.mon.err(self,message)
            self.end('error',message)

    # dummy, must be overidden by derived class
    def subshow_ready_callback(self):
        self.mon.err(self,"subshow_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason='killed'
        Show.base_close_or_unload(self)


    def base_subshow_ready_callback(self):
        # callback from begining of a subshow, provide previous player to called show
        # used by show_ready_callback of called show
        # in the case of a menushow last track is always the menu
        self.mon.trace(self,' -  sends ' + self.mon.pretty_inst(self.previous_player))
        return self,self.previous_player


    def base_shuffle(self):
        self.previous_player=self.current_player
        self.current_player = None
        self.mon.trace(self,' - LOOP STARTS WITH current is: ' + self.mon.pretty_inst(self.current_player))
        self.mon.trace(self,'       -  previous is: ' + self.mon.pretty_inst(self.previous_player))



    def base_load_track_or_show(self,selected_track,loaded_callback,end_shower_callback,enable_menu):
        track_type=selected_track['type']
        if track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index <0:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self.end('error','show not in showlist')
            else:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
                self.shower=self.show_manager.init_subshow(self.show_id,selected_show,self.show_canvas)
                self.mon.trace(self,' - show is: ' + self.mon.pretty_inst(self.shower) + ' ' + selected_show['show-ref'])
                if self.shower is None:
                    self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                    self.terminate_signal=True
                    self.what_next_after_showing()
                else:
                    # empty controls list as not used, pleases landscape
                    # print 'send direction to subshow from show',self.kickback_for_next_track
                    # Show.base_withdraw_show_background(self)
                    self.shower.play(end_shower_callback,self.subshow_ready_callback,self.kickback_for_next_track,self.level+1,[])
        else:
            # dispatch track by type
            self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Track type is: "+ track_type)
            
            self.current_player=self.base_init_selected_player(selected_track)
            #menu has no track file
            if selected_track['type']=='menu':
                track_file=''
                
            # messageplayer passes the text not a file name
            elif selected_track['type'] == 'message':
                track_file=selected_track['text']
            else:
                track_file=self.base_complete_path(selected_track['location'])
                
            self.mon.trace(self,' - track is: ' + track_file)
            self.mon.trace(self,' - current_player is: '+ self.mon.pretty_inst(self.current_player))
            self.current_player.load(track_file,
                                     loaded_callback,
                                     enable_menu=enable_menu)

    # DUMMY, must be overidden by derived class
    def what_next_after_showing(self):
        self.mon.err(self,"what_next_after showing not overidden")
        # set what to do when closed or unloaded
        self.ending_reason='killed'
        Show.base_close_or_unload(self)


    def base_init_selected_player(self,selected_track):
        # dispatch track by type
        track_type=selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
                                      
        if track_type == "image":
            return ImagePlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                               self.show_params,selected_track,self.pp_dir,self.pp_home,
                               self.pp_profile,self.end,self.command_callback)
    
        elif track_type == "video":
            return VideoPlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                               self.show_params,selected_track,self.pp_dir,self.pp_home,
                               self.pp_profile,self.end,self.command_callback)

        elif track_type == "audio":
            return AudioPlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                               self.show_params,selected_track,self.pp_dir,self.pp_home,
                               self.pp_profile,self.end,self.command_callback)

        elif track_type == "web" and self.show_params['type'] not in ('artmediashow','artliveshow'):
            return BrowserPlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                                 self.show_params,selected_track,self.pp_dir,self.pp_home,
                                 self.pp_profile,self.end,self.command_callback)
  
        elif track_type == "message":
            return MessagePlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                                 self.show_params,selected_track,self.pp_dir,self.pp_home,
                                 self.pp_profile,self.end,self.command_callback)

        elif track_type == "menu":
            return MenuPlayer(self.show_id,self.showlist,self.root,self.show_canvas,
                              self.show_params,selected_track,self.pp_dir,self.pp_home,
                              self.pp_profile,self.end,self.command_callback)
                                   
        else:
            return None


    # DUMMY, must be overidden by derived class
    def track_ready_callback(self,track_background):
        self.mon.err(self,"track_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason='killed'
        Show.base_close_or_unload(self)


    # called just before a track is shown to remove the  previous track from the screen
    # and if necessary close it        
    def base_track_ready_callback(self,enable_show_background):
        self.mon.trace(self,'')
        # show the show background done for every track but quick operation
        if enable_show_background is True:
            self.base_show_show_background()
        else:
            self.base_withdraw_show_background()
        # !!!!!!!!! withdraw the background from the parent show
        if self.previous_shower != None:
            self.previous_shower.base_withdraw_show_background()
        # close the player from the previous track
        if self.previous_player is not  None:
            self.mon.trace(self,' - hiding previous: ' + self.mon.pretty_inst(self.previous_player))
            self.previous_player.hide()
            # print 'Not None  - previous state is',self.previous_player.get_play_state()
            if self.previous_player.get_play_state() == 'showing':
                # print 'showing so closing previous'
                # showing or frozen
                self.mon.trace(self,' - closing previous: ' + self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_closed_callback_previous)
            else:
                self.mon.trace(self,' - previous is none\n')
                self.previous_player=None
        self.canvas.update_idletasks( )


    def _base_closed_callback_previous(self,status,message):
        self.mon.trace(self,' -  previous is None  - was: ' + self.mon.pretty_inst(self.previous_player))
        self.previous_player=None


    # used by end_shower to get the last track of the subshow
    def base_end_shower(self):
        self.mon.trace(self,' -  returned back to level: ' +str(self.level))
        # get the previous subshow and last track it played 
        self.previous_shower,self.current_player=self.shower.base_subshow_ended_callback()
        if self.previous_shower!= None:
            self.subshow_kickback_signal=self.shower.subshow_kickback_signal
            # print 'get subshow kickback from subshow',self.subshow_kickback_signal
            self.previous_shower.base_withdraw_show_background()
            self.base_show_show_background()
        self.previous_player=None
        self.mon.trace(self,'- get previous_player from subshow: ' + self.mon.pretty_inst(self.current_player))
        self.shower=None


    # close or unload the current player when ending the show
    def base_close_or_unload(self):
        self.mon.trace(self,self.mon.pretty_inst(self.current_player))
        # need to test for None because player may be made None by subshow lower down the stack for terminate
        if self.current_player is not None:
            self.mon.trace(self,self.current_player.get_play_state())
            if self.current_player.get_play_state() in ('loaded','showing','show-failed'):
                if self.current_player.get_play_state() == 'loaded':
                    self.mon.trace(self,' - unloading current from: '+self.ending_reason)
                    self.current_player.unload()
                else:
                    self.mon.trace(self,' - closing current from: '  + self.ending_reason)
                    self.current_player.close(None)
            self._wait_for_end()
        else:
            # current_player is None because closed further down show stack
            self.mon.trace(self,' - show ended with current_player=None because: ' + self.ending_reason)

            # if exiting pipresents then need to close previous show else get memotry leak
            # if not exiting pipresents the keep previous so it can be closed when showing the next track
            # print 'CURRENT PLAYER IS NONE' ,self.ending_reason
            if self.ending_reason == 'killed':
                self.base_close_previous()

            elif self.ending_reason == 'error':
                self.base_close_previous()

            elif self.ending_reason == 'exit':
                self.end('normal',"show quit by exit command")

            elif self.ending_reason == 'user-stop':
                self.end('normal',"show quit by stop operation")
                    
            else:
                self.mon.fatal(self,"Unhandled ending_reason: ")
                self.end('error',"Unhandled ending_reason")          


    def _base_closed_callback_current(self,status,message):
        self.mon.trace(self,' current is None  - was: ' + self.mon.pretty_inst(self.current_player))


    # wait for unloading or closing to complete then end
    def _wait_for_end(self):
        self.mon.trace(self, self.mon.pretty_inst(self.current_player))
        if self.current_player is not None:
            self.mon.trace(self,' - play state is ' +self.current_player.get_play_state())
            if self.current_player.play_state not in ('unloaded','closed','load-failed'):  ####
                self.canvas.after(50,self._wait_for_end)
            else:
                self.mon.trace(self,' - current closed '+ self.mon.pretty_inst(self.current_player) + ' ' + self.ending_reason)

                 #why is some of thsi different to close and unload????????????? perhaps because current_player isn't none, just closed
                if self.ending_reason == 'killed':
                    self.current_player.hide()
                    self.current_player=None
                    self.base_close_previous()

                elif self.ending_reason == 'error':
                    self.current_player.hide()
                    self.current_player=None
                    self.base_close_previous()

                elif self.ending_reason == 'exit':
                    self.current_player.hide()
                    self.current_player=None
                    self.base_close_previous()

                elif self.ending_reason == 'change-medialist':
                    self.current_player.hide()
                    self.current_player=None
                    # self.base_close_previous()
                    # go to start of list via wait for trigger.
                    self.wait_for_trigger()
                    
                elif self.ending_reason == 'show-timeout':
                    self.current_player.hide()
                    self.current_player=None
                    self.end('normal',"show timeout")
                    
                elif self.ending_reason == 'user-stop':
                    if self.level !=0:
                        self.end('normal',"show quit by stop operation")
                    else:
                        self.current_player.hide()
                        self.current_player=None
                        self.base_close_previous()
                        
                    
                else:
                    self.mon.fatal(self,"Unhandled ending_reason: " + self.ending_reason)
                    self.end('error',"Unhandled ending_reason")
        else:
            self.mon.trace(self,' - current is None ' +  self.mon.pretty_inst(self.current_player) + ' ' + self.ending_reason)


# ***************************
# end of show 
# ***************************

    # dummy, normally overidden by derived class
    def end(self,reason,message):
        self.mon.err(self,"end not overidden")
        self.base_end('error',message)

    def base_end(self,reason,message):
        self.base_withdraw_show_background()
        self.base_delete_show_background()
        self.mon.trace(self,' at level ' + str(self.level) + '\n - Current is ' + self.mon.pretty_inst(self.current_player) + '\n - Previous is ' + self.mon.pretty_inst(self.previous_player) + '\n with reason' + reason + '\n\n')
        self.mon.log(self,self.show_params['show-ref']+ ' Show Id: '+ str(self.show_id)+ ": Ending Show")
        self.end_callback(self.show_id,reason,message)
        self=None


    def base_subshow_ended_callback(self):
        # called by end_shower of a parent show  to get the last track of the subshow and the subshow
        self.mon.trace(self,' -  returns ' + self.mon.pretty_inst(self.current_player))
        return self, self.current_player


# ********************************
# Respond to external events
# ********************************


    def base_close_previous(self):
        self.mon.trace(self,'')
        # close the player from the previous track
        if self.previous_player is not  None:
            self.mon.trace(self,' - previous not None ' + self.mon.pretty_inst(self.previous_player))
            if self.previous_player.get_play_state() == 'showing':
                # showing or frozen
                self.mon.trace(self,' - closing previous ' + self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_close_previous_callback)
            else:
                self.mon.trace(self,'previous is not showing')
                self.previous_player.hide()
                self.previous_player=None
                self.end(self.ending_reason,'')
        else:
            self.mon.trace(self,' - previous is None')
            self.end(self.ending_reason,'')
            
                

    def _base_close_previous_callback(self,status,message):
        self.mon.trace(self, ' -  previous is None  - was ' + self.mon.pretty_inst(self.previous_player))
        self.previous_player.hide()
        self.previous_player=None
        self.end(self.ending_reason,'')


    # exit received from external source
    def base_exit(self):
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Exit received")
        self.mon.trace(self,'')
        # set signal to exit the show when all  sub-shows and players have ended
        self.exit_signal=True
        # then stop subshow or tracks.
        if self.shower is not None:
            self.shower.exit()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal','exit by ShowManager')

    # show timeout callback received
    def base_show_timeout_stop(self):
        self.mon.trace(self,'')
        # set signal to exit the show when all  sub-shows and players have ended
        self.show_timeout_signal=True
        # then stop and shows or tracks.
        if self.shower is not None:
            self.shower.show_timeout_stop()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal','stopped by Show Timeout')


    # dummy, normally overidden by derived class
    def terminate(self):
        self.mon.err(self,"terminate not overidden")
        self.base_end('error',"terminate not overidden")

    # terminate Pi Presents
    def base_terminate(self):
        self.mon.trace(self,'')
        # set signal to stop the show when all  sub-shows and players have ended
        self.terminate_signal=True
        if self.shower is not None:
            self.shower.terminate()
        elif self.current_player is not None:
            self.ending_reason='killed'
            Show.base_close_or_unload(self)
        else:
            self.end('killed',' terminated with no shower or player to terminate')


  # respond to input events
    def base_handle_input_event(self,symbol):
        self.mon.log(self, self.show_params['show-ref']+ ' Show Id: '+ str(self.show_id)+": received input event: " + symbol)

        if self.shower is not None:
            self.shower.handle_input_event(symbol)
        else:
            self.handle_input_event_this_show(symbol)

    #dummy must be overridden in derived class
    def handle_input_event_this_show(self,symbol):
        self.mon.err(self,"input_pressed_this_show not overidden")
        self.ending_reason='killed'
        Show.base_close_or_unload(self)
            
    def base_load_show_background(self):
        # load show background image
        if self.background_file != '':
            background_img_file = self.base_complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error',"Show background file not found "+ background_img_file
            else:
                pil_background_img=Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width,image_height=pil_background_img.size
                window_width=self.show_canvas_width
                window_height=self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img=pil_background_img.resize((window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                # print 'self.background ',self.background
                self.background_obj = self.canvas.create_image(self.show_canvas_x1,
                                                               self.show_canvas_y1,
                                                               image=self.background,
                                                               anchor=NW)
                self.canvas.itemconfig(self.background_obj,state='hidden')
                self.canvas.update_idletasks( )
                # print '\nloaded background_obj: ',self.background_obj
                return 'normal','show background loaded'
        else:
            return 'normal','no backgound to load'
              
    def base_show_show_background(self):
        if self.background_obj is not None:
            # print 'show show background'
            self.canvas.itemconfig(self.background_obj,state='normal')
            # self.canvas.update_idletasks( )    

    def base_withdraw_show_background(self):
        self.mon.trace(self,'')
        if self.background_obj is not None:
            # print 'withdraw background obj', self.background_obj
            self.canvas.itemconfig(self.background_obj,state='hidden')
            # self.canvas.update_idletasks( )


    def base_delete_show_background(self):
        if self.background_obj is not None:
            # print 'delete background obj'
            self.canvas.delete(self.background_obj)
            self.background=None
            # self.canvas.update_idletasks( )


# ******************************
# write statiscics
# *********************************
    def write_stats(self,command,show_params,next_track):
            # action, this ref, this name, type, ref, name, location
            if next_track['type']=='show':
                # get the show from the showlist
                index = self.showlist.index_of_show(next_track['sub-show'])
                if index <0:
                    self.mon.err(self,"Show not found in showlist: "+ next_track['sub-show'])
                    self.end('error','show not in showlist')
                else:
                    target=self.showlist.show(index)
                    ref=target['show-ref']
                    title=target['title']
                    track_type=target['type']
            else:
                # its a track
                ref= next_track['track-ref']
                title=next_track['title']
                track_type=next_track['type']
            if next_track['type'] in ('show','message'):
                loc = ''
            else:
                loc = next_track['location']                 
            self.mon.stats(show_params['type'],show_params['show-ref'],show_params['title'],command,
                            track_type,ref,title,loc)
            
 


# ******************************
# lookup controls 
# *********************************
    def base_lookup_control(self,symbol,controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        # not found so must be a trigger
        return ''

# ******************************
# Eggtimer
# *********************************
        
    def display_eggtimer(self):
        text=self.show_params['eggtimer-text']
        if text != '':
            self.egg_timer=self.canvas.create_text(int(self.show_params['eggtimer-x'])+ self.show_canvas_x1,
                                                   int(self.show_params['eggtimer-y']) + self.show_canvas_y1,
                                                   text= text,
                                                   fill=self.show_params['eggtimer-colour'],
                                                   font=self.show_params['eggtimer-font'],
                                                   anchor='nw')
            
            self.canvas.update_idletasks( )


    def delete_eggtimer(self):
        if self.egg_timer is not None:
            self.canvas.delete(self.egg_timer)
            self.egg_timer=None
            self.canvas.update_idletasks( )



# ******************************
# Display Admin Messages
# *********************************

    def display_admin_message(self,text):

        self.admin_message=self.canvas.create_text(int(self.show_params['admin-x']) + self.show_canvas_x1,
                                                   int(self.show_params['admin-y'])+self.show_canvas_y1,
                                                   text= text,
                                                   fill=self.show_params['admin-colour'],
                                                   font=self.show_params['admin-font'],
                                                   anchor='nw')
            
        self.canvas.update_idletasks( )


    def delete_admin_message(self):
        if self.admin_message is not None:
            self.canvas.delete(self.admin_message)
            self.canvas.update_idletasks( )


# ******************************
# utilities
# ******************************        



    def base_complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file != '' and track_file[0]=="+":
            track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to load is: "+ track_file)
        return track_file     
  

    def calculate_duration(self,line):
        fields=line.split(':')
        if len(fields)==1:
            secs=fields[0]
            minutes='0'
            hours='0'
        if len(fields)==2:
            secs=fields[1]
            minutes=fields[0]
            hours='0'
        if len(fields)==3:
            secs=fields[2]
            minutes=fields[1]
            hours=fields[0]
        if not secs.isdigit() or not minutes.isdigit() or not hours.isdigit():
            return 'error','bad time',0
        else:
            return 'normal','',3600*long(hours)+60*long(minutes)+long(secs)
Пример #12
0
class OMXDriver(object):
    _LAUNCH_CMD = '/usr/bin/omxplayer --no-keys '  # needs changing if user has installed his own version of omxplayer elsewhere
    KEY_MAP =   { '-': 17, '+': 18, '=': 18} # add more keys here, see popcornmix/omxplayer github

    def __init__(self,widget,pp_dir):

        self.widget=widget
        self.pp_dir=pp_dir
        
        self.mon=Monitor()

        self.start_play_signal=False
        self.end_play_signal=False
        self.end_play_reason='nothing'
        self.duration=0
        self.video_position=0
        
        self.pause_at_end_required=False
        self.paused_at_end=False
        
        self.pause_before_play_required=True
        self.paused_at_start=False

        self.paused=False      

        self.terminate_reason=''

        #  dbus and subprocess
        self._process=None
        self.__iface_root=None
        self.__iface_props = None
        self.__iface_player = None

        # legacy
        self.xbefore=''
        self.xafter=''



    def load(self, track, options,caller):
        self.caller=caller
        track= "'"+ track.replace("'","'\\''") + "'"

        self.dbus_user = os.environ["USER"]

        self.dbus_name = "org.mpris.MediaPlayer2.omxplayer"+str(int(time()*10))
        
        self.omxplayer_cmd = OMXDriver._LAUNCH_CMD + options + " --dbus_name '"+ self.dbus_name + "' " + track
        # print self.dbus_user, self.dbus_name
        # print self.omxplayer_cmd
        self.mon.log(self, "Send command to omxplayer: "+ self.omxplayer_cmd)
        self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/dev/null','a'),stderr=file('/dev/null','a'))
        # stdout=file('/dev/null','a'),stderr=file('/dev/null','a')
        self.pid=self._process.pid

        # wait for omxplayer to start
        tries=0
        while tries<400:
            # print 'trying',tries
            success=self.__dbus_connect()
            tries+=1
            sleep (0.1)
            if success is True: break
            
        if success is False:
            self.end_play_signal=True
            self.end_play_reason='Failed to connect to omxplayer DBus'
            return

        self.mon.log(self,'connected to omxplayer dbus ' + str(success))
            
        self.start_play_signal = True
        if self.pause_before_play_required is True and self.paused_at_start is False:
            self.pause('after load')
            self.paused_at_start=True

        # get duration of the track in microsecs
        success,duration=self.get_duration()
        if success is False:
            self.duration=0
            self.end_play_signal=True
            self.end_play_reason='Failed to read duration - Not connected to omxplayer DBus'
        else:
            self.duration=duration-350000
            
            # start the thread that is going to monitor output from omxplayer.
            self._position_thread = Thread(target=self._monitor_status)
            self._position_thread.start()


    def _monitor_status(self):
        
        while True:
            retcode=self._process.poll()

            # print 'in loop', self.pid, retcode
            if retcode is not None:
                # print 'process not running', retcode, self.pid
                # process is not running because quit or natural end
                self.end_play_signal=True
                self.end_play_reason='nice_day'
                break
            else:
                # test position ony when prsecc is running, could have race condition
                if self.paused_at_end is False:
                    # test position only when not paused for the end, in case process is dead
                    success, video_position = self.get_position()
                    if success is False:
                        pass # failure can happen because track has ended and process ended. Don't change video position
                    else:
                        self.video_position=video_position
                        # if timestamp is near the end then pause
                        if self.pause_at_end_required is True and self.video_position>self.duration:    #microseconds
                            self.pause(' at end of track')
                            self.paused_at_end=True
                            self.end_play_signal=True
                            self.end_play_reason='pause_at_end'
            sleep(0.05)                        


    
    def show(self,freeze_at_end_required):
        self.pause_at_end_required=freeze_at_end_required
        # unpause to start playing
        self.unpause(' to start showing')

        
    def control(self,char):

        val = OMXDriver.KEY_MAP[char]
        self.mon.log(self,'>control received and sent to omxplayer ' + str(self.pid))
        if self.is_running():
            try:
                self.__iface_player.Action(dbus.Int32(val))
            except dbus.exceptions.DBusException:
                self.mon.warn(self,'Failed to send control - dbus exception')
                return
        else:
            self.mon.warn(self,'Failed to send control - process not running')
            return
        

    def pause(self,reason):
        self.mon.log(self,'pause received '+reason)
        if not self.paused:
            self.paused = True
            self.mon.log(self,'not paused so send pause '+reason)
            if self.is_running():
                try:
                    self.__iface_player.Pause()
                except dbus.exceptions.DBusException:
                    self.mon.warn(self,'Failed to send pause - dbus exception')
                    return
            else:
                self.mon.warn(self,'Failed to send pause - process not running')
                return



    def toggle_pause(self,reason):
        self.mon.log(self,'toggle pause received '+ reason)
        if not self.paused:
            self.paused = True
        else:
            self.paused=False
        if self.is_running():
            try:
                self.__iface_player.Pause()
            except dbus.exceptions.DBusException:
                self.mon.warn(self,'Failed to toggle pause - dbus exception')
                return
        else:
            self.mon.warn(self,'Failed to toggle pause - process not running')
            return



    def unpause(self,reason):
        self.mon.log(self,'Unpause received '+ reason)
        if self.paused:
            self.paused = False
            self.mon.log(self,'Is paused so Track unpaused '+ reason)
            if self.is_running():
                try:
                    self.__iface_player.Pause()
                except dbus.exceptions.DBusException:
                    self.mon.warn(self,'Failed to send pause - dbus exception')
                    return
            else:
                self.mon.warn(self,'Failed to send pause - process not running')
                return


    def stop(self):
        self.mon.log(self,'>stop received and quit sent to omxplayer ' + str(self.pid))
        if self.is_running():
            try:
                self.__iface_root.Quit()
            except dbus.exceptions.DBusException:
                self.mon.warn(self,'Failed to quit - dbus exception')
                return
        else:
            self.mon.warn(self,'Failed to quit - process not running')
            return


    # kill the subprocess (omxplayer and omxplayer.bin). Used for tidy up on exit.
    def terminate(self,reason):
        self.terminate_reason=reason
        self.stop()
        
    def get_terminate_reason(self):
        return self.terminate_reason
    

   # test of whether _process is running
    def is_running(self):
        retcode=self._process.poll()
        # print 'is alive', retcode
        if retcode is None:
            return True
        else:
            return False



    # kill off omxplayer when it hasn't terminated at the end of a track.
    # send SIGINT (CTRL C) so it has a chance to tidy up daemons and omxplayer.bin
    def kill(self):
        if self.is_running():
            self._process.send_signal(signal.SIGINT)


    def get_position(self):
        """Return the current position"""
        if self.is_running():
            try:
                micros = self.__iface_props.Position()
                return True,micros
            except dbus.exceptions.DBusException:
                 return False,-1               
        else:
            return False,-1

    def get_duration(self):
        """Return the total length of the playing media"""
        if self.is_running():
            try:
                micros = self.__iface_props.Duration()
                return True,micros
            except dbus.exceptions.DBusException:
                return False,-1
        else:
            return False,-1




    # *********************
    # connect to dbus
    # *********************
    def __dbus_connect(self):
        # read the omxplayer dbus data from files generated by omxplayer
        bus_address_filename = "/tmp/omxplayerdbus.{}".format(self.dbus_user)
        bus_pid_filename = "/tmp/omxplayerdbus.{}.pid".format(self.dbus_user)
        try:
            with open(bus_address_filename, "r") as f:
                bus_address = f.read().rstrip()
            with open(bus_pid_filename, "r") as f:
                bus_pid = f.read().rstrip()
        except IOError:
            self.mon.trace(self,"Waiting for D-Bus session for user {}. Is omxplayer running under this user?".format(self.dbus_user))
            return False

        # looks like this saves some file opens as vars are used instead of files above
        os.environ["DBUS_SESSION_BUS_ADDRESS"] = bus_address
        os.environ["DBUS_SESSION_BUS_PID"] = bus_pid
        session_bus = dbus.SessionBus()
        try:
            omx_object = session_bus.get_object(self.dbus_name, "/org/mpris/MediaPlayer2", introspect=False)
            self.__iface_root = dbus.Interface(omx_object, "org.mpris.MediaPlayer2")
            self.__iface_props = dbus.Interface(omx_object, "org.freedesktop.DBus.Properties")
            self.__iface_player = dbus.Interface(omx_object, "org.mpris.MediaPlayer2.Player")
        except dbus.exceptions.DBusException as ex:
            self.mon.trace(self,"Waiting for dbus connection to omxplayer: {}".format(ex.get_dbus_message()))
            return False
        return True
Пример #13
-1
class PiPresents(object):

    def pipresents_version(self):
        vitems=self.pipresents_issue.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])


    def __init__(self):
        # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3.5"
        self.pipresents_minorissue = '1.3.5d'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner


        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit(102)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'GapShow','Show','ArtShow',
                            'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player',
                            'MediaList','LiveList','ShowList',
                            'PathManager','ControlsManager','ShowManager','PluginManager','IOPluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'TimeOfDay','ScreenDriver','Animate','OSCDriver','CounterManager',
                            'Network','Mailer'
                            ]
        

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        # Monitor.classes=['OSCDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched (self,None, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as ifile:
            self.mon.log(self,'\nRaspbian: '+ifile.read())

        self.mon.log(self,'\n'+check_output(["omxplayer", "-v"]))
        self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"]))

        if os.geteuid() == 0:
            print 'Do not run Pi Presents with sudo'
            self.mon.log(self,'Do not run Pi Presents with sudo')
            self.mon.finish()
            sys.exit(102)

        
        if "DESKTOP_SESSION" not in os.environ:
            print 'Pi Presents must be run from the Desktop'
            self.mon.log(self,'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION'])
        
        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.ioplugin_manager=None
        self.oscdriver=None
        self.osc_enabled=False
        self.tod_enabled=False
        self.email_enabled=False
        
        user=os.getenv('USER')

        if user is None:
            tkMessageBox.showwarning("You must be logged in to run Pi Presents")
            exit(102)

        if user !='pi':
            self.mon.warn(self,"You must be logged as pi to use GPIO")

        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work



        # check network is available
        self.network_connected=False
        self.network_details=False
        self.interface=''
        self.ip=''
        self.unit=''
        
        # sets self.network_connected and self.network_details
        self.init_network()

        
        # start the mailer and send email when PP starts
        self.email_enabled=False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M")
                message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + '   Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip 
                self.send_email('start',subject,message) 

         
        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.mon.err(self,"Profile not specified in command ")
            self.end('error','Profile not specified with the commands -p option')
        
       # get directory containing pp_home from the command,
        if self.options['home']  == "":
            home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)


        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found is True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self,"Failed to find pp_home directory at " + home)
            self.end('error',"Failed to find pp_home directory at " + home)


        # check profile exists
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self,None,"Running profile: " + self.pp_profile_path)
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error',"Failed to find requested profile: "+ self.pp_profile)

        self.mon.start_stats(self.options['profile'])
        
        if self.options['verify'] is True:
            self.mon.err(self,"Validation option not supported - use the editor")
            self.end('error','Validation option not supported - use the editor')

         
        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error',"showlist not found at "+self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")
            self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")


        # get the 'start' show from the showlist
        index = self.showlist.index_of_start_show()
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error',"Show [start] not found in showlist")


# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.starter_show['background-colour'])

        self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvas cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.starter_show['background-colour'])


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE TOUCHSCREEN DRIVER
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find, or error in screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.reboot_required=False
        self.terminate_required=False
        self.exitpipresents_required=False

        # initialise the I/O plugins by importing their drivers
        self.ioplugin_manager=IOPluginManager()
        reason,message=self.ioplugin_manager.init(self.pp_dir,self.pp_profile,self.root,self.handle_input_event)
        if reason == 'error':
            # self.mon.err(self,message)
            self.end('error',message)

        
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
                self.oscdriver=OSCDriver()
                reason,message=self.oscdriver.init(self.pp_profile,
                                                   self.unit,self.interface,self.ip,
                                                   self.handle_command,self.handle_input_event,self.e_osc_handle_animate)
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end('error',message)
                else:
                    self.osc_enabled=True
                    self.root.after(1000,self.oscdriver.start_server())

        
        # initialise ToD scheduler calculating schedule for today
        self.tod=TimeOfDay()
        reason,message,self.tod_enabled = self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.showlist,self.root,self.handle_command)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)
            
        # warn if the network not available when ToD required
        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(self,'Network not connected  so Time of Day scheduler may be using the internal clock')

        # init the counter manager
        self.counter_manager=CounterManager()
        self.counter_manager.init()


        # warn about start shows and scheduler

        if self.starter_show['start-show']=='' and self.tod_enabled is False:
            self.mon.sched(self,None,"No Start Shows in Start Show and no shows scheduled") 
            self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] !='' and self.tod_enabled is True:
            self.mon.sched(self,None,"Start Shows in Start Show and shows scheduled - conflict?") 
            self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()           

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod.poll()

        # start the I/O plugins input event generation
        self.ioplugin_manager.start()


        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --screensize comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in --screensize',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************

    def e_osc_handle_animate(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_animate(arg))

    def osc_handle_animate(self,line):
        self.mon.log(self,"animate command received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

    # output events are animate commands       
    def handle_output_event(self,symbol,param_type,param_values,req_time):
            reason,message=self.ioplugin_manager.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)



    # all input events call this callback providing a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.mon.err(self,'pp-shutdown removed in version 1.3.3a, see Release Notes')
            self.end('error','pp-shutdown removed in version 1.3.3a, see Release Notes')

            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.shutdownnow_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # pass the input event to all registered shows
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    # commands are generaed by tracks and shows
    # they can open or close shows, generate input events and do special tasks
    # commands also generate osc outputs to other computers
    # handles one command provided as a line of text
    
    def handle_command(self,command_text,source='',show=''):
        # print 'PIPRESENTS ',command_text,'\n   Source',source,'from',show
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        fields= command_text.split()

        if fields[0] in ('osc','OSC'): 
            if self.osc_enabled is True:
                status,message=self.oscdriver.parse_osc_command(fields[1:])
                if status=='warn':
                    self.mon.warn(self,message)
                if status=='error':
                    self.mon.err(self,message)
                    self.end('error',message)
                return
        

        if fields[0] =='counter':
            status,message=self.counter_manager.parse_counter_command(fields[1:])
            if status=='error':
                self.mon.err(self,message)
                self.end('error',message)
            return

                           
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''
        if show_command in ('open','close','closeall','openexclusive'):
            self.mon.sched(self, TimeOfDay.now,command_text + ' received from show:'+show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
            
        elif show_command =='monitor':
            self.handle_monitor_command(show_ref)
            return

        elif show_command =='cec':
            self.handle_cec_command(show_ref)
            return
        
        elif show_command == 'event':
            self.handle_input_event(show_ref,'Show Control')
            return
        
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.shutdownnow_pressed)
            return

        elif show_command == 'reboot':
            # need root.after to get out of st thread
            self.root.after(1,self.reboot_pressed)
            return
        
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return


    def handle_monitor_command(self,command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')

    def handle_cec_command(self,command):
        if command == 'on':
            os.system('echo "on 0" | cec-client -s')
        elif command == 'standby':
            os.system('echo "standby 0" | cec-client -s')

        elif command == 'scan':
            os.system('echo scan | cec-client -s -d 1')
                      
    # deal with differnt commands/input events

    def shutdownnow_pressed(self):
        self.shutdown_required=True
        if self.show_manager.all_shows_exited() is True:
           self.all_shows_ended_callback('normal','no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def reboot_pressed(self):
        self.reboot_required=True
        if self.show_manager.all_shows_exited() is True:
           self.all_shows_ended_callback('normal','no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows() 


    def handle_sigterm(self,signum,fframe):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required=True
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')



# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal','no shows running')

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.starter_show['background-colour'])
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message)
            self.mon.sched(self, None,"Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")
                          
            # close logging files
            self.mon.finish()
            print 'Uncollectable Garbage',gc.collect()
            # objgraph.show_backrefs(objgraph.by_type('Monitor'))
            sys.exit(101)
                          
        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message_text)   
            self.mon.sched(self,None, "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")
                          
            # close logging files 
            self.mon.finish()
            print 'uncollectable garbage',gc.collect()
            sys.exit(102)

        else:           
            self.mon.sched(self,None,"Pi Presents  exiting normally, bye\n")
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            
            # close logging files 
            self.mon.finish()
            if self.reboot_required is True:
                # print 'REBOOT'
                call (['sudo','reboot'])
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call (['sudo','shutdown','now','SHUTTING DOWN'])
            print 'uncollectable garbage',gc.collect()
            sys.exit(100)


    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation
        if self.animate is not None:
            self.animate.terminate()

        # tidy up i/o plugins
        if self.ioplugin_manager != None:
            self.ioplugin_manager.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()



# *******************************
# Connecting to network and email
# *******************************

    def init_network(self):

        timeout=int(self.options['nonetwork'])
        if timeout== 0:
            self.network_connected=False
            self.unit=''
            self.ip=''
            self.interface=''
            return
        
        self.network=Network()
        self.network_connected=False

        # try to connect to network
        self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network')
        success=self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected=True
        self.mon.sched (self, None,'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details=False
        network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit=self.network.unit

        # get interface and IP details of preferred interface
        self.interface,self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected=False
            return
        self.network_details=True
        self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip)


    def init_mailer(self):

        self.email_enabled=False
        email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self,'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self,'Found pp_email.cfg at ' + email_file_path)
        self.mailer=Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled=True
            self.mon.log (self,'Email Enabled')


    def try_connect(self):
        tries=1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) +  '\n ' +str(error))
                tries +=1
                if tries >5:
                    self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries))
                    return False


    def send_email(self,reason,subject,message):
        if self.try_connect() is False:
            return False
        else:
            success,error = self.mailer.send(subject,message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self,'Sent email for ' + reason)
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect from email server ' + str(error))
                return True