Example #1
0
    def __init__(self):
        super().__init__()

        self.controller = Controller()
        self.displays = Displays()

        if len(self.displays) == 0:
            print('Could not find supported displays')
            quit()

        self.sensors = LuminanceSourceManager()
        self.sensors.add_source_type(LuminanceIIO)
        self.sensors.add_source_type(LuminanceMQTT, {
            'topic': MQTT_TOPIC,
            'host': MQTT_HOST
        })
        manual_parameters = self.controller.get_range()
        manual_parameters.update({'value': self.displays.get_brightness()})
        self.sensors.add_source_type(LuminanceManual, manual_parameters)
        self.sensors.activate(DEFAULT_SENSOR)

        self.menu = self.construct_menu()
        self.menu_visible = False
        self.setContextMenu(self.menu)
        self.activated.connect(lambda reason: self.action_click(reason, self))

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.main_control)
        self.timer.start(1000)

        if MQTT_PUBLISH is True:
            self.mqtt_publisher = LuminanceMQTT(name="mqttp",
                                                path=MQTT_TOPIC,
                                                host=MQTT_HOST)
Example #2
0
 def __init__(self):
     self.sensors = Sensors()
     self.displays = Displays()
     self.gate = Gate()
Example #3
0
class Track():
    def __init__(self):
        self.sensors = Sensors()
        self.displays = Displays()
        self.gate = Gate()

    def startRace(self):
        self.displays.clear()
        self.gate.release()
        self.sensors.start()

    def stopRace(self):
        # No  need to keep sensing when no race is happening
        self.sensors.stop()
        # Make sure we can reset the gate
        self.gate.reset()
        self.displays.displayTimes(self.sensors.getTimes())
        return self.sensors.getTimes()

    def getTimes(self):
        return self.sensors.getTimes()

    def test(self):
        self.gate.release()
        time.sleep(2)
        self.gate.reset()
        self.displays.displayHex([0xba5e, 0xba11, 0x0])
        time.sleep(2)
        self.displays.displayHex([0x0, 0xcafe, 0xbabe])
        time.sleep(2)
        self.displays.displayHex([0xdead, 0x0, 0xbeef])
        time.sleep(2)
        self.displays.clear()
        time.sleep(1)
        currentTime = time.time()
        while (currentTime + 10.0) > time.time():
            self.displays.displayTimes(self.sensors.getState())
        self.displays.clear()
Example #4
0
    def __init__(self):
        mirror_logger.info("Started Mirrorcast")
        self.indicator = appindicator.Indicator.new(
            "mirrorMenu",
            os.path.abspath('/opt/mirrorcast/mirrorcast_tray.png'),
            appindicator.IndicatorCategory.SYSTEM_SERVICES)
        self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)

        #A string so we know what the user is doing
        self.state = "stopped"
        self.menu = gtk.Menu()

        #Set up menu
        item_start = gtk.MenuItem('Start Mirroring')
        item_start.connect('activate', self.start)
        self.menu.append(item_start)
        #Media Sub Menu
        self.media_sub = gtk.Menu()
        item_media = gtk.MenuItem('Play Media (Experimental)')
        item_media.set_submenu(self.media_sub)
        item_file = gtk.MenuItem('Media File')
        item_file.connect('activate', self.file)
        self.media_sub.append(item_file)
        item_dvd = gtk.MenuItem('Play DVD')
        item_dvd.connect('activate', self.dvd)
        self.media_sub.append(item_dvd)
        item_youtube = gtk.MenuItem('Youtube URL')
        item_youtube.connect('activate', self.youtube)
        self.media_sub.append(item_youtube)
        self.menu.append(item_media)

        item_freeze = gtk.MenuItem('Freeze')
        item_freeze.connect('activate', self.freeze)
        self.menu.append(item_freeze)
        item_update = gtk.MenuItem('Update Mirrorcast')
        item_update.connect('activate', self.update)
        self.menu.append(item_update)
        item_quit = gtk.MenuItem('Quit')
        item_quit.connect('activate', self.quit)
        self.menu.append(item_quit)
        sep = gtk.SeparatorMenuItem()
        self.menu.append(sep)
        self.outputSub = gtk.Menu()
        output = gtk.MenuItem("Cast To")
        output.set_submenu(self.outputSub)
        '''Recievers/Hosts Menu'''
        self.hosts = hosts()
        #Varaibles for sorting receivers into sub-menus
        self.list_receivers = []
        self.sortedMenu = []
        sortSub = []
        subitems = []
        sortInd = 0
        sortInd2 = 0
        #Add receivers to menu
        self.list_receivers.append(gtk.RadioMenuItem('None'))
        for ind, i in enumerate(self.hosts.receivers):
            #allow user to sort their receivers into sublists
            if i['aspect'] == "sub":
                self.sortedMenu.append(gtk.Menu())
                sortSub.append(gtk.MenuItem(i['host']))
                sortSub[sortInd].set_submenu(self.sortedMenu[sortInd])
                self.outputSub.append(sortSub[sortInd])
                sortInd = sortInd + 1
            elif sortInd > 0:
                try:
                    subitems.append(
                        gtk.RadioMenuItem(str(i['host']),
                                          group=self.subitems[sortInd2 - 1]))
                except:
                    subitems.append(
                        gtk.RadioMenuItem(str(i['host']),
                                          group=self.list_receivers[0]))
                subitems[sortInd2].connect('toggled', self.hosts.set_receiver,
                                           subitems[sortInd2].get_label())
                self.sortedMenu[sortInd - 1].append(subitems[sortInd2])
                sortInd2 = sortInd2 + 1
            else:
                self.list_receivers.append(
                    gtk.RadioMenuItem(str(i['host']),
                                      group=self.list_receivers[ind - 1]))
        for i in self.list_receivers:
            self.outputSub.append(i)
            i.connect('toggled', self.hosts.set_receiver, i.get_label())
        self.list_receivers[0].set_active(True)

        self.Display = Displays()
        self.displaysSub = gtk.Menu()
        displays = gtk.MenuItem("Select Display to Mirror")
        displays.set_submenu(self.displaysSub)
        self.list_displays = []
        #Add displays/monitors to menu
        for ind, i in enumerate(self.Display.monitors):
            if ind != 0:
                self.list_displays.append(
                    gtk.RadioMenuItem(str(i[0]),
                                      group=self.list_displays[ind - 1]))
            else:
                self.list_displays.append(
                    gtk.RadioMenuItem(self.Display.monitors[0][0]))
        for i in self.list_displays:
            self.displaysSub.append(i)
            i.connect('toggled', self.Display.set_display, i.get_label())
        self.list_displays[0].set_active(True)
        self.menu.append(output)
        self.menu.append(displays)
        self.menu.show_all()
        self.indicator.set_menu(self.menu)
        self.sound = Audio()
        self.ffmpeg = None
        self.vlc = None
        self.sleep = dbus_listen(item_start, self.ffmpeg)
Example #5
0
class TrayMenu:
    def __init__(self):
        mirror_logger.info("Started Mirrorcast")
        self.indicator = appindicator.Indicator.new(
            "mirrorMenu",
            os.path.abspath('/opt/mirrorcast/mirrorcast_tray.png'),
            appindicator.IndicatorCategory.SYSTEM_SERVICES)
        self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)

        #A string so we know what the user is doing
        self.state = "stopped"
        self.menu = gtk.Menu()

        #Set up menu
        item_start = gtk.MenuItem('Start Mirroring')
        item_start.connect('activate', self.start)
        self.menu.append(item_start)
        #Media Sub Menu
        self.media_sub = gtk.Menu()
        item_media = gtk.MenuItem('Play Media (Experimental)')
        item_media.set_submenu(self.media_sub)
        item_file = gtk.MenuItem('Media File')
        item_file.connect('activate', self.file)
        self.media_sub.append(item_file)
        item_dvd = gtk.MenuItem('Play DVD')
        item_dvd.connect('activate', self.dvd)
        self.media_sub.append(item_dvd)
        item_youtube = gtk.MenuItem('Youtube URL')
        item_youtube.connect('activate', self.youtube)
        self.media_sub.append(item_youtube)
        self.menu.append(item_media)

        item_freeze = gtk.MenuItem('Freeze')
        item_freeze.connect('activate', self.freeze)
        self.menu.append(item_freeze)
        item_update = gtk.MenuItem('Update Mirrorcast')
        item_update.connect('activate', self.update)
        self.menu.append(item_update)
        item_quit = gtk.MenuItem('Quit')
        item_quit.connect('activate', self.quit)
        self.menu.append(item_quit)
        sep = gtk.SeparatorMenuItem()
        self.menu.append(sep)
        self.outputSub = gtk.Menu()
        output = gtk.MenuItem("Cast To")
        output.set_submenu(self.outputSub)
        '''Recievers/Hosts Menu'''
        self.hosts = hosts()
        #Varaibles for sorting receivers into sub-menus
        self.list_receivers = []
        self.sortedMenu = []
        sortSub = []
        subitems = []
        sortInd = 0
        sortInd2 = 0
        #Add receivers to menu
        self.list_receivers.append(gtk.RadioMenuItem('None'))
        for ind, i in enumerate(self.hosts.receivers):
            #allow user to sort their receivers into sublists
            if i['aspect'] == "sub":
                self.sortedMenu.append(gtk.Menu())
                sortSub.append(gtk.MenuItem(i['host']))
                sortSub[sortInd].set_submenu(self.sortedMenu[sortInd])
                self.outputSub.append(sortSub[sortInd])
                sortInd = sortInd + 1
            elif sortInd > 0:
                try:
                    subitems.append(
                        gtk.RadioMenuItem(str(i['host']),
                                          group=self.subitems[sortInd2 - 1]))
                except:
                    subitems.append(
                        gtk.RadioMenuItem(str(i['host']),
                                          group=self.list_receivers[0]))
                subitems[sortInd2].connect('toggled', self.hosts.set_receiver,
                                           subitems[sortInd2].get_label())
                self.sortedMenu[sortInd - 1].append(subitems[sortInd2])
                sortInd2 = sortInd2 + 1
            else:
                self.list_receivers.append(
                    gtk.RadioMenuItem(str(i['host']),
                                      group=self.list_receivers[ind - 1]))
        for i in self.list_receivers:
            self.outputSub.append(i)
            i.connect('toggled', self.hosts.set_receiver, i.get_label())
        self.list_receivers[0].set_active(True)

        self.Display = Displays()
        self.displaysSub = gtk.Menu()
        displays = gtk.MenuItem("Select Display to Mirror")
        displays.set_submenu(self.displaysSub)
        self.list_displays = []
        #Add displays/monitors to menu
        for ind, i in enumerate(self.Display.monitors):
            if ind != 0:
                self.list_displays.append(
                    gtk.RadioMenuItem(str(i[0]),
                                      group=self.list_displays[ind - 1]))
            else:
                self.list_displays.append(
                    gtk.RadioMenuItem(self.Display.monitors[0][0]))
        for i in self.list_displays:
            self.displaysSub.append(i)
            i.connect('toggled', self.Display.set_display, i.get_label())
        self.list_displays[0].set_active(True)
        self.menu.append(output)
        self.menu.append(displays)
        self.menu.show_all()
        self.indicator.set_menu(self.menu)
        self.sound = Audio()
        self.ffmpeg = None
        self.vlc = None
        self.sleep = dbus_listen(item_start, self.ffmpeg)

    #the following function is run when the user clicks "Start/Stop Mirroring"
    def start(self, w):
        notify.init("mirrorMenu")
        mirror_logger.info("Detected Audio Device: " +
                           str(self.sound.audioDev))
        if w.get_label() == 'Start Mirroring':
            #If the user did not select a receiver
            if self.hosts.receiver == "None":
                notify.init("mirrorMenu")
                notify.Notification.new("Error",
                                        "You did not select a receiver",
                                        None).show()
                return
            notify.Notification.new(
                "Connecting to Receiver",
                "Attempting to establish connection to " + self.hosts.receiver,
                None).show()
            mirror_logger.info("User is trying to connect to " +
                               self.hosts.receiver)
            #If we cannot connect to the receiver
            if self.connect("play,") == False:
                notify.init("mirrorMenu")
                notify.Notification.new(
                    "Connection Error",
                    "Could not connect to" + self.hosts.receiver +
                    ". please try again and if problem persists then please contact your system administrator.",
                    None).show()
                mirror_logger.warning("Failed to connect to " +
                                      self.hosts.receiver)
                return
            #Create and start loop that checks if receiver can still be reached
            mirror_logger.info("User connected to " + self.hosts.receiver)
            w.set_label("Stop Mirroring")
            self.start_casting()
            #Start a loop that will keep checking if the client can still reach the server
            connection = threading.Thread(target=self.alive, args=[w])
            connection.start()
        elif w.get_label() == 'Stop Mirroring':
            self.state = "stopped"
            self.sound.audio(False)
            self.Display.display(False, self.hosts.aspect)
            if self.ffmpeg != None:
                if self.ffmpeg.poll() == None:
                    self.ffmpeg.terminate()
            w.set_label('Start Mirroring')
            return

    def start_casting(self):
        res = self.Display.resolution
        self.sleep.sleep = False
        #If receiver is set to display 4:3 and the client is 16:9 then change screen resoltion to 1024x768
        if (self.hosts.aspect == "wide" or self.hosts.aspect == "16:9"
                or self.hosts.aspect
                == "16:10") and self.Display.get_ratio(res) == "16:9":
            self.hosts.aspect = "16:9"
        else:
            self.hosts.aspect = "4:3"
            if self.Display.get_ratio(self.Display.resolution) != "4:3":
                res = "1024x768"
                try:
                    subprocess.call("xrandr --output " + self.Display.type +
                                    " --mode 1024x768",
                                    shell=True)
                except:
                    mirror_logger.warning(
                        "Could now change screen resolution to 4:3")
                    notify.Notification.new(
                        "Resolution Error",
                        "Failed to change screen resolution to match receiver.",
                        None).show()
                    return
        self.sound.audio(True)
        #Start encoding and sending the stream to the receiver
        self.state = "casting"
        time.sleep(1)
        display = os.environ['DISPLAY']  #get display of current user
        self.ffmpeg = subprocess.Popen([
            "ffmpeg", "-loglevel", "warning", "-f", "pulse", "-ac", "2", "-i",
            "default", "-async", "1", "-f", "x11grab", "-r", "25", "-s",
            str(res), "-i",
            str(display) + "+" + str(int(self.Display.xoffset)) + "," +
            str(self.Display.yoffset), "-aspect", self.hosts.aspect, "-vcodec",
            "libx264", "-pix_fmt", "yuv420p", "-tune", "zerolatency",
            "-preset", "ultrafast", "-vf",
            "scale=" + str(res).replace('x', ':'), "-f", "mpegts",
            "udp://" + self.hosts.receiver + ":8090"
        ],
                                       stdout=subprocess.PIPE)
        self.sound.monitor_audio()
        notify.Notification.new(
            "Connection Established",
            "Connection to " + self.hosts.receiver + " established.",
            None).show()

    def alive(self, w):
        mirror_logger.info("Sending Alive Packets")
        timestamp = time.localtime()
        timeout = 10
        retries = 1
        i = 0
        command = "active," + socket.gethostname()

        while True:
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(5)
                sock.connect((self.hosts.receiver, 8092))
                sock.settimeout(None)
                #If the user's computer is going to sleep
                if self.state == "stopped" or self.sleep.sleep == True:
                    mirror_logger.info("User stopped casting")
                    command = "stop," + socket.gethostname()
                    sock.send(command.encode('ascii'))
                    sock.close()
                    return
                if self.state == "freeze":
                    logging.info("User frooze their screen")
                    command = "freeze," + socket.gethostname()
                    sock.send(command.encode('ascii'))
                    status = sock.recv(1024)
                    if status.decode('ascii') == "paused":
                        self.ffmpeg.terminate()
                        w.set_label('Start Mirroring')
                        notify.init("mirrorMenu")
                        notify.Notification.new(
                            "Freezed",
                            "You have frozen your current desktop, click Start Mirroring to resume",
                            None).show()
                        self.state = "stopped"
                        time.sleep(1)
                        self.sound.audio(False)
                        self.Display.display(False, self.hosts.aspect)
                    sock.close()
                    return
                sock.send(command.encode('ascii'))
                status = sock.recv(1024)
                if status.decode('ascii') == "ok":
                    timestamp = time.localtime()
                sock.close()
            except:
                #time.sleep(1)
                if (
                        time.mktime(time.localtime()) - time.mktime(timestamp)
                ) >= timeout and self.state != "stopped" and self.sleep.sleep != True:
                    i = i + 1
                    if i == 1:
                        mirror_logger.warning("Attempting to reconnect to " +
                                              self.hosts.receiver)
                        if self.ffmpeg.poll() == None:
                            self.ffmpeg.terminate()
                        notify.Notification.new(
                            "Reconnecting",
                            "Connection to " + self.hosts.receiver +
                            " has been lost. Attempting to reconnect.",
                            None).show()
                        time.sleep(2)
                    if self.connect("play,") == True:
                        mirror_logger.info("Reconnected to " +
                                           self.hosts.receiver)
                        self.start_casting()
                        i = 0
                    if i == retries:
                        self.state = "stopped"
                        w.set_label('Start Mirroring')
                        notify.init("mirrorMenu")
                        notify.Notification.new(
                            "Connection Lost", "Connection to " +
                            self.hosts.receiver + " timed out.", None).show()
                        mirror_logger.warning(
                            "Connection Lost: Connection to " +
                            self.hosts.receiver + " timed out.")
                        return

    def quit(self, w):
        self.sound.audio(False)
        self.state = "stopped"
        self.Display.display(False, self.hosts.aspect)
        #kill ffmpeg incase user forgot to click "stop"
        if self.ffmpeg != None:
            if self.ffmpeg.poll() == None:
                self.ffmpeg.terminate()
        gtk.main_quit()

    def freeze(self, w):
        if self.state == "stopped":
            notify.init("mirrorMenu")
            notify.Notification.new(
                "Not Mirroring",
                "To freeze your screen you need to Start Mirroring.",
                None).show()
            return
        self.state = "freeze"
        return

    def update(self, w):
        if self.state == "casting":
            notify.init("mirrorMenu")
            notify.Notification.new(
                "Cannot Update",
                "Please stop mirroring before you try to update", None).show()
            return
        subprocess.call("/opt/mirrorcast/mirrorcast-autoupdater.sh",
                        shell=True)
        gtk.main_quit()
        return

    def file(self, w):
        if self.state == "casting":
            notify.init("mirrorMenu")
            notify.Notification.new(
                "Error",
                "Please stop mirroring before you try to use this feature",
                None).show()
        else:
            if self.hosts.receiver == "None":
                notify.init("mirrorMenu")
                notify.Notification.new("Error",
                                        "Please select a receiving device",
                                        None).show()
                return
            if self.connect("media,") == False:
                notify.init("mirrorMenu")
                notify.Notification.new(
                    "Connection Error",
                    "Could not connect to" + self.hosts.receiver +
                    ". please try again and if problem persists then please contact your system administrator.",
                    None).show()
                mirror_logger.warning("Failed to connect to " +
                                      self.hosts.receiver)
                return
            mirror_logger.info("User connected to " + self.hosts.receiver +
                               " to play media file")
            select = Tk()
            select.withdraw()
            types = [("Video Files", ("*.mp4", "*.avi", "*.mov", "*.mkv",
                                      "*.flv", "*.mpeg", "*.mpg", "*.wmv")),
                     ("All files", "*.*")]
            file = askopenfilename(filetypes=types)
            select.destroy()
            print(file)
            if file == () or file == None or file == "":
                return
            if self.vlc != None:
                if self.vlc.poll() == None:
                    self.vlc.terminate()
            subprocess.Popen([
                "vlc", "-q", "file://" + file, "--sout-avcodec-strict=-2",
                "--sout=#transcode{vcodec=h264,vb=2000,scale=Auto}:duplicate{dst=http{mux=ts,dst=:8090/video},dst=display}",
                ":sout-keep"
            ],
                             stdout=subprocess.PIPE)
            time.sleep(2)
            self.send_cmd("media-start,")
            ui = mediaui(self.hosts.receiver)

    def dvd(self, w):
        if self.state == "casting":
            notify.init("mirrorMenu")
            notify.Notification.new(
                "Error",
                "Please stop mirroring before you try to use this feature",
                None).show()
        else:
            if self.hosts.receiver == "None":
                notify.init("mirrorMenu")
                notify.Notification.new("Error",
                                        "Please select a receiving device",
                                        None).show()
                return
            if self.connect("media,") == False:
                notify.init("mirrorMenu")
                notify.Notification.new(
                    "Connection Error",
                    "Could not connect to" + self.hosts.receiver +
                    ". please try again and if problem persists then please contact your system administrator.",
                    None).show()
                mirror_logger.warning("Failed to connect to " +
                                      self.hosts.receiver)
                return
            mirror_logger.info("User connected to " + self.hosts.receiver +
                               " to stream DVD")
            if self.vlc != None:
                if self.vlc.poll() == None:
                    self.vlc.terminate()
            self.vlc = subprocess.Popen([
                "vlc", "-q", "dvdsimple://", "--sout-avcodec-strict=-2",
                "--sout-x264-preset", "ultrafast",
                "--sout=#transcode{vcodec=h264,vb=2000,scale=Auto}:duplicate{dst=http{mux=ts,dst=:8090/video},dst=display}",
                ":sout-keep"
            ],
                                        stdout=subprocess.PIPE)
            time.sleep(2)
            self.send_cmd("media-start,")
            ui = mediaui(self.hosts.receiver)

    def youtube(self, w):
        if self.state == "casting":
            notify.init("mirrorMenu")
            notify.Notification.new(
                "Error",
                "Please stop mirroring before you try to use this feature",
                None).show()
        else:
            if self.hosts.receiver == "None":
                notify.init("mirrorMenu")
                notify.Notification.new("Error",
                                        "Please select a receiving device",
                                        None).show()
            if self.connect("tu-media,") == False:
                notify.init("mirrorMenu")
                notify.Notification.new(
                    "Connection Error",
                    "Could not connect to" + self.hosts.receiver +
                    ". please try again and if problem persists then please contact your system administrator.",
                    None).show()
                mirror_logger.warning("Failed to connect to " +
                                      self.hosts.receiver)
                return
            ui = tubeui(self.hosts.receiver)

    def connect(self, cmd):
        command = cmd + socket.gethostname()
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            sock.connect((self.hosts.receiver, 8092))
            sock.settimeout(None)
            sock.send(command.encode('ascii'))
            while True:
                status = sock.recv(8024)
                #if server returns busy then some one else is already using this receiver
                if status.decode('ascii') == "busy":
                    notify.init("mirrorMenu")
                    mirror_logger.info("User attempted to connect to " +
                                       self.hosts.receiver +
                                       " but receiver was busy")
                    notify.Notification.new(
                        "Error",
                        "Sorry some one else is already  connected to this receiver, please try again later.",
                        None).show()
                    sock.close()
                    return False
                #If client succeeds  in connecting to receiver
                elif status.decode('ascii') == "ready":
                    mirror_logger.info("Server is ready")
                    break
            sock.close()
        except:
            return False
        if cmd == "play,":
            self.state = "casting"
        return True

    def send_cmd(self, cmd):
        command = cmd + socket.gethostname()
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            sock.connect((self.hosts.receiver, 8092))
            sock.settimeout(None)
            sock.send(command.encode('ascii'))
            sock.close()
        except:
            return False
Example #6
0
class ZenDisplay(QtWidgets.QSystemTrayIcon):
    """System tray icon class"""
    def __init__(self):
        super().__init__()

        self.controller = Controller()
        self.displays = Displays()

        if len(self.displays) == 0:
            print('Could not find supported displays')
            quit()

        self.sensors = LuminanceSourceManager()
        self.sensors.add_source_type(LuminanceIIO)
        self.sensors.add_source_type(LuminanceMQTT, {
            'topic': MQTT_TOPIC,
            'host': MQTT_HOST
        })
        manual_parameters = self.controller.get_range()
        manual_parameters.update({'value': self.displays.get_brightness()})
        self.sensors.add_source_type(LuminanceManual, manual_parameters)
        self.sensors.activate(DEFAULT_SENSOR)

        self.menu = self.construct_menu()
        self.menu_visible = False
        self.setContextMenu(self.menu)
        self.activated.connect(lambda reason: self.action_click(reason, self))

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.main_control)
        self.timer.start(1000)

        if MQTT_PUBLISH is True:
            self.mqtt_publisher = LuminanceMQTT(name="mqttp",
                                                path=MQTT_TOPIC,
                                                host=MQTT_HOST)

    def toggle_menu(self):
        """Toggle context menu visibility"""
        if self.menu_visible:
            self.menu.hide()
        else:
            self.menu.popup(QtGui.QCursor.pos())
        self.menu_visible = not self.menu_visible

    def construct_menu(self):
        """Construct a menu from the sitemap"""
        menu = QtWidgets.QMenu()
        self.construct_menu_displays(menu)
        self.construct_menu_sensors(menu)

        # Create quit button
        menu.addSeparator()
        action_quit = menu.addAction("Quit")
        action_quit.triggered.connect(lambda _: quit())
        return menu

    def construct_menu_displays(self, parent):
        """Create submenu for displays"""
        display_menu = parent.addMenu('Displays')
        for display in self.displays:
            action = display_menu.addAction(display['name'])
            action.setCheckable(True)
            action.setChecked(display['use'])
            did = display['id']
            action.triggered.connect(
                lambda state, did=did: self.displays.set_active(did, state))

    def construct_menu_sensors(self, parent):
        """Create submenu for sensors"""
        sensor_menu = parent.addMenu('Sensors')
        sensor_group = QtWidgets.QActionGroup(sensor_menu, exclusive=True)
        for sensor in self.sensors:
            action = sensor_menu.addAction(sensor.name + " (" + sensor.path +
                                           ")")
            action.setCheckable(True)
            if sensor.uid == self.sensors.get_active():
                action.setChecked(True)
            sid = sensor.uid
            action.triggered.connect(
                lambda state, sid=sid: self.sensors.activate(sid))
            sensor_group.addAction(action)

    def main_control(self):
        """Main control function, sets display brightness dynamically"""
        luminance = self.sensors.get_luminance()
        current_brightness = self.displays.get_brightness()
        recommended_brightness = self.controller.recommend_brightness(
            luminance, current_brightness)
        self.setToolTip('Brightness: ' + str(recommended_brightness) + '%')
        self.displays.set_brightness(recommended_brightness)
        if MQTT_PUBLISH is True:
            self.mqtt_publisher.publish(luminance)

    def event(self, event):
        """Event handler for QEvent objects"""
        if event.type() == QtCore.QEvent.Wheel:
            new_value = None
            if event.angleDelta().y() < 0:
                new_value = self.sensors[self.sensors.get_active()].decrease()
            else:
                new_value = self.sensors[self.sensors.get_active()].increase()
            if new_value is not None and self.supportsMessages():
                self.showMessage('Brightness', str(new_value) + '%', msecs=500)
            return True
        return False

    @classmethod
    @QtCore.pyqtSlot()
    def action_click(cls, reason, tray):
        """Toggle menu on click"""
        if reason in [
                QtWidgets.QSystemTrayIcon.Trigger,
                QtWidgets.QSystemTrayIcon.Context
        ]:
            tray.toggle_menu()