GTK+ GUI for configuring PD Buddy Sink devices
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

pd-buddy-gtk.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. #!/usr/bin/env python3
  2. import sys
  3. import pdbuddy
  4. import gi
  5. gi.require_version('Gtk', '3.0')
  6. from gi.repository import Gtk, Gio, GObject, GLib
  7. def comms_error_dialog(parent, e):
  8. dialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.ERROR,
  9. Gtk.ButtonsType.CLOSE, "Error communicating with device")
  10. dialog.format_secondary_text(e.strerror)
  11. dialog.run()
  12. dialog.destroy()
  13. class SelectListRowModel(GObject.GObject):
  14. def __init__(self, serport):
  15. GObject.GObject.__init__(self)
  16. self.serport = serport
  17. class SelectListStore(Gio.ListStore):
  18. def update_items(self):
  19. # Get a list of serial ports
  20. serports = list(pdbuddy.Sink.get_devices())
  21. # Mark ports to remove or add
  22. remove_list = []
  23. list_len = self.get_n_items()
  24. for i in range(list_len):
  25. remove = True
  26. for j in range(len(serports)):
  27. if serports[j] is not None and self.get_item(i).serport == serports[j]:
  28. serports[j] = None
  29. remove = False
  30. if remove:
  31. remove_list.append(i)
  32. # Remove the missing ones
  33. for i in remove_list:
  34. self.remove(i)
  35. # Add any new ports
  36. for port in serports:
  37. if port is not None:
  38. self.append(SelectListRowModel(port))
  39. def list_box_update_header_func(row, before, data):
  40. """Add a separator header to all rows but the first one"""
  41. name = Gtk.Buildable.get_name(row)
  42. # No header over the vrange-row to make it look like part of the row above
  43. if before is None or name == "vrange-row":
  44. row.set_header(None)
  45. return
  46. current = row.get_header()
  47. if current is None:
  48. current = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
  49. row.set_header(current)
  50. class SelectList(Gtk.Box):
  51. __gsignals__ = {
  52. 'row-activated': (GObject.SIGNAL_RUN_FIRST, None,
  53. (object,))
  54. }
  55. def __init__(self):
  56. Gtk.Box.__init__(self)
  57. self._model = None
  58. self._builder = Gtk.Builder()
  59. self._builder.add_from_file("data/select-stack.ui")
  60. self._builder.connect_signals(self)
  61. sl = self._builder.get_object("select-list")
  62. # Add separators to the list
  63. sl.set_header_func(list_box_update_header_func, None)
  64. self.pack_start(self._builder.get_object("select-stack"), True, True, 0)
  65. self.show_all()
  66. def bind_model(self, model, func):
  67. self._builder.get_object("select-list").bind_model(model, func)
  68. self._model = model
  69. self.reload()
  70. GLib.timeout_add(1000, self.reload)
  71. def reload(self):
  72. self._model.update_items()
  73. # Set the visible child
  74. stack = self._builder.get_object("select-stack")
  75. if self._model.get_n_items():
  76. stack.set_visible_child(self._builder.get_object("select-frame"))
  77. else:
  78. stack.set_visible_child(self._builder.get_object("select-none"))
  79. return True
  80. def on_select_list_row_activated(self, box, row):
  81. self.emit("row-activated", row.model.serport)
  82. class SelectListRow(Gtk.ListBoxRow):
  83. def __init__(self, model):
  84. Gtk.EventBox.__init__(self)
  85. self.model = model
  86. self._builder = Gtk.Builder()
  87. self._builder.add_from_file("data/select-list-row.ui")
  88. self._builder.connect_signals(self)
  89. name = self._builder.get_object("name")
  90. name.set_text('{} {} {}'.format(self.model.serport.manufacturer,
  91. self.model.serport.product,
  92. self.model.serport.serial_number))
  93. device = self._builder.get_object("device")
  94. device.set_text(self.model.serport.device)
  95. self.add(self._builder.get_object("grid"))
  96. self.show_all()
  97. def on_identify_clicked(self, button):
  98. window = self.get_toplevel()
  99. try:
  100. with pdbuddy.Sink(self.model.serport) as pdbs:
  101. pdbs.identify()
  102. except OSError as e:
  103. comms_error_dialog(window, e)
  104. return
  105. class PDOListRowModel(GObject.GObject):
  106. def __init__(self, pdo):
  107. GObject.GObject.__init__(self)
  108. self.pdo = pdo
  109. class PDOListStore(Gio.ListStore):
  110. def update_items(self, pdo_list):
  111. # Clear the list
  112. self.remove_all()
  113. # Add everything from the new list
  114. for pdo in pdo_list:
  115. self.append(PDOListRowModel(pdo))
  116. class PDOListRow(Gtk.ListBoxRow):
  117. oc_tooltips = [
  118. "I<sub>Peak</sub> = I<sub>OC</sub> (default)",
  119. """Overload Capabilities:
  120. 1. I<sub>Peak</sub> = 150% I<sub>OC</sub> for 1 ms @ 5% duty cycle (I<sub>Low</sub> = 97% I<sub>OC</sub> for 19 ms)
  121. 2. I<sub>Peak</sub> = 125% I<sub>OC</sub> for 2 ms @ 10% duty cycle (I<sub>Low</sub> = 97% I<sub>OC</sub> for 18 ms)
  122. 3. I<sub>Peak</sub> = 110% I<sub>OC</sub> for 10 ms @ 50% duty cycle (I<sub>Low</sub> = 90% I<sub>OC</sub> for 10 ms)""",
  123. """Overload Capabilities:
  124. 1. I<sub>Peak</sub> = 200% I<sub>OC</sub> for 1 ms @ 5% duty cycle (I<sub>Low</sub> = 95% I<sub>OC</sub> for 19 ms)
  125. 2. I<sub>Peak</sub> = 150% I<sub>OC</sub> for 2 ms @ 10% duty cycle (I<sub>Low</sub> = 94% I<sub>OC</sub> for 18 ms)
  126. 3. I<sub>Peak</sub> = 125% I<sub>OC</sub> for 10 ms @ 50% duty cycle (I<sub>Low</sub> = 75% I<sub>OC</sub> for 10 ms)""",
  127. """Overload Capabilities:
  128. 1. I<sub>Peak</sub> = 200% I<sub>OC</sub> for 1 ms @ 5% duty cycle (I<sub>Low</sub> = 95% I<sub>OC</sub> for 19 ms)
  129. 2. I<sub>Peak</sub> = 175% I<sub>OC</sub> for 2 ms @ 10% duty cycle (I<sub>Low</sub> = 92% I<sub>OC</sub> for 18 ms)
  130. 3. I<sub>Peak</sub> = 150% I<sub>OC</sub> for 10 ms @ 50% duty cycle (I<sub>Low</sub> = 50% I<sub>OC</sub> for 10 ms)"""
  131. ]
  132. def __init__(self, model):
  133. Gtk.ListBoxRow.__init__(self)
  134. self.model = model
  135. self.set_activatable(False)
  136. self.set_selectable(False)
  137. self.set_can_focus(False)
  138. # Make the widgets and populate them with info from the model
  139. # Main box
  140. box = Gtk.Box(Gtk.Orientation.HORIZONTAL, 12)
  141. box.set_homogeneous(True)
  142. box.set_margin_left(12)
  143. box.set_margin_right(12)
  144. box.set_margin_top(6)
  145. box.set_margin_bottom(6)
  146. # Type label
  147. if model.pdo.pdo_type == "fixed":
  148. type_text = "Fixed"
  149. elif model.pdo.pdo_type == "pps":
  150. type_text = "Programmable"
  151. elif model.pdo.pdo_type == "unknown":
  152. type_text = "Unknown"
  153. elif model.pdo.pdo_type == "typec_virtual":
  154. type_text = "Type-C Current"
  155. type_label = Gtk.Label(type_text)
  156. type_label.set_halign(Gtk.Align.START)
  157. box.pack_start(type_label, True, True, 0)
  158. # Voltage label
  159. if model.pdo.pdo_type == "fixed":
  160. voltage_label = Gtk.Label("{:g} V".format(model.pdo.v / 1000.0))
  161. voltage_label.set_halign(Gtk.Align.END)
  162. box.pack_start(voltage_label, True, True, 0)
  163. elif model.pdo.pdo_type == "pps":
  164. voltage_label = Gtk.Label("{:g}\u2013{:g} V".format(
  165. model.pdo.vmin / 1000.0, model.pdo.vmax / 1000.0))
  166. voltage_label.set_halign(Gtk.Align.END)
  167. box.pack_start(voltage_label, True, True, 0)
  168. # Right box
  169. right_box = Gtk.Box(Gtk.Orientation.HORIZONTAL, 6)
  170. right_box.set_halign(Gtk.Align.END)
  171. if model.pdo.pdo_type != "unknown":
  172. # Current label
  173. current_label = Gtk.Label("{:g} A".format(model.pdo.i / 1000.0))
  174. current_label.set_halign(Gtk.Align.END)
  175. right_box.pack_end(current_label, True, False, 0)
  176. # Over-current image(?)
  177. try:
  178. if model.pdo.peak_i > 0:
  179. oc_image = Gtk.Image.new_from_icon_name(
  180. "dialog-information-symbolic", Gtk.IconSize.BUTTON)
  181. oc_image.set_tooltip_markup(
  182. PDOListRow.oc_tooltips[model.pdo.peak_i])
  183. right_box.pack_end(oc_image, True, False, 0)
  184. except AttributeError:
  185. # If this isn't a fixed PDO, there's no peak_i attribute.
  186. # Not a problem, so just ignore the error.
  187. pass
  188. else:
  189. # PDO value
  190. text_label = Gtk.Label()
  191. text_label.set_markup("<tt>{}</tt>".format(model.pdo))
  192. right_box.pack_end(text_label, True, False, 0)
  193. box.pack_end(right_box, True, True, 0)
  194. self.add(box)
  195. self.show_all()
  196. class Handler:
  197. def __init__(self, builder):
  198. self.builder = builder
  199. self.serial_port = None
  200. self.vrange_set = False
  201. self.selectlist = None
  202. def on_pdb_window_realize(self, *args):
  203. # Get the list
  204. sb = self.builder.get_object("select-box")
  205. self.selectlist = SelectList()
  206. sb.pack_start(self.selectlist, True, True, 0)
  207. liststore = SelectListStore()
  208. self.selectlist.bind_model(liststore, SelectListRow)
  209. self.selectlist.connect("row-activated", self.on_select_list_row_activated)
  210. # Add separators to the configuration page lists
  211. sc_list = self.builder.get_object("sink-config-list")
  212. sc_list.set_header_func(list_box_update_header_func, None)
  213. pd_list = self.builder.get_object("power-delivery-list")
  214. pd_list.set_header_func(list_box_update_header_func, None)
  215. def on_pdb_window_delete_event(self, *args):
  216. Gtk.main_quit(*args)
  217. def on_select_list_row_activated(self, selectlist, serport):
  218. # Get relevant widgets
  219. voltage = self.builder.get_object("voltage-adjustment")
  220. vr_switch = self.builder.get_object("vrange-switch")
  221. vmin_adj = self.builder.get_object("vmin-adjustment")
  222. vmax_adj = self.builder.get_object("vmax-adjustment")
  223. current = self.builder.get_object("current-adjustment")
  224. current_dim = self.builder.get_object("current-dimension")
  225. giveback = self.builder.get_object("giveback-switch")
  226. pd_frame = self.builder.get_object("power-delivery-frame")
  227. output = self.builder.get_object("output-switch")
  228. cap_row = self.builder.get_object("source-cap-row")
  229. cap_warning = self.builder.get_object("source-cap-warning")
  230. cap_label = self.builder.get_object("short-source-cap-label")
  231. cap_arrow = self.builder.get_object("source-cap-arrow")
  232. self.serial_port = serport
  233. window = self.builder.get_object("pdb-window")
  234. try:
  235. with pdbuddy.Sink(self.serial_port) as pdbs:
  236. try:
  237. pdbs.load()
  238. except KeyError:
  239. # If there's no configuration, we don't want to fail. We
  240. # do want to display no configuration though
  241. self.cfg = pdbuddy.SinkConfig(
  242. status=pdbuddy.SinkStatus.VALID,
  243. flags=pdbuddy.SinkFlags.NONE, v=0, vmin=0, vmax=0,
  244. i=0, idim=pdbuddy.SinkDimension.CURRENT)
  245. else:
  246. self.cfg = pdbs.get_tmpcfg()
  247. if self.cfg.vmin is None:
  248. self.cfg = self.cfg._replace(vmin=0)
  249. if self.cfg.vmax is None:
  250. self.cfg = self.cfg._replace(vmax=0)
  251. except OSError as e:
  252. comms_error_dialog(window, e)
  253. return
  254. self._store_device_settings()
  255. self._set_save_button_visibility()
  256. # Set giveback switch state
  257. giveback.set_active(bool(self.cfg.flags & pdbuddy.SinkFlags.GIVEBACK))
  258. # Get voltage and current from device and load them into the GUI
  259. voltage.set_value(self.cfg.v/1000)
  260. vr_switch.set_active(self.cfg.vmin != 0 or self.cfg.vmax != 0)
  261. self.vrange_set = True
  262. vmin_adj.set_value(self.cfg.vmin/1000)
  263. self._set_hv_pref_image()
  264. vmax_adj.set_value(self.cfg.vmax/1000)
  265. self.vrange_set = False
  266. if self.cfg.idim == pdbuddy.SinkDimension.CURRENT:
  267. current_dim.set_active_id("idim-current")
  268. elif self.cfg.idim == pdbuddy.SinkDimension.POWER:
  269. current_dim.set_active_id("idim-power")
  270. elif self.cfg.idim == pdbuddy.SinkDimension.RESISTANCE:
  271. current_dim.set_active_id("idim-resistance")
  272. current.set_value(self.cfg.i/1000)
  273. # Set PD frame visibility and output switch state
  274. try:
  275. with pdbuddy.Sink(self.serial_port) as pdbs:
  276. output.set_state(pdbs.output)
  277. except KeyError:
  278. pd_frame.set_visible(False)
  279. else:
  280. pd_frame.set_visible(True)
  281. # TODO: do these next things repeatedly
  282. # Get the Source_Capabilities
  283. with pdbuddy.Sink(self.serial_port) as pdbs:
  284. caps = pdbs.get_source_cap()
  285. # Update the warning icon
  286. cap_warning.set_visible(not pdbuddy.follows_power_rules(caps))
  287. # Update the text in the capability label
  288. if caps:
  289. cap_label.set_text('{:g} W'.format(pdbuddy.calculate_pdp(caps)))
  290. else:
  291. cap_label.set_text('None')
  292. # Make the row insensitive if there are no capabilities
  293. cap_row.set_activatable(caps)
  294. cap_arrow.set_visible(caps)
  295. # Show the Sink page
  296. hst = self.builder.get_object("header-stack")
  297. hsink = self.builder.get_object("header-sink")
  298. hsink.set_title('{} {} {}'.format(serport.manufacturer,
  299. serport.product,
  300. serport.serial_number))
  301. hsink.set_subtitle(serport.device)
  302. hst.set_visible_child(hsink)
  303. st = self.builder.get_object("stack")
  304. sink = self.builder.get_object("sink")
  305. st.set_visible_child(sink)
  306. # Ping the Sink repeatedly
  307. GLib.timeout_add(1000, self._ping)
  308. def _ping(self):
  309. """Ping the device we're configuring, showing to the list on failure"""
  310. if self.serial_port is None:
  311. self.selectlist.reload()
  312. self.on_header_sink_back_clicked(None)
  313. return False
  314. try:
  315. with pdbuddy.Sink(self.serial_port) as pdbs:
  316. pdbs.send_command("")
  317. return True
  318. except:
  319. self.selectlist.reload()
  320. self.on_header_sink_back_clicked(None)
  321. return False
  322. def on_header_sink_back_clicked(self, data):
  323. self.serial_port = None
  324. # Show the Select page
  325. hst = self.builder.get_object("header-stack")
  326. hselect = self.builder.get_object("header-select")
  327. hst.set_visible_child(hselect)
  328. st = self.builder.get_object("stack")
  329. select = self.builder.get_object("select")
  330. st.set_visible_child(select)
  331. def on_sink_save_clicked(self, button):
  332. window = self.builder.get_object("pdb-window")
  333. try:
  334. with pdbuddy.Sink(self.serial_port) as pdbs:
  335. pdbs.set_tmpcfg(self.cfg)
  336. pdbs.write()
  337. self._store_device_settings()
  338. self._set_save_button_visibility()
  339. except OSError as e:
  340. comms_error_dialog(window, e)
  341. self.on_header_sink_back_clicked(None)
  342. def _store_device_settings(self):
  343. """Store the settings that were loaded from the device"""
  344. self.cfg_clean = self.cfg
  345. def _set_save_button_visibility(self):
  346. """Show the save button if there are new settings to save"""
  347. # Get relevant widgets
  348. rev = self.builder.get_object("sink-save-revealer")
  349. # Set visibility
  350. rev.set_reveal_child(self.cfg != self.cfg_clean)
  351. def on_voltage_adjustment_value_changed(self, adj):
  352. self.cfg = self.cfg._replace(v=int(adj.get_value() * 1000))
  353. self._set_save_button_visibility()
  354. def on_vrange_switch_state_set(self, switch, state):
  355. row = self.builder.get_object("vrange-row")
  356. vmin_adj = self.builder.get_object("vmin-adjustment")
  357. vmax_adj = self.builder.get_object("vmax-adjustment")
  358. row.set_visible(state)
  359. if not state:
  360. self.cfg = self.cfg._replace(vmin=0, vmax=0)
  361. self.vrange_set = True
  362. vmin_adj.set_value(self.cfg.vmin/1000)
  363. vmax_adj.set_value(self.cfg.vmax/1000)
  364. self.vrange_set = False
  365. self._set_save_button_visibility()
  366. def on_vmin_adjustment_value_changed(self, adj):
  367. if not self.vrange_set:
  368. self.cfg = self.cfg._replace(vmin=int(adj.get_value() * 1000))
  369. # Update vmax if necessary
  370. vmax_adj = self.builder.get_object("vmax-adjustment")
  371. if adj.get_value() > vmax_adj.get_value():
  372. vmax_adj.set_value(adj.get_value())
  373. self._set_save_button_visibility()
  374. def on_vmax_adjustment_value_changed(self, adj):
  375. if not self.vrange_set:
  376. self.cfg = self.cfg._replace(vmax=int(adj.get_value() * 1000))
  377. # Update vmin if necessary
  378. vmin_adj = self.builder.get_object("vmin-adjustment")
  379. if adj.get_value() < vmin_adj.get_value():
  380. vmin_adj.set_value(adj.get_value())
  381. self._set_save_button_visibility()
  382. def on_hv_preferred_button_clicked(self, button):
  383. self.cfg = self.cfg._replace(
  384. flags=self.cfg.flags^pdbuddy.SinkFlags.HV_PREFERRED)
  385. self._set_hv_pref_image()
  386. self._set_save_button_visibility()
  387. def _set_hv_pref_image(self):
  388. hv_pref = self.builder.get_object("hv-preferred-button")
  389. image = self.builder.get_object("order-image")
  390. if self.cfg.flags & pdbuddy.SinkFlags.HV_PREFERRED:
  391. image.set_from_icon_name("go-previous-symbolic",
  392. Gtk.IconSize.BUTTON)
  393. else:
  394. image.set_from_icon_name("go-next-symbolic", Gtk.IconSize.BUTTON)
  395. def on_current_dimension_changed(self, cb):
  396. item = cb.get_active_id()
  397. value = self.builder.get_object("current-adjustment")
  398. unit = self.builder.get_object("current-unit")
  399. if item == "idim-current":
  400. if self.cfg.idim == pdbuddy.SinkDimension.POWER:
  401. self.cfg = self.cfg._replace(i=self.cfg.i/self.cfg.v*1000.0)
  402. elif self.cfg.idim == pdbuddy.SinkDimension.RESISTANCE:
  403. self.cfg = self.cfg._replace(i=self.cfg.v/self.cfg.i*1000.0)
  404. value.configure(self.cfg.i / 1000.0, 0, 5, 0.1, 1, 0)
  405. idim = pdbuddy.SinkDimension.CURRENT
  406. unit.set_text("A")
  407. if item == "idim-power":
  408. if self.cfg.idim == pdbuddy.SinkDimension.CURRENT:
  409. self.cfg = self.cfg._replace(i=self.cfg.i*self.cfg.v/1000.0)
  410. elif self.cfg.idim == pdbuddy.SinkDimension.RESISTANCE:
  411. self.cfg = self.cfg._replace(
  412. i=self.cfg.v*self.cfg.v/self.cfg.i)
  413. idim = pdbuddy.SinkDimension.POWER
  414. value.configure(self.cfg.i / 1000.0, 0, 100, 1, 10, 0)
  415. unit.set_text("W")
  416. if item == "idim-resistance":
  417. if self.cfg.idim == pdbuddy.SinkDimension.CURRENT:
  418. self.cfg = self.cfg._replace(i=self.cfg.v/self.cfg.i*1000.0)
  419. elif self.cfg.idim == pdbuddy.SinkDimension.POWER:
  420. self.cfg = self.cfg._replace(
  421. i=self.cfg.v*self.cfg.v/self.cfg.i)
  422. idim = pdbuddy.SinkDimension.RESISTANCE
  423. value.configure(self.cfg.i / 1000.0, 0, 655.35, 1, 10, 0)
  424. unit.set_text("\u03a9")
  425. self.cfg = self.cfg._replace(idim=idim)
  426. self._set_save_button_visibility()
  427. def on_current_adjustment_value_changed(self, adj):
  428. self.cfg = self.cfg._replace(i=int(adj.get_value() * 1000))
  429. self._set_save_button_visibility()
  430. def on_giveback_switch_state_set(self, switch, state):
  431. if state:
  432. self.cfg = self.cfg._replace(flags=self.cfg.flags|pdbuddy.SinkFlags.GIVEBACK)
  433. else:
  434. self.cfg = self.cfg._replace(flags=self.cfg.flags&~pdbuddy.SinkFlags.GIVEBACK)
  435. self._set_save_button_visibility()
  436. def on_output_switch_state_set(self, switch, state):
  437. with pdbuddy.Sink(self.serial_port) as pdbs:
  438. pdbs.output = state
  439. def on_source_cap_row_activated(self, box, row):
  440. # Find which row was clicked
  441. sc_row = self.builder.get_object("source-cap-row")
  442. if row != sc_row:
  443. # If it's not the source-cap-row, leave
  444. return
  445. # Get the source capabilities
  446. with pdbuddy.Sink(self.serial_port) as pdbs:
  447. caps = pdbs.get_source_cap()
  448. if not caps:
  449. # If there are no capabilities, don't show a dialog
  450. return
  451. # Create the dialog
  452. window = self.builder.get_object("pdb-window")
  453. dialog_builder = Gtk.Builder.new_from_file("data/src-cap-dialog.ui")
  454. dialog = dialog_builder.get_object("src-cap-dialog")
  455. dialog.set_transient_for(window)
  456. dialog.get_content_area().set_border_width(0)
  457. # Populate PD Power
  458. d_power = dialog_builder.get_object("power-label")
  459. d_power.set_text("{:g} W".format(pdbuddy.calculate_pdp(caps)))
  460. # Warning icon
  461. cap_warning = dialog_builder.get_object("source-cap-warning")
  462. cap_warning.set_visible(not pdbuddy.follows_power_rules(caps))
  463. # Populate Information
  464. d_info_header = dialog_builder.get_object("info-header")
  465. d_info = dialog_builder.get_object("info-label")
  466. # Make the string to display
  467. info_str = ""
  468. try:
  469. if caps[0].dual_role_pwr:
  470. info_str += "Dual-Role Power\n"
  471. if caps[0].usb_suspend:
  472. info_str += "USB Suspend Supported\n"
  473. if caps[0].unconstrained_pwr:
  474. info_str += "Unconstrained Power\n"
  475. if caps[0].usb_comms:
  476. info_str += "USB Communications Capable\n"
  477. if caps[0].dual_role_data:
  478. info_str += "Dual-Role Data\n"
  479. info_str = info_str[:-1]
  480. except AttributeError:
  481. # If we have a typec_virtual PDO, there will be AttributeErrors
  482. # from the above. Not a problem, so just pass.
  483. pass
  484. # Set the text and label visibility
  485. d_info.set_text(info_str)
  486. d_info_header.set_visible(info_str)
  487. d_info.set_visible(info_str)
  488. # PDO list
  489. d_list = dialog_builder.get_object("src-cap-list")
  490. d_list.set_header_func(list_box_update_header_func, None)
  491. model = PDOListStore()
  492. d_list.bind_model(model, PDOListRow)
  493. model.update_items(caps)
  494. # Show the dialog
  495. dialog.run()
  496. dialog.destroy()
  497. class Application(Gtk.Application):
  498. def __init__(self, *args, **kwargs):
  499. super().__init__(*args, application_id="com.clayhobbs.pd-buddy-gtk",
  500. **kwargs)
  501. self.window = None
  502. def do_startup(self):
  503. Gtk.Application.do_startup(self)
  504. self.builder = Gtk.Builder.new_from_file("data/pd-buddy-gtk.ui")
  505. self.builder.connect_signals(Handler(self.builder))
  506. def do_activate(self):
  507. # We only allow a single window and raise any existing ones
  508. if not self.window:
  509. # Windows are associated with the application
  510. # when the last one is closed the application shuts down
  511. self.window = self.builder.get_object("pdb-window")
  512. self.add_window(self.window)
  513. self.window.set_wmclass("PD Buddy Configuration",
  514. "PD Buddy Configuration")
  515. self.window.present()
  516. def run():
  517. app = Application()
  518. app.run(sys.argv)
  519. if __name__ == "__main__":
  520. run()