#!/usr/bin/env python3 import argparse import sys import sdl2 import sdl2.ext import sdl2.sdlttf as ttf WIDTH = 1280 HEIGHT = 720 FONT = "/usr/share/fonts/open-sans/OpenSans-Semibold.ttf" def debug(msg, enabled): if enabled: print(f"[DEBUG] {msg}", file=sys.stderr) class ControllerManager: def __init__(self, ignore, debug_enabled): self.ignore = ignore self.debug_enabled = debug_enabled self.controllers = {} self.unsupported = {} def scan(self): self.controllers.clear() self.unsupported.clear() count = sdl2.SDL_NumJoysticks() for i in range(count): name_ptr = sdl2.SDL_JoystickNameForIndex(i) name = ( name_ptr.decode() if name_ptr else "Unknown Device" ) if sdl2.SDL_IsGameController(i): if any( x.lower() in name.lower() for x in self.ignore ): debug( f"Ignoring {name}", self.debug_enabled ) continue c = sdl2.SDL_GameControllerOpen(i) if c: joy = sdl2.SDL_GameControllerGetJoystick(c) instance = sdl2.SDL_JoystickInstanceID( joy ) self.controllers[instance] = { "name": name, "controller": c } debug( f"Added controller {name}", self.debug_enabled ) else: self.unsupported[i] = name debug( f"Unsupported joystick {name}", self.debug_enabled ) def add(self, index): name_ptr = sdl2.SDL_JoystickNameForIndex(index) name = ( name_ptr.decode() if name_ptr else "Unknown Device" ) if sdl2.SDL_IsGameController(index): c = sdl2.SDL_GameControllerOpen(index) if c: joy = sdl2.SDL_GameControllerGetJoystick(c) instance = sdl2.SDL_JoystickInstanceID( joy ) self.controllers[instance] = { "name": name, "controller": c } debug( f"Hotplugged {name}", self.debug_enabled ) else: self.unsupported[index] = name debug( f"Hotplugged unsupported {name}", self.debug_enabled ) def remove(self, instance): if instance in self.controllers: name = self.controllers[instance]["name"] sdl2.SDL_GameControllerClose( self.controllers[instance]["controller"] ) del self.controllers[instance] debug( f"Removed {name}", self.debug_enabled ) def draw_text(renderer, font, text, x, y, color): surface = ttf.TTF_RenderUTF8_Blended( font, text.encode("utf-8"), color ) if not surface: print( "TTF render failed: " + ttf.TTF_GetError().decode(), file=sys.stderr ) return texture = sdl2.SDL_CreateTextureFromSurface( renderer, surface ) if not texture: print( "Texture creation failed: " + sdl2.SDL_GetError().decode(), file=sys.stderr ) sdl2.SDL_FreeSurface(surface) return rect = sdl2.SDL_Rect() rect.x = x rect.y = y rect.w = surface.contents.w rect.h = surface.contents.h result = sdl2.SDL_RenderCopy( renderer, texture, None, rect ) if result != 0: print( "RenderCopy failed: " + sdl2.SDL_GetError().decode(), file=sys.stderr ) sdl2.SDL_DestroyTexture(texture) sdl2.SDL_FreeSurface(surface) def main(): parser = argparse.ArgumentParser() parser.add_argument("question") parser.add_argument( "--ignore", nargs="*", default=[] ) parser.add_argument( "--debug", action="store_true" ) args = parser.parse_args() sdl2.SDL_Init( sdl2.SDL_INIT_VIDEO | sdl2.SDL_INIT_GAMECONTROLLER | sdl2.SDL_INIT_JOYSTICK ) ttf.TTF_Init() window = sdl2.SDL_CreateWindow( args.question.encode(), sdl2.SDL_WINDOWPOS_CENTERED, sdl2.SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, sdl2.SDL_WINDOW_FULLSCREEN ) renderer = sdl2.SDL_CreateRenderer( window, -1, sdl2.SDL_RENDERER_ACCELERATED ) info = sdl2.SDL_RendererInfo() sdl2.SDL_GetRendererInfo(renderer, info) print( "Renderer:", info.name.decode(), file=sys.stderr ) font = ttf.TTF_OpenFont( FONT.encode(), 42 ) manager = ControllerManager( args.ignore, args.debug ) manager.scan() event = sdl2.SDL_Event() running = True selected_controller = None selected_time = 0 while running: while sdl2.SDL_PollEvent(event): if event.type == sdl2.SDL_CONTROLLERDEVICEADDED: manager.add( event.cdevice.which ) elif event.type == sdl2.SDL_JOYDEVICEADDED: manager.add( event.jdevice.which ) elif event.type == sdl2.SDL_JOYDEVICEREMOVED: manager.remove( event.jdevice.which ) elif event.type == sdl2.SDL_CONTROLLERBUTTONDOWN: instance = event.cbutton.which if instance in manager.controllers: selected_controller = manager.controllers[instance]["name"] debug( f"Selected {selected_controller}", args.debug ) selected_time = sdl2.SDL_GetTicks() # draw sdl2.SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255 ) sdl2.SDL_RenderClear(renderer) draw_text( renderer, font, args.question, 50, 50, sdl2.SDL_Color(255,255,255,255) ) y = 150 for c in manager.controllers.values(): draw_text( renderer, font, "✓ " + c["name"], 100, y, sdl2.SDL_Color(255,255,255,255) ) y += 60 for name in manager.unsupported.values(): draw_text( renderer, font, "✗ " + name, 100, y, sdl2.SDL_Color(100,100,100,255) ) y += 60 if selected_controller: elapsed = sdl2.SDL_GetTicks() - selected_time draw_text( renderer, font, "Selected:", 100, 550, sdl2.SDL_Color(255,255,255,255) ) draw_text( renderer, font, selected_controller, 100, 610, sdl2.SDL_Color(0,255,0,255) ) sdl2.SDL_RenderPresent(renderer) if elapsed > 2000: print(selected_controller) running = False continue else: sdl2.SDL_RenderPresent(renderer) # cleanup for c in manager.controllers.values(): sdl2.SDL_GameControllerClose( c["controller"] ) ttf.TTF_CloseFont(font) sdl2.SDL_DestroyRenderer(renderer) sdl2.SDL_DestroyWindow(window) ttf.TTF_Quit() sdl2.SDL_Quit() if __name__ == "__main__": sys.exit(main())