def run(self): self.waitCondition.acquire() while not self.shouldStop(): # Allow the subclass to update the GUI log('StateMachine: visiting state ' + str(self.state)) self.step(self.state) # Compute the next state next = (self.state.toSeconds() + self.delay) % (24 * 60 * 60) self.state = Time.fromSeconds(next) # Calculate the delay now = datetime.datetime.now() seconds = now.hour * 60 * 60 + now.minute * 60 + now.second + now.microsecond / 1000000.0 delay = self.state.toSeconds() - seconds if delay < 0: delay = delay + 24 * 60 * 60 log('Sleeping for %f seconds' % delay) self.waitCondition.wait(delay) #except: # log('Exception thrown in StateMachine thread') # self.waitCondition.release() # self.stop() log('StateMachine shutting down') self.cleanup() self.waitCondition.release() log('StateMachine finished shutting down')
def add(self, timeObject, timeString, rule = None): ''' timeObject - the id found in the <time> tag timeString - the text of the <time> tag rule - this parameter is purely for recursion. Because the rule has already been created, it can be passed in as a parameter to avoid creating an unnecessary copy. Recursion is only employed if the rule is a constant (no symbols); otherwise, a modified timeObject will invalidate the rule. ''' log('Adding rule %s: ' % str(timeObject) + timeString.encode('utf-8')) # Use 1-based time for rule resolution timeObject.hours = (timeObject.hours - 1) % (24 if self.use24 else 12) + 1 # Create the rule if not rule: rule = [self.createToken(word, timeObject) for word in timeString.split(' ')] # If rule contains no symbols, only constants, then only add rule for its hour isConst = self.isConstant(rule) # Now that the rule is created, we can apply the default duration if # the rule is all constant if isConst and not timeObject.duration: timeObject.duration = Time.fromSeconds(self.defaultDuration) for i in range(len(self.rules)): hour = (i - 1) % (24 if self.use24 else 12) + 1 # make hour 1-based # Constant times are only for a single hour if isConst and hour != timeObject.hours: continue self.rules[i] = self.insert(self.rules[i], rule, timeObject, hour) if isConst: # However, if the duration pushes the rule into the next hour, # clone the rule (with an appropriately shorter duration) and # recursively add to the next hour as well. hourBoundary = (timeObject.hours + 1) * 3600 if timeObject.end() > hourBoundary: # Start at the beginning of the next hour nextTime = Time(timeObject.hours + 1, 0, 0) nextTime.duration = Time.fromSeconds(timeObject.end() - hourBoundary) nextTime.useSeconds = timeObject.useSeconds # For cosmetics self.add(nextTime, timeString, rule) break
def spin(self): self.qlockThread.start() #self.spriteThread.start() # Give the solvers time to do their work, so that when the window fades # in, the solution will already have been highlighted. # I noticed problems as high as 10ms, so use 25ms. if not self.config.ssMode: xbmc.sleep(25) self.window.doModal() try: self.qlockThread.stop() #self.spriteThread.stop() except: log('Error occurred while stopping background threads')
def __unicode__(self): ''' Convert the symbol to a string. If the stringTable entry for the calculated number has a space, then the returned value will also have a space and therefore will need to be split. If the string table entry does not exist, an empty string is returned. ''' if self.unit == 'h': id = self.transform(self.timeSource.hours) elif self.unit == 'm': id = self.transform(self.timeSource.minutes) elif self.unit == 's': id = self.transform(self.timeSource.seconds) # If id is not defined, return an empty string if id not in self.stringTable: log('Error: no string defined for id %d' % id) return self.stringTable.get(id, '')
def lookupRecursive(self, node, time): '''Basic linked list node transversal''' if node == None: log('ERROR: No node, returning []') return [] if node.next == None: # Base case: next node is null, return this one return node.rule # Compare minutes and seconds (by making the hours equal) tempTime = Time(node.next.time.hours, time.minutes, time.seconds) if tempTime.toSeconds() < node.next.time.toSeconds(): # Base case: time comes before node.time, so return node.rule return node.rule return self.lookupRecursive(node.next, time)
def step(self, time): # Ask the solver for the time log('Solving for the current time (%s)' % str(time)) solution = self.solver.resolveTime(time) solutionUTF8 = [uni.encode('utf-8') for uni in solution] log('Solution: ' + str(solutionUTF8)) truthMatrix = createTruthMatrix(self.layout.height, self.layout.width) success = self.highlight(self.layout.matrix, truthMatrix, solution) if not success: log('Unable to highlight solution. Reattempting with no spaces between words') truthMatrix = createTruthMatrix(self.layout.height, self.layout.width) success = self.highlight(self.layout.matrix, truthMatrix, solution, False) if success: log('Success') else: log('Failed to highlight solution again. Drawing best attempt') # Draw the result self.window.drawMatrix(truthMatrix)
def __init__(self, file): log('Using theme: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing theme file!') raise # Let the user see the error try: self.active = root.find('active').text self.inactive = root.find('inactive').text imgNode = root.find('image') if imgNode != None: self.image = imgNode.text # Width and height default to fullscreen if unspecified self.imageWidth = int(imgNode.attrib['width']) if 'width' in imgNode.attrib else 1280 self.imageHeight = int(imgNode.attrib['height']) if 'height' in imgNode.attrib else 720 # If an image was found, background is optional if root.find('background') != None: self.background = root.find('background').text else: self.background = '00000000' # fully transparent else: # If an image wasn't found, background is mandatory self.background = root.find('background').text self.image = None self.imageWidth = 1280 self.imageHeight = 720 # In SS mode, the background is black so ignore the alpha channel if xbmc.getCondVisibility('System.ScreenSaverActive'): # Proceed if all chars are hex if self.isHexColor(self.background): self.background = 'ff' + self.background[2:] except: log('Error parsing theme file!') sys.exit()
def __init__(self, window, layout): delay = self.calcDelay(layout) super(QlockThread, self).__init__(delay) self.window = window # Use a lowercase matrix for comparison self.layout = deepcopy(layout) for row in range(self.layout.height): for col in range(self.layout.width): self.layout.matrix[row][col] = self.layout.matrix[row][col].lower() log('Creating the solver') now = datetime.datetime.now() start = now.hour * 60 * 60 + now.minute * 60 + now.second + now.microsecond / 1000000.0 # Let the solver know about the default delay. It will need this # information once it has parsed a times string into tokens. self.solver = solver.Solver(layout, delay) nodes = self.solver.countNodes() now = datetime.datetime.now() stop = now.hour * 60 * 60 + now.minute * 60 + now.second + now.microsecond / 1000000.0 log('Solver created in %f seconds with %d nodes and %d rules' % (stop - start, nodes, len(layout.times)))
def changeTheme(self): self.config.loadNextTheme() background = self.getControl(gui.CONTROL_BACKGROUND) if self.config.theme.isHexColor(self.config.theme.background): # setColorDiffuse() expects 0xAARRGGBB background.setColorDiffuse('0x' + self.config.theme.background) else: # As of https://github.com/xbmc/xbmc/commit/44472bc64, setColorDiffuse() # can accept a skin color as well as a hex code. If your XBMC is older, # the background will be transparent. background.setColorDiffuse(self.config.theme.background) # Always complain about XBMC's missing features log('Unable to change text color: Not implemented in XBMC!') image = self.getControl(gui.CONTROL_IMAGE) if self.config.theme.image: image.setImage(os.path.join(self.config.themeDir, self.config.theme.image)) image.setWidth(self.config.theme.imageWidth) image.setHeight(self.config.theme.imageHeight) image.setVisible(True) else: image.setVisible(False)
def changeTheme(self): self.config.loadNextTheme() background = self.getControl(gui.CONTROL_BACKGROUND) if self.config.theme.isHexColor(self.config.theme.background): # setColorDiffuse() expects 0xAARRGGBB background.setColorDiffuse('0x' + self.config.theme.background) else: # As of https://github.com/xbmc/xbmc/commit/44472bc64, setColorDiffuse() # can accept a skin color as well as a hex code. If your XBMC is older, # the background will be transparent. background.setColorDiffuse(self.config.theme.background) # Always complain about XBMC's missing features log('Unable to change text color: Not implemented in XBMC!') image = self.getControl(gui.CONTROL_IMAGE) if self.config.theme.image: image.setImage( os.path.join(self.config.themeDir, self.config.theme.image)) image.setWidth(self.config.theme.imageWidth) image.setHeight(self.config.theme.imageHeight) image.setVisible(True) else: image.setVisible(False)
def __init__(self, config): # Test to see if the window is already open if xbmc.getCondVisibility('Window.IsVisible(%s)' % WINDOW_ID): log('Bailing out (window already exists?)') sys.exit() # Generate and pretty-print the GUI XML windowGUI = gui.Window(config.layout, config.theme, config.ssMode) windowXML = windowGUI.toXMLPrettyPlease() # Create a mimic directory that we can write to skinDir = os.path.join(config.profile, 'resources', 'skins', 'Default', '720p') if not os.path.isdir(skinDir): os.makedirs(skinDir) ElementTree.ElementTree(windowXML).write(os.path.join(skinDir, 'unqlocked.xml')) log('Wrote ' + os.path.join(skinDir, 'unqlocked.xml')) # Copy our images over to the new folder mediaDir = os.path.join(config.profile, 'resources', 'skins', 'Default', 'media') if not os.path.isdir(mediaDir): os.makedirs(mediaDir) # Allow layout to specify background images images = ['unqlocked-1px-white.png'] if config.theme.image: images.append(config.theme.image) for image in images: imgPath = os.path.join(config.themeDir, image) newPath = os.path.join(mediaDir, image) if not os.path.exists(newPath): shutil.copyfile(imgPath, newPath) log('Wrote ' + newPath) # Now create the GUI window self.window = window.UnqlockedWindow('unqlocked.xml', config.profile, 'Default') self.window.setConfig(config) self.window.setLayoutCallback(self.layoutCallback) self.window.setDemoCallback(self.demoCallback) self.window.drawBackground() # Create the threads self.qlockThread = statemachine.QlockThread(self.window, config.layout) #self.spriteThread = statemachine.SpriteThread(self.window, config) # not implemented yet self.config = config
def __init__(self, file): log('Using theme: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing theme file!') raise # Let the user see the error try: self.active = root.find('active').text self.inactive = root.find('inactive').text imgNode = root.find('image') if imgNode != None: self.image = imgNode.text # Width and height default to fullscreen if unspecified self.imageWidth = int(imgNode.attrib['width'] ) if 'width' in imgNode.attrib else 1280 self.imageHeight = int(imgNode.attrib['height'] ) if 'height' in imgNode.attrib else 720 # If an image was found, background is optional if root.find('background') != None: self.background = root.find('background').text else: self.background = '00000000' # fully transparent else: # If an image wasn't found, background is mandatory self.background = root.find('background').text self.image = None self.imageWidth = 1280 self.imageHeight = 720 # In SS mode, the background is black so ignore the alpha channel if xbmc.getCondVisibility('System.ScreenSaverActive'): # Proceed if all chars are hex if self.isHexColor(self.background): self.background = 'ff' + self.background[2:] except: log('Error parsing theme file!') sys.exit()
def __init__(self, file): log('Using layout: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing layout file!') raise # Let the user see the error try: background = root.find('background') self.height = int(background.attrib['height']) self.width = int(background.attrib['width']) except: log('Error: <background> tag missing/incorrect!') sys.exit() self.matrix = [] entities = [ char.strip().upper() for char in background.text.split(',') ] if (self.height * self.width > len(entities)): log('Error: Too many characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() elif (self.height * self.width < len(entities)): log('Error: Too few characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() for i in range(self.height): self.matrix.append(entities[i * self.width:(i + 1) * self.width]) timesNode = root.find('times') if timesNode == None: log('Error: <times> node not found!') sys.exit() if 'use24' in timesNode.attrib and timesNode.attrib['use24'] == 'true': self.use24 = True else: self.use24 = False self.times = {} for time in timesNode.findall('time'): if 'id' not in time.attrib: log('Warning: found <time> tag with no "id" attribute') continue key = Time(time.attrib['id']) # If set, store the duration with the key if 'duration' in time.attrib: key.duration = Time(time.attrib['duration']) self.times[key] = time.text.lower() if len(self.times) == 0: log('Error: no <time> tags found!') sys.exit() self.strings = {} stringsNode = root.find('strings') if stringsNode == None: log('Error: <strings> node not found!') sys.exit() for string in stringsNode.findall('string'): if 'id' not in string.attrib: log('Warning: found <string> tag with no "id" attribute') continue if string.text == None or string.text.strip() == '': log('Warning: empty <string> tag for id=%s' % string.attrib['id']) continue self.strings[int( string.attrib['id'])] = string.text.strip().lower()
def getFont(self): '''Parse the current skin's Font.xml file for a list of font names by size''' log('Loading font set from current skin: ' + xbmc.getSkinDir()) fontName = '' # Use letterHeight (reasoning: WIDTH may be elastic in the future) desiredSize = self.letterHeight * 1 / 2 # Decent ratio fallback = 'font' # Font.xml can be in any resolution folder, keep trying until we find # one. Use the first Font.xml we come across. skinDir = xbmc.translatePath("special://skin/") for item in os.listdir(skinDir): fontFile = os.path.join(skinDir, item, 'Font.xml') if not os.path.exists(fontFile): continue try: root = parse(fontFile).getroot() except: continue for set in root.findall('fontset'): # Now that we've found the file, use the Default fontset # (guaranteed to exist regardless of skin) if 'id' not in set.attrib or set.attrib['id'] != 'Default': continue # Add one so we don't lie when saying "smaller" (versus "smaller than or equal to") log('Font set loaded. Searching for a font smaller than %dpt' % (desiredSize + 1)) # Index the discovered fonts into two categories fontsWithoutStyle = {} fontsWithStyle = {} for font in set.findall('font'): if font.find('size') == None or font.find('name') == None: continue size = int(font.find('size').text) # Skip fonts larger than the desired size if size > desiredSize: continue if not font.find('style'): fontsWithoutStyle[size] = font.find('name').text else: fontsWithStyle[size] = font.find('name').text # Categories generated. Prefer unstyled fonts if len(fontsWithoutStyle): max = sorted(fontsWithoutStyle.keys(), reverse=True)[0] log('Using unstyled font "%s" (%dpt)' % (fontsWithoutStyle[max], max)) return fontsWithoutStyle[max] elif len(fontsWithStyle): max = sorted(fontsWithStyle.keys(), reverse=True)[0] log('Using styled font "%s" (%dpt)' % (fontsWithStyle[max], max)) return fontsWithStyle[max] log('No suitable fonts found. Falling back to ' + fallback) return fallback log('Default font set not found. Falling back to ' + fallback) return fallback log('Font.xml not found. Falling back to ' + fallback) return fallback
def refresh(self): log('Stopping Qlock thread') self.qlockThread.stop() log('Qlock thread stopped? Sleeping')
def __init__(self, file): log('Using layout: ' + os.path.basename(file)) try: root = ElementTree.parse(file).getroot() except: log('Error parsing layout file!') raise # Let the user see the error try: background = root.find('background') self.height = int(background.attrib['height']) self.width = int(background.attrib['width']) except: log('Error: <background> tag missing/incorrect!') sys.exit() self.matrix = [] entities = [char.strip().upper() for char in background.text.split(',')] if (self.height * self.width > len(entities)): log('Error: Too many characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() elif (self.height * self.width < len(entities)): log('Error: Too few characters in background (expecting %d, found %d)' % \ (self.height * self.width, len(entities))) sys.exit() for i in range(self.height): self.matrix.append(entities[i * self.width : (i + 1) * self.width]) timesNode = root.find('times') if timesNode == None: log('Error: <times> node not found!') sys.exit() if 'use24' in timesNode.attrib and timesNode.attrib['use24'] == 'true': self.use24 = True else: self.use24 = False self.times = {} for time in timesNode.findall('time'): if 'id' not in time.attrib: log('Warning: found <time> tag with no "id" attribute') continue key = Time(time.attrib['id']) # If set, store the duration with the key if 'duration' in time.attrib: key.duration = Time(time.attrib['duration']) self.times[key] = time.text.lower() if len(self.times) == 0: log('Error: no <time> tags found!') sys.exit() self.strings = {} stringsNode = root.find('strings') if stringsNode == None: log('Error: <strings> node not found!') sys.exit() for string in stringsNode.findall('string'): if 'id' not in string.attrib: log('Warning: found <string> tag with no "id" attribute') continue if string.text == None or string.text.strip() == '': log('Warning: empty <string> tag for id=%s' % string.attrib['id']) continue self.strings[int(string.attrib['id'])] = string.text.strip().lower()