1#!/usr/bin/python3 2 3import urwid 4import pykms 5 6def exit_on_q(key): 7 if key in ('q', 'Q'): 8 raise urwid.ExitMainLoop() 9 elif key == 'a': 10 apply_mode() 11 12alarm_handle = None 13 14def recalc_info(l, d): 15 global alarm_handle 16 17 alarm_handle = None 18 19 for w in recalc_list: 20 w.recalc() 21 22def div_or_zero(n, d): 23 if d == 0: 24 return 0 25 else: 26 return n / d 27 28class MyIntEdit(urwid.IntEdit): 29 _metaclass_ = urwid.signals.MetaSignals 30 signals = ['value_change'] 31 32 def __init__(self, caption, calc=None): 33 self._myval = 0 34 self._disable_change = False 35 self._calc = calc 36 self._updlist = None 37 38 super().__init__(caption, 0) 39 40 def set_edit_text(self, text): 41 global alarm_handle 42 43 super().set_edit_text(text) 44 newtext = super().get_edit_text() 45 new_val = int(newtext) if newtext != "" else 0 46 if new_val != self._myval: 47 self._myval = new_val 48 if not self._disable_change: 49 urwid.emit_signal(self, 'value_change', self, self._myval) 50 51 if alarm_handle == None: 52 alarm_handle = loop.set_alarm_in(0, recalc_info) 53 54 if self._updlist != None: 55 for w in self._updlist: 56 w.recalc() 57 58 def recalc(self): 59 self._disable_change = True 60 self.set_val(self._calc()) 61 self._disable_change = False 62 63 def set_val(self, val): 64 self.set_edit_text(str(int(val))) 65 66 def get_val(self): 67 return self._myval 68 69 def set_updlist(self, list): 70 self._updlist = list 71 72 def keypress(self, size, key): 73 if key == '+': 74 self.set_edit_text(str(self.value() + 1)) 75 elif key == '-': 76 self.set_edit_text(str(self.value() - 1)) 77 else: 78 return super().keypress(size, key) 79 80class MyIntText(urwid.Text): 81 def __init__(self, fmt, calc=None): 82 super().__init__("") 83 self._fmt = fmt 84 self._calc = calc 85 86 def recalc(self): 87 val = self._calc() 88 super().set_text(self._fmt.format(val)) 89 90def khz_to_ps(khz): 91 if khz == 0: 92 return 0 93 else: 94 return 1.0 / khz * 1000 * 1000 * 1000 95 96def khz_to_us(khz): 97 if khz == 0: 98 return 0 99 else: 100 return 1.0 / khz * 1000 101 102pclk_khz_widget = MyIntEdit(u"pclk (kHz) ") 103pclk_ps_widget = MyIntText(fmt="pclk {:.2f} ps", calc = lambda: khz_to_ps(pclk_khz_widget.get_val())) 104 105pclk_widgets = [pclk_khz_widget, pclk_ps_widget] 106 107pclk_columns = urwid.LineBox(urwid.Columns(pclk_widgets), title = "Pixel clock") 108 109# Horizontal widgets 110 111hdisp_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp2_widget.get_val()) 112hfp_widget = MyIntEdit(u"hfp ", calc = lambda: hss_widget.get_val() - hdisp_widget.get_val()) 113hsw_widget = MyIntEdit(u"hsw ", calc = lambda: hse_widget.get_val() - hss_widget.get_val()) 114hbp_widget = MyIntEdit(u"hbp ", calc = lambda: htot_widget.get_val() - hse_widget.get_val()) 115 116hdisp2_widget = MyIntEdit(u"hdisp ", calc = lambda: hdisp_widget.get_val()) 117hss_widget = MyIntEdit(u"hss ", 118 calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val()) 119hse_widget = MyIntEdit(u"hse ", 120 calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val()) 121htot_widget = MyIntEdit(u"htot ", 122 calc = lambda: hdisp_widget.get_val() + hfp_widget.get_val() + hsw_widget.get_val() + hbp_widget.get_val()) 123 124hwidgets1 = [hdisp_widget, hfp_widget, hsw_widget, hbp_widget] 125hwidgets2 = [hdisp2_widget, hss_widget, hse_widget, htot_widget] 126 127horiz_pile1 = urwid.Pile(hwidgets1) 128horiz_pile2 = urwid.Pile(hwidgets2) 129 130h_columns = urwid.LineBox(urwid.Columns([(15, horiz_pile1), (15, horiz_pile2)]), title = "Horizontal") 131 132# Vertical columns 133 134vdisp_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp2_widget.get_val()) 135vfp_widget = MyIntEdit(u"vfp ", calc = lambda: vss_widget.get_val() - vdisp_widget.get_val()) 136vsw_widget = MyIntEdit(u"vsw ", calc = lambda: vse_widget.get_val() - vss_widget.get_val()) 137vbp_widget = MyIntEdit(u"vbp ", calc = lambda: vtot_widget.get_val() - vse_widget.get_val()) 138 139vdisp2_widget = MyIntEdit(u"vdisp ", calc = lambda: vdisp_widget.get_val()) 140vss_widget = MyIntEdit(u"vss ", 141 calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val()) 142vse_widget = MyIntEdit(u"vse ", 143 calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val()) 144vtot_widget = MyIntEdit(u"vtot ", 145 calc = lambda: vdisp_widget.get_val() + vfp_widget.get_val() + vsw_widget.get_val() + vbp_widget.get_val()) 146 147vwidgets1 = [vdisp_widget, vfp_widget, vsw_widget, vbp_widget] 148vwidgets2 = [vdisp2_widget, vss_widget, vse_widget, vtot_widget] 149 150vert_pile1 = urwid.Pile(vwidgets1) 151vert_pile2 = urwid.Pile(vwidgets2) 152 153v_columns = urwid.LineBox(urwid.Columns([(15, vert_pile1), (15, vert_pile2)]), title = "Vertical") 154 155# Info widgets 156 157line_us_widget = MyIntText(fmt="line {:.2f} us", 158 calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val()) 159line_khz_widget = MyIntText(fmt="line {:.2f} kHz", 160 calc = lambda: div_or_zero(pclk_khz_widget.get_val(), htot_widget.get_val())) 161 162frame_tot_widget = MyIntText(fmt="tot {} pix", 163 calc = lambda: htot_widget.get_val() * vtot_widget.get_val()) 164frame_us_widget = MyIntText(fmt="frame {:.2f} ms", 165 calc = lambda: khz_to_us(pclk_khz_widget.get_val()) * htot_widget.get_val() * vtot_widget.get_val() / 1000) 166frame_khz_widget = MyIntText(fmt="frame {:.2f} Hz", 167 calc = lambda: div_or_zero(pclk_khz_widget.get_val() * 1000, htot_widget.get_val() * vtot_widget.get_val())) 168 169info_box = urwid.LineBox(urwid.Pile([line_us_widget, line_khz_widget, urwid.Divider(), frame_tot_widget, frame_us_widget, frame_khz_widget]), title = "Info") 170 171# Set update lists 172 173recalc_list = [ pclk_ps_widget, line_us_widget, line_khz_widget, frame_tot_widget, frame_us_widget, frame_khz_widget ] 174 175hdisp_widget.set_updlist([hdisp2_widget, hss_widget, hse_widget, htot_widget]) 176hfp_widget.set_updlist([hss_widget, hse_widget, htot_widget]) 177hsw_widget.set_updlist([hse_widget, htot_widget]) 178hbp_widget.set_updlist([htot_widget]) 179hdisp2_widget.set_updlist([hdisp_widget, hfp_widget]) 180hss_widget.set_updlist([hfp_widget, hsw_widget]) 181hse_widget.set_updlist([hsw_widget, hbp_widget]) 182htot_widget.set_updlist([hbp_widget]) 183 184vdisp_widget.set_updlist([vdisp2_widget, vss_widget, vse_widget, vtot_widget]) 185vfp_widget.set_updlist([vss_widget, vse_widget, vtot_widget]) 186vsw_widget.set_updlist([vse_widget, vtot_widget]) 187vbp_widget.set_updlist([vtot_widget]) 188vdisp2_widget.set_updlist([vdisp_widget, vfp_widget]) 189vss_widget.set_updlist([vfp_widget, vsw_widget]) 190vse_widget.set_updlist([vsw_widget, vbp_widget]) 191vtot_widget.set_updlist([vbp_widget]) 192 193# Flags 194 195fb = None 196 197DRM_MODE_FLAG_PHSYNC = (1<<0) 198DRM_MODE_FLAG_NHSYNC = (1<<1) 199DRM_MODE_FLAG_PVSYNC = (1<<2) 200DRM_MODE_FLAG_NVSYNC = (1<<3) 201DRM_MODE_FLAG_INTERLACE = (1<<4) 202DRM_MODE_FLAG_DBLCLK = (1<<12) 203 204def mode_is_ilace(mode): 205 return (mode.flags & DRM_MODE_FLAG_INTERLACE) != 0 206 207def apply_mode(): 208 global fb 209 210 mode = pykms.Videomode() 211 mode.clock = pclk_khz_widget.get_val() 212 213 mode.hdisplay = hdisp2_widget.get_val() 214 mode.hsync_start = hss_widget.get_val() 215 mode.hsync_end = hse_widget.get_val() 216 mode.htotal = htot_widget.get_val() 217 218 mode.vdisplay = vdisp2_widget.get_val() 219 mode.vsync_start = vss_widget.get_val() 220 mode.vsync_end = vse_widget.get_val() 221 mode.vtotal = vtot_widget.get_val() 222 223 if ilace_box.state: 224 mode.flags |= DRM_MODE_FLAG_INTERLACE 225 226 if dblclk_box.state: 227 mode.flags |= DRM_MODE_FLAG_DBLCLK 228 229 if hsync_pol.state == True: 230 mode.flags |= DRM_MODE_FLAG_PHSYNC 231 elif hsync_pol.state == False: 232 mode.flags |= DRM_MODE_FLAG_NHSYNC 233 234 if vsync_pol.state == True: 235 mode.flags |= DRM_MODE_FLAG_PVSYNC 236 elif vsync_pol.state == False: 237 mode.flags |= DRM_MODE_FLAG_NVSYNC 238 239 fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24"); 240 pykms.draw_test_pattern(fb); 241 242 crtc.set_mode(conn, fb, mode) 243 244def read_mode(mode): 245 pclk_khz_widget.set_val(mode.clock) 246 hdisp2_widget.set_val(mode.hdisplay) 247 hss_widget.set_val(mode.hsync_start) 248 hse_widget.set_val(mode.hsync_end) 249 htot_widget.set_val(mode.htotal) 250 251 vdisp2_widget.set_val(mode.vdisplay) 252 vss_widget.set_val(mode.vsync_start) 253 vse_widget.set_val(mode.vsync_end) 254 vtot_widget.set_val(mode.vtotal) 255 256 ilace_box.set_state(mode_is_ilace(mode)) 257 dblclk_box.set_state((mode.flags & DRM_MODE_FLAG_DBLCLK) != 0) 258 259 sync = 'mixed' 260 if (mode.flags & DRM_MODE_FLAG_PHSYNC) != 0: 261 sync = True 262 elif (mode.flags & DRM_MODE_FLAG_NHSYNC) != 0: 263 sync = False 264 hsync_pol.set_state(sync) 265 266 sync = 'mixed' 267 if (mode.flags & DRM_MODE_FLAG_PVSYNC) != 0: 268 sync = True 269 elif (mode.flags & DRM_MODE_FLAG_NVSYNC) != 0: 270 sync = False 271 vsync_pol.set_state(sync) 272 273def apply_press(w): 274 apply_mode() 275 276ilace_box = urwid.CheckBox('interlace') 277hsync_pol = urwid.CheckBox('hsync positive', has_mixed=True) 278vsync_pol = urwid.CheckBox('vsync positive', has_mixed=True) 279dblclk_box = urwid.CheckBox('double clock') 280 281flags_pile = urwid.LineBox(urwid.Pile([ilace_box, hsync_pol, vsync_pol, dblclk_box]), title = "Flags") 282 283apply_button = urwid.LineBox(urwid.Padding(urwid.Button('apply', on_press=apply_press))) 284 285# Main 286 287def mode_press(w, mode): 288 read_mode(mode) 289 290def mode_to_str(mode): 291 return "{}@{}{}".format(mode.name, mode.vrefresh, "i" if mode_is_ilace(mode) else "") 292 293mode_buttons = [] 294 295card = pykms.Card() 296conn = card.get_first_connected_connector() 297crtc = conn.get_current_crtc() 298modes = conn.get_modes() 299i = 0 300for m in modes: 301 mode_buttons.append(urwid.Button(mode_to_str(m), on_press=mode_press, user_data=m)) 302 i += 1 303 304modes_pile = urwid.LineBox(urwid.Pile(mode_buttons), title = "Video modes") 305 306main_pile = urwid.Pile([modes_pile, pclk_columns, urwid.Columns([ h_columns, v_columns ]), info_box, flags_pile, apply_button]) 307 308main_columns = urwid.Filler(main_pile, valign='top') 309 310loop = urwid.MainLoop(main_columns, unhandled_input=exit_on_q, handle_mouse=False) 311 312# select the first mode 313mode_press(None, modes[0]) 314 315loop.run() 316 317fb = None 318