Stefan Schuermans commited on 2013-11-23 22:31:06
Showing 2 changed files, with 264 additions and 11 deletions.
| ... | ... |
@@ -1,6 +1,175 @@ |
| 1 | 1 |
<?xml version="1.0" encoding="UTF-8"?> |
| 2 | 2 |
<interface> |
| 3 | 3 |
<!-- interface-requires gtk+ 3.0 --> |
| 4 |
+ <object class="GtkDialog" id="DialogDestination"> |
|
| 5 |
+ <property name="can_focus">False</property> |
|
| 6 |
+ <property name="border_width">5</property> |
|
| 7 |
+ <property name="title" translatable="yes">BlinkenArea Sync GUI - Extras Destination Address...</property> |
|
| 8 |
+ <property name="type_hint">dialog</property> |
|
| 9 |
+ <child internal-child="vbox"> |
|
| 10 |
+ <object class="GtkBox" id="DiaDestVBox"> |
|
| 11 |
+ <property name="can_focus">False</property> |
|
| 12 |
+ <property name="orientation">vertical</property> |
|
| 13 |
+ <property name="spacing">2</property> |
|
| 14 |
+ <child internal-child="action_area"> |
|
| 15 |
+ <object class="GtkButtonBox" id="DiaDestAction"> |
|
| 16 |
+ <property name="can_focus">False</property> |
|
| 17 |
+ <property name="layout_style">end</property> |
|
| 18 |
+ <child> |
|
| 19 |
+ <object class="GtkButton" id="DiaDestBtnCancel"> |
|
| 20 |
+ <property name="label">gtk-cancel</property> |
|
| 21 |
+ <property name="use_action_appearance">False</property> |
|
| 22 |
+ <property name="visible">True</property> |
|
| 23 |
+ <property name="can_focus">True</property> |
|
| 24 |
+ <property name="receives_default">False</property> |
|
| 25 |
+ <property name="use_action_appearance">False</property> |
|
| 26 |
+ <property name="use_stock">True</property> |
|
| 27 |
+ </object> |
|
| 28 |
+ <packing> |
|
| 29 |
+ <property name="expand">False</property> |
|
| 30 |
+ <property name="fill">True</property> |
|
| 31 |
+ <property name="position">0</property> |
|
| 32 |
+ </packing> |
|
| 33 |
+ </child> |
|
| 34 |
+ <child> |
|
| 35 |
+ <object class="GtkButton" id="DiaDestBtnOk"> |
|
| 36 |
+ <property name="label">gtk-ok</property> |
|
| 37 |
+ <property name="use_action_appearance">False</property> |
|
| 38 |
+ <property name="visible">True</property> |
|
| 39 |
+ <property name="can_focus">True</property> |
|
| 40 |
+ <property name="can_default">True</property> |
|
| 41 |
+ <property name="has_default">True</property> |
|
| 42 |
+ <property name="receives_default">True</property> |
|
| 43 |
+ <property name="use_action_appearance">False</property> |
|
| 44 |
+ <property name="use_stock">True</property> |
|
| 45 |
+ </object> |
|
| 46 |
+ <packing> |
|
| 47 |
+ <property name="expand">False</property> |
|
| 48 |
+ <property name="fill">True</property> |
|
| 49 |
+ <property name="position">1</property> |
|
| 50 |
+ </packing> |
|
| 51 |
+ </child> |
|
| 52 |
+ </object> |
|
| 53 |
+ <packing> |
|
| 54 |
+ <property name="expand">False</property> |
|
| 55 |
+ <property name="fill">True</property> |
|
| 56 |
+ <property name="pack_type">end</property> |
|
| 57 |
+ <property name="position">0</property> |
|
| 58 |
+ </packing> |
|
| 59 |
+ </child> |
|
| 60 |
+ <child> |
|
| 61 |
+ <object class="GtkGrid" id="DiaDestGrid"> |
|
| 62 |
+ <property name="visible">True</property> |
|
| 63 |
+ <property name="can_focus">False</property> |
|
| 64 |
+ <property name="row_homogeneous">True</property> |
|
| 65 |
+ <property name="column_homogeneous">True</property> |
|
| 66 |
+ <child> |
|
| 67 |
+ <object class="GtkLabel" id="DiaDestLblCur"> |
|
| 68 |
+ <property name="visible">True</property> |
|
| 69 |
+ <property name="can_focus">False</property> |
|
| 70 |
+ <property name="xalign">0</property> |
|
| 71 |
+ <property name="xpad">3</property> |
|
| 72 |
+ <property name="ypad">3</property> |
|
| 73 |
+ <property name="label" translatable="yes">Current Destination Address:</property> |
|
| 74 |
+ </object> |
|
| 75 |
+ <packing> |
|
| 76 |
+ <property name="left_attach">0</property> |
|
| 77 |
+ <property name="top_attach">1</property> |
|
| 78 |
+ <property name="width">1</property> |
|
| 79 |
+ <property name="height">1</property> |
|
| 80 |
+ </packing> |
|
| 81 |
+ </child> |
|
| 82 |
+ <child> |
|
| 83 |
+ <object class="GtkLabel" id="DiaDestLblNew"> |
|
| 84 |
+ <property name="visible">True</property> |
|
| 85 |
+ <property name="can_focus">False</property> |
|
| 86 |
+ <property name="xalign">0</property> |
|
| 87 |
+ <property name="xpad">3</property> |
|
| 88 |
+ <property name="ypad">3</property> |
|
| 89 |
+ <property name="label" translatable="yes">New Destination Address:</property> |
|
| 90 |
+ </object> |
|
| 91 |
+ <packing> |
|
| 92 |
+ <property name="left_attach">0</property> |
|
| 93 |
+ <property name="top_attach">2</property> |
|
| 94 |
+ <property name="width">1</property> |
|
| 95 |
+ <property name="height">1</property> |
|
| 96 |
+ </packing> |
|
| 97 |
+ </child> |
|
| 98 |
+ <child> |
|
| 99 |
+ <object class="GtkLabel" id="DiaDestCur"> |
|
| 100 |
+ <property name="visible">True</property> |
|
| 101 |
+ <property name="can_focus">False</property> |
|
| 102 |
+ <property name="xalign">0</property> |
|
| 103 |
+ <property name="xpad">3</property> |
|
| 104 |
+ <property name="ypad">3</property> |
|
| 105 |
+ </object> |
|
| 106 |
+ <packing> |
|
| 107 |
+ <property name="left_attach">1</property> |
|
| 108 |
+ <property name="top_attach">1</property> |
|
| 109 |
+ <property name="width">1</property> |
|
| 110 |
+ <property name="height">1</property> |
|
| 111 |
+ </packing> |
|
| 112 |
+ </child> |
|
| 113 |
+ <child> |
|
| 114 |
+ <object class="GtkEntry" id="DiaDestNew"> |
|
| 115 |
+ <property name="visible">True</property> |
|
| 116 |
+ <property name="can_focus">True</property> |
|
| 117 |
+ <property name="invisible_char">●</property> |
|
| 118 |
+ <property name="activates_default">True</property> |
|
| 119 |
+ </object> |
|
| 120 |
+ <packing> |
|
| 121 |
+ <property name="left_attach">1</property> |
|
| 122 |
+ <property name="top_attach">2</property> |
|
| 123 |
+ <property name="width">1</property> |
|
| 124 |
+ <property name="height">1</property> |
|
| 125 |
+ </packing> |
|
| 126 |
+ </child> |
|
| 127 |
+ <child> |
|
| 128 |
+ <object class="GtkLabel" id="DiaDestDef"> |
|
| 129 |
+ <property name="visible">True</property> |
|
| 130 |
+ <property name="can_focus">False</property> |
|
| 131 |
+ <property name="xalign">0</property> |
|
| 132 |
+ <property name="xpad">3</property> |
|
| 133 |
+ <property name="ypad">3</property> |
|
| 134 |
+ <property name="label" translatable="yes">255.255.255.255</property> |
|
| 135 |
+ </object> |
|
| 136 |
+ <packing> |
|
| 137 |
+ <property name="left_attach">1</property> |
|
| 138 |
+ <property name="top_attach">0</property> |
|
| 139 |
+ <property name="width">1</property> |
|
| 140 |
+ <property name="height">1</property> |
|
| 141 |
+ </packing> |
|
| 142 |
+ </child> |
|
| 143 |
+ <child> |
|
| 144 |
+ <object class="GtkLabel" id="DiaDestLblDef"> |
|
| 145 |
+ <property name="visible">True</property> |
|
| 146 |
+ <property name="can_focus">False</property> |
|
| 147 |
+ <property name="xalign">0</property> |
|
| 148 |
+ <property name="xpad">3</property> |
|
| 149 |
+ <property name="ypad">3</property> |
|
| 150 |
+ <property name="label" translatable="yes">Default Destination Address:</property> |
|
| 151 |
+ </object> |
|
| 152 |
+ <packing> |
|
| 153 |
+ <property name="left_attach">0</property> |
|
| 154 |
+ <property name="top_attach">0</property> |
|
| 155 |
+ <property name="width">1</property> |
|
| 156 |
+ <property name="height">1</property> |
|
| 157 |
+ </packing> |
|
| 158 |
+ </child> |
|
| 159 |
+ </object> |
|
| 160 |
+ <packing> |
|
| 161 |
+ <property name="expand">True</property> |
|
| 162 |
+ <property name="fill">True</property> |
|
| 163 |
+ <property name="position">1</property> |
|
| 164 |
+ </packing> |
|
| 165 |
+ </child> |
|
| 166 |
+ </object> |
|
| 167 |
+ </child> |
|
| 168 |
+ <action-widgets> |
|
| 169 |
+ <action-widget response="0">DiaDestBtnCancel</action-widget> |
|
| 170 |
+ <action-widget response="1">DiaDestBtnOk</action-widget> |
|
| 171 |
+ </action-widgets> |
|
| 172 |
+ </object> |
|
| 4 | 173 |
<object class="GtkWindow" id="MainWindow"> |
| 5 | 174 |
<property name="visible">True</property> |
| 6 | 175 |
<property name="can_focus">False</property> |
| ... | ... |
@@ -59,6 +228,31 @@ |
| 59 | 228 |
</child> |
| 60 | 229 |
</object> |
| 61 | 230 |
</child> |
| 231 |
+ <child> |
|
| 232 |
+ <object class="GtkMenuItem" id="MiExtras"> |
|
| 233 |
+ <property name="use_action_appearance">False</property> |
|
| 234 |
+ <property name="visible">True</property> |
|
| 235 |
+ <property name="can_focus">False</property> |
|
| 236 |
+ <property name="label" translatable="yes">E_xtras</property> |
|
| 237 |
+ <property name="use_underline">True</property> |
|
| 238 |
+ <child type="submenu"> |
|
| 239 |
+ <object class="GtkMenu" id="MenuExtras"> |
|
| 240 |
+ <property name="visible">True</property> |
|
| 241 |
+ <property name="can_focus">False</property> |
|
| 242 |
+ <child> |
|
| 243 |
+ <object class="GtkMenuItem" id="MiExtrasDestination"> |
|
| 244 |
+ <property name="use_action_appearance">False</property> |
|
| 245 |
+ <property name="visible">True</property> |
|
| 246 |
+ <property name="can_focus">False</property> |
|
| 247 |
+ <property name="label" translatable="yes">_Destination Address</property> |
|
| 248 |
+ <property name="use_underline">True</property> |
|
| 249 |
+ <signal name="activate" handler="onExtrasDestination" swapped="no"/> |
|
| 250 |
+ </object> |
|
| 251 |
+ </child> |
|
| 252 |
+ </object> |
|
| 253 |
+ </child> |
|
| 254 |
+ </object> |
|
| 255 |
+ </child> |
|
| 62 | 256 |
</object> |
| 63 | 257 |
<packing> |
| 64 | 258 |
<property name="expand">False</property> |
| ... | ... |
@@ -340,6 +534,19 @@ a blinkenarea.org project - https://www.blinkenarea.org/</property> |
| 340 | 534 |
<property name="position">3</property> |
| 341 | 535 |
</packing> |
| 342 | 536 |
</child> |
| 537 |
+ <child> |
|
| 538 |
+ <object class="GtkStatusbar" id="Status"> |
|
| 539 |
+ <property name="visible">True</property> |
|
| 540 |
+ <property name="can_focus">False</property> |
|
| 541 |
+ <property name="orientation">vertical</property> |
|
| 542 |
+ <property name="spacing">2</property> |
|
| 543 |
+ </object> |
|
| 544 |
+ <packing> |
|
| 545 |
+ <property name="expand">False</property> |
|
| 546 |
+ <property name="fill">True</property> |
|
| 547 |
+ <property name="position">4</property> |
|
| 548 |
+ </packing> |
|
| 549 |
+ </child> |
|
| 343 | 550 |
</object> |
| 344 | 551 |
</child> |
| 345 | 552 |
</object> |
| ... | ... |
@@ -36,10 +36,12 @@ class SyncGui: |
| 36 | 36 |
self.widBtnPlay = self.builder.get_object("Play")
|
| 37 | 37 |
self.widLogoO = self.builder.get_object("LogoO")
|
| 38 | 38 |
self.widLogoG = self.builder.get_object("LogoG")
|
| 39 |
+ self.widStatus = self.builder.get_object("Status")
|
|
| 39 | 40 |
handlers = {
|
| 40 | 41 |
"onDestroy": self.onDestroy, |
| 41 | 42 |
"onFileOpen": self.onFileOpen, |
| 42 | 43 |
"onFileExit": self.onFileExit, |
| 44 |
+ "onExtrasDestination": self.onExtrasDestination, |
|
| 43 | 45 |
"onPlaylistDblClick": self.onPlaylistDblClick, |
| 44 | 46 |
"onNewPosition": self.onNewPosition, |
| 45 | 47 |
"onPrevious": self.onPrevious, |
| ... | ... |
@@ -53,18 +55,18 @@ class SyncGui: |
| 53 | 55 |
if len(sys.argv) >= 2: # load initial playlist from command line |
| 54 | 56 |
self.playlist.read(sys.argv[1]) |
| 55 | 57 |
self.playlist.update(self.widPlaylistStore) |
| 58 |
+ self.sock = None |
|
| 56 | 59 |
self.stEntryIdx = -1 # no entry selected |
| 57 | 60 |
self.stName = "" # no current entry name |
| 58 | 61 |
self.stDuration = 0 # current entry has zero size |
| 59 | 62 |
self.stPosition = 0 # at begin of current entry |
| 60 | 63 |
self.stPlaying = False # not playing |
| 61 |
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
| 62 |
- self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) |
|
| 63 |
- self.sock.connect(("255.255.255.255", 5740))
|
|
| 64 |
- gobject.timeout_add(10, self.onTimer10ms) |
|
| 65 |
- gobject.timeout_add(100, self.onTimer100ms) |
|
| 64 |
+ self.stDestination = "255.255.255.255" # local LAN broadcast by default |
|
| 65 |
+ self.setupSock() |
|
| 66 | 66 |
self.updateEntry() |
| 67 | 67 |
self.updateButtonVisibility() |
| 68 |
+ gobject.timeout_add(10, self.onTimer10ms) |
|
| 69 |
+ gobject.timeout_add(100, self.onTimer100ms) |
|
| 68 | 70 |
|
| 69 | 71 |
def showPosition(self): |
| 70 | 72 |
"""update the position texts next to the position slider""" |
| ... | ... |
@@ -145,6 +147,27 @@ class SyncGui: |
| 145 | 147 |
self.widLogoO.set_visible(not self.stPlaying) |
| 146 | 148 |
self.widLogoG.set_visible(self.stPlaying) |
| 147 | 149 |
|
| 150 |
+ def closeSock(self): |
|
| 151 |
+ """close UDP socket""" |
|
| 152 |
+ self.widStatus.remove_all(0) |
|
| 153 |
+ self.widStatus.push(0, "UDP output ERROR") |
|
| 154 |
+ if self.sock is not None: |
|
| 155 |
+ self.sock.close() |
|
| 156 |
+ self.sock = None |
|
| 157 |
+ |
|
| 158 |
+ def setupSock(self): |
|
| 159 |
+ """create a new UDP socket and "connect" it to the destination address""" |
|
| 160 |
+ self.closeSock() |
|
| 161 |
+ try: |
|
| 162 |
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|
| 163 |
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) |
|
| 164 |
+ self.sock.connect((self.stDestination, 5740)) |
|
| 165 |
+ self.widStatus.remove_all(0) |
|
| 166 |
+ self.widStatus.push(0, "UDP output to \"" + self.stDestination + |
|
| 167 |
+ "\" port 5740") |
|
| 168 |
+ except: |
|
| 169 |
+ self.closeSock() |
|
| 170 |
+ |
|
| 148 | 171 |
def onDestroy(self, widget): |
| 149 | 172 |
"""window will be destroyed""" |
| 150 | 173 |
Gtk.main_quit() |
| ... | ... |
@@ -152,10 +175,10 @@ class SyncGui: |
| 152 | 175 |
def onFileOpen(self, widget): |
| 153 | 176 |
"""File Open clicked in menu""" |
| 154 | 177 |
#print("DEBUG sync_gui File Open")
|
| 155 |
- # run file chooser dialog |
|
| 156 |
- dialog = Gtk.FileChooserDialog("BlinkenArea Sync GUI - File Open..",
|
|
| 157 |
- self.widMainWindow, |
|
| 158 |
- Gtk.FileChooserAction.OPEN, |
|
| 178 |
+ # create and run file chooser dialog |
|
| 179 |
+ dialog = Gtk.FileChooserDialog( |
|
| 180 |
+ "BlinkenArea Sync GUI - File Open...", |
|
| 181 |
+ self.widMainWindow, Gtk.FileChooserAction.OPEN, |
|
| 159 | 182 |
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, |
| 160 | 183 |
Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) |
| 161 | 184 |
dialog.set_default_response(Gtk.ResponseType.OK) |
| ... | ... |
@@ -181,6 +204,23 @@ class SyncGui: |
| 181 | 204 |
#print("DEBUG sync_gui File Exit")
|
| 182 | 205 |
Gtk.main_quit() |
| 183 | 206 |
|
| 207 |
+ def onExtrasDestination(self, widget): |
|
| 208 |
+ """Extras Destination Address clicked in menu""" |
|
| 209 |
+ #print("DEBUG sync_gui Extras Destination")
|
|
| 210 |
+ # run input dialog to ask for new destination |
|
| 211 |
+ dialog = self.builder.get_object("DialogDestination")
|
|
| 212 |
+ cur = self.builder.get_object("DiaDestCur")
|
|
| 213 |
+ new = self.builder.get_object("DiaDestNew")
|
|
| 214 |
+ cur.set_text(self.stDestination) |
|
| 215 |
+ new.set_text(self.stDestination) |
|
| 216 |
+ response = dialog.run() |
|
| 217 |
+ if response == 1: |
|
| 218 |
+ self.stDestination = new.get_text() |
|
| 219 |
+ # hide input dialog |
|
| 220 |
+ dialog.hide() |
|
| 221 |
+ # re-create UDP socket |
|
| 222 |
+ self.setupSock() |
|
| 223 |
+ |
|
| 184 | 224 |
def onPlaylistDblClick(self, widget, row, col): |
| 185 | 225 |
"""playlist entry has been double-clicked""" |
| 186 | 226 |
# get index of selected entry |
| ... | ... |
@@ -267,19 +307,25 @@ class SyncGui: |
| 267 | 307 |
self.updateEntry() |
| 268 | 308 |
else: |
| 269 | 309 |
self.updatePosition() |
| 270 |
- return True # call again |
|
| 310 |
+ # request being called again |
|
| 311 |
+ return True |
|
| 271 | 312 |
|
| 272 | 313 |
def onTimer100ms(self): |
| 273 | 314 |
"""timer callback, every 100ms""" |
| 274 | 315 |
# send sync packet |
| 316 |
+ if self.sock is not None: |
|
| 275 | 317 |
flags = 0 |
| 276 | 318 |
if self.stPlaying: |
| 277 | 319 |
flags = flags | 1 |
| 278 | 320 |
name = self.stName |
| 279 | 321 |
pos_ms = round(self.stPosition * 1000) |
| 280 | 322 |
data = "PoSy" + struct.pack("!I64sI", flags, name, pos_ms)
|
| 323 |
+ try: |
|
| 281 | 324 |
self.sock.send(data) |
| 282 |
- return True # call again |
|
| 325 |
+ except: |
|
| 326 |
+ self.closeSock() |
|
| 327 |
+ # request being called again |
|
| 328 |
+ return True |
|
| 283 | 329 |
|
| 284 | 330 |
# main application entry point |
| 285 | 331 |
if __name__ == "__main__": |
| 286 | 332 |