implement UDP output and destination config dialog
Stefan Schuermans

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