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 |