#!/usr/bin/python """ A cheap HTPC menu with LIRC support. Use a remote control or the keyboard to navigate menus. Keyboard keys are UP, DOWN, LEFT, RIGHT, and ENTER. Requires pygame and pylirc modules. Copyright (C) 2008, Judd Vinet v0.1 // 2008-10-06 """ import sys, os, pygame, pylirc, string # Config WIDTH, HEIGHT = 800, 600 FONT_FACE = 'bitstreamverasans' FONT_SIZE = 24 TITLE = 'Cheap HTPC v0.1' BG_IMAGE = '/home/jvinet/code/htpc/darkwood.jpg' MEDIA_EXTS = ['.avi', '.mp4', '.wmv', '.mov', '.mpeg4', '.mkv'] FULLSCREEN = False # maximum number of entries we can fit in the window items_per_page = int(HEIGHT / ((FONT_SIZE+2))) - 2 size = (WIDTH, HEIGHT) black = 0, 0, 0 white = 220, 220, 220 blue = 0, 0, 255 yellow = 255, 255, 0 lirc = None # Uncomment this to enable LIRC support #lirc = pylirc.init('pyhtpc', os.getenv('HOME')+'/.lircrc', 0) pygame.init() screen = pygame.display.set_mode(size) if FULLSCREEN: pygame.display.toggle_fullscreen() screen.fill(black) pygame.event.set_allowed([pygame.KEYUP, pygame.KEYDOWN]) font = pygame.font.SysFont(FONT_FACE, FONT_SIZE, True); mainmenu = [ ("Local Movies", "/home/jvinet/movies"), ("Music", "/home/jvinet/mp3"), ("Shares", "/home/jvinet/smb") ] class Menu: curmenu, topmenu = [], [] length, offset, depth, selected = 0, 0, 0, 0 cwd = None # None means we're at the main menu def init(self, topmenu): self.curmenu = self.topmenu = topmenu self.length = len(self.topmenu) self.image = pygame.image.load(BG_IMAGE).convert() def draw(self): # clear the screen screen.fill(black) # draw bg image screen.blit(self.image, self.image.get_rect()) # draw title txt = font.render(TITLE, True, (255,255,255)) screen.blit(txt, (0,0)) i = 0 for item in self.curmenu: y = ((i+1) * (FONT_SIZE+2)) + 15 if i == self.selected: s = pygame.Surface(((WIDTH-40), FONT_SIZE+2)); s.fill(blue) screen.blit(s, (20,y)) txt = font.render(item[0], True, yellow) else: txt = font.render(item[0], True, white) screen.blit(txt, (20,y)) i = i + 1 pygame.display.flip() def move_down(self): self.selected = self.selected + 1 if self.selected >= len(self.curmenu): if len(self.curmenu) + self.offset < self.length: # more items are available at this menu level, regenerate # the menu with a new offset self.offset, self.selected = self.offset+1, self.selected-1 self.curmenu, self.length = self.change_dir(self.cwd, self.offset) else: self.offset, self.selected = 0, 0 self.curmenu, self.length = self.change_dir(self.cwd, self.offset) def move_up(self): self.selected = self.selected - 1 if self.selected < 0: if self.offset > 0: # less (ie, above) items are available at this menu level, regenerate # the menu with a new offset self.offset, self.selected = self.offset-1, self.selected+1 self.curmenu, self.length = self.change_dir(self.cwd, self.offset) else: self.selected = min(items_per_page-1, self.length-1) self.offset = max(self.length - items_per_page, 0) self.curmenu, self.length = self.change_dir(self.cwd, self.offset) def move_back(self): if self.cwd is not None: self.depth, self.selected = self.depth-1, 0 if self.depth > 0: self.curmenu, self.length = self.change_dir('..', 0) self.cwd = os.getcwd() else: self.cwd, self.offset = None, 0 self.curmenu, self.length = self.change_dir(self.cwd, self.offset) def select(self): name,path = self.curmenu[self.selected] if os.path.isdir(path): self.cwd = path self.curmenu, self.length = self.change_dir(self.cwd, 0) self.depth, self.selected = self.depth+1, 0 self.draw() else: match = False for ext in MEDIA_EXTS: if path.endswith(ext): escape_chars = [' ', '&', '(', ')'] for c in escape_chars: path = path.replace(c, "\\"+c) cmd = "mplayer -fs %s" % (path) print "Exec:", cmd if FULLSCREEN: pygame.display.toggle_fullscreen() os.system(cmd) if FULLSCREEN: pygame.display.toggle_fullscreen() break def change_dir(self, dir, offset=0): if dir is None: return self.topmenu, len(self.topmenu) os.chdir(dir) menu = [] for root, dirs, files in os.walk(os.getcwd()): for d in dirs: menu.append((d, os.getcwd() + '/' + d)) for f in files: menu.append((f, os.getcwd() + '/' + f)) break # splice out this number from the full menu, # relative to the menu offset. return menu[offset:offset+items_per_page], len(menu) menu = Menu() menu.init(mainmenu) menu.draw() def do_action(action): if action == "QUIT": sys.exit() elif action == "DOWN": menu.move_down() menu.draw() elif action == "UP": menu.move_up() menu.draw() elif action == "BACK": menu.move_back() menu.draw() elif action == "SELECT": menu.select() # MAIN LOOP while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: do_action("QUIT") if event.type == pygame.KEYDOWN: if event.key == pygame.K_DOWN: do_action("DOWN") elif event.key == pygame.K_UP: do_action("UP") elif event.key == pygame.K_LEFT: do_action("BACK") elif event.key == pygame.K_RETURN: do_action("SELECT") elif event.key == pygame.K_RIGHT: do_action("SELECT") elif event.key == pygame.K_ESCAPE: do_action("QUIT") elif event.key == pygame.K_q: do_action("QUIT") if lirc is not None: codes = pylirc.nextcode() while codes is not None: for code in codes: do_action(code) codes = pylirc.nextcode() pygame.time.delay(100)