Revision e21a6d08
| b/py/maugis/scenic/application.py | ||
|---|---|---|
| 1 |
#!/usr/bin/env python |
|
| 2 |
# -*- coding: utf-8 -*- |
|
| 3 |
# |
|
| 4 |
# Scenic |
|
| 5 |
# Copyright (C) 2008 Société des arts technologiques (SAT) |
|
| 6 |
# http://www.sat.qc.ca |
|
| 7 |
# All rights reserved. |
|
| 8 |
# |
|
| 9 |
# This file is free software: you can redistribute it and/or modify |
|
| 10 |
# it under the terms of the GNU General Public License as published by |
|
| 11 |
# the Free Software Foundation, either version 2 of the License, or |
|
| 12 |
# (at your option) any later version. |
|
| 13 |
# |
|
| 14 |
# Scenic is distributed in the hope that it will be useful, |
|
| 15 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 16 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 17 |
# GNU General Public License for more details. |
|
| 18 |
# |
|
| 19 |
# You should have received a copy of the GNU General Public License |
|
| 20 |
# along with Scenic. If not, see <http://www.gnu.org/licenses/>. |
|
| 21 |
|
|
| 22 |
""" |
|
| 23 |
Main application |
|
| 24 |
""" |
|
| 25 |
import os |
|
| 26 |
import gtk # for dialog responses. TODO: remove |
|
| 27 |
|
|
| 28 |
from twisted.internet import defer |
|
| 29 |
from twisted.internet import error |
|
| 30 |
from twisted.internet import reactor |
|
| 31 |
|
|
| 32 |
from scenic import communication |
|
| 33 |
from scenic import saving |
|
| 34 |
from scenic import process # just for constants |
|
| 35 |
from scenic.streamer import StreamerManager |
|
| 36 |
from scenic import dialogs |
|
| 37 |
from scenic import ports |
|
| 38 |
from scenic import gui |
|
| 39 |
from scenic.gui import _ # gettext |
|
| 40 |
|
|
| 41 |
class Config(saving.ConfigStateSaving): |
|
| 42 |
""" |
|
| 43 |
Class attributes are default. |
|
| 44 |
""" |
|
| 45 |
# Default values |
|
| 46 |
negotiation_port = 17446 # receiving TCP (SIC) messages on it. |
|
| 47 |
smtpserver = "smtp.sat.qc.ca" |
|
| 48 |
email_info = "scenic@sat.qc.ca" |
|
| 49 |
audio_source = "jackaudiosrc" |
|
| 50 |
audio_sink = "jackaudiosink" |
|
| 51 |
audio_codec = "raw" |
|
| 52 |
audio_channels = 8 |
|
| 53 |
video_source = "v4l2src" |
|
| 54 |
video_device = "/dev/video0" |
|
| 55 |
video_sink = "xvimagesink" |
|
| 56 |
video_codec = "mpeg4" |
|
| 57 |
video_display = ":0.0" |
|
| 58 |
video_bitrate = "3000000" |
|
| 59 |
video_width = 640 |
|
| 60 |
video_height = 480 |
|
| 61 |
confirm_quit = False |
|
| 62 |
theme = "Darklooks" |
|
| 63 |
|
|
| 64 |
def __init__(self): |
|
| 65 |
config_file = 'scenic.cfg' |
|
| 66 |
if os.path.isfile('/etc/' + config_file):
|
|
| 67 |
config_dir = '/etc' |
|
| 68 |
else: |
|
| 69 |
config_dir = os.environ['HOME'] + '/.scenic' |
|
| 70 |
config_file_path = os.path.join(config_dir, config_file) |
|
| 71 |
saving.ConfigStateSaving.__init__(self, config_file_path) |
|
| 72 |
|
|
| 73 |
class Application(object): |
|
| 74 |
def __init__(self, kiosk_mode=False, fullscreen=False): |
|
| 75 |
self.config = Config() |
|
| 76 |
self.send_video_port = None |
|
| 77 |
self.recv_video_port = None |
|
| 78 |
self.send_audio_port = None |
|
| 79 |
self.recv_audio_port = None |
|
| 80 |
self.ports_allocator = ports.PortsAllocator() |
|
| 81 |
self.address_book = saving.AddressBook() |
|
| 82 |
self.streamer_manager = StreamerManager(self) |
|
| 83 |
self._has_session = False |
|
| 84 |
self.streamer_manager.state_changed_signal.connect(self.on_streamer_state_changed) # XXX |
|
| 85 |
print("Starting SIC server on port %s" % (self.config.negotiation_port))
|
|
| 86 |
self.server = communication.Server(self, self.config.negotiation_port) # XXX |
|
| 87 |
self.client = None |
|
| 88 |
self.got_bye = False |
|
| 89 |
# starting the GUI: |
|
| 90 |
self.gui = gui.Gui(self, kiosk_mode=kiosk_mode, fullscreen=fullscreen) |
|
| 91 |
reactor.addSystemEventTrigger("before", "shutdown", self.before_shutdown)
|
|
| 92 |
try: |
|
| 93 |
self.server.start_listening() |
|
| 94 |
except error.CannotListenError, e: |
|
| 95 |
print("Cannot start SIC server.")
|
|
| 96 |
print(str(e)) |
|
| 97 |
raise |
|
| 98 |
|
|
| 99 |
def before_shutdown(self): |
|
| 100 |
""" |
|
| 101 |
Last things done before quitting. |
|
| 102 |
""" |
|
| 103 |
print("The application is shutting down.")
|
|
| 104 |
# TODO: stop streamers |
|
| 105 |
if self.client is not None: |
|
| 106 |
if not self.got_bye: |
|
| 107 |
self.send_bye() |
|
| 108 |
self.stop_streamers() |
|
| 109 |
self.disconnect_client() |
|
| 110 |
print('stopping server')
|
|
| 111 |
self.server.close() |
|
| 112 |
# ------------------------- session occuring ------------- |
|
| 113 |
def has_session(self): |
|
| 114 |
""" |
|
| 115 |
@rettype: bool |
|
| 116 |
""" |
|
| 117 |
return self._has_session |
|
| 118 |
# -------------------- streamer ports ----------------- |
|
| 119 |
|
|
| 120 |
def allocate_ports(self): |
|
| 121 |
# TODO: start_session |
|
| 122 |
self.recv_video_port = self.ports_allocator.allocate() |
|
| 123 |
self.recv_audio_port = self.ports_allocator.allocate() |
|
| 124 |
|
|
| 125 |
def free_ports(self): |
|
| 126 |
# TODO: stop_session |
|
| 127 |
for port in [self.recv_video_port, self.recv_audio_port]: |
|
| 128 |
try: |
|
| 129 |
self.ports_allocator.free(port) |
|
| 130 |
except ports.PortsAllocatorError, e: |
|
| 131 |
print(e) |
|
| 132 |
|
|
| 133 |
def save_configuration(self): |
|
| 134 |
""" |
|
| 135 |
Saves the configuration to a file. |
|
| 136 |
Reads the widget value prior to do it. |
|
| 137 |
""" |
|
| 138 |
self.gui._gather_configuration() # need to get the value of the configuration widgets. |
|
| 139 |
self.config.save() |
|
| 140 |
self.address_book.save() # addressbook values are already stored. |
|
| 141 |
# --------------------------- network receives ------------ |
|
| 142 |
def handle_invite(self, message, addr): |
|
| 143 |
self.got_bye = False |
|
| 144 |
send_to_port = message["please_send_to_port"] |
|
| 145 |
|
|
| 146 |
def _on_contact_request_dialog_response(response): |
|
| 147 |
""" |
|
| 148 |
User is accetping or declining an offer. |
|
| 149 |
@param result: Answer to the dialog. |
|
| 150 |
""" |
|
| 151 |
if response == gtk.RESPONSE_OK: |
|
| 152 |
if self.client is not None: |
|
| 153 |
self.send_accept(message, addr) |
|
| 154 |
else: |
|
| 155 |
print("Error: connection lost, so we could not accept.")# FIXME
|
|
| 156 |
elif response == gtk.RESPONSE_CANCEL or gtk.RESPONSE_DELETE_EVENT: |
|
| 157 |
self.send_refuse_and_disconnect() |
|
| 158 |
else: |
|
| 159 |
pass |
|
| 160 |
return True |
|
| 161 |
|
|
| 162 |
if self.streamer_manager.is_busy(): |
|
| 163 |
print("Got invitation, but we are busy.")
|
|
| 164 |
communication.connect_send_and_disconnect(addr, send_to_port, {'msg':'REFUSE', 'sid':0}) #FIXME: where do we get the port number from?
|
|
| 165 |
else: |
|
| 166 |
self.client = communication.Client(self, send_to_port) |
|
| 167 |
self.client.connect(addr) |
|
| 168 |
# TODO: if a contact in the addressbook has this address, displays it! |
|
| 169 |
text = _("<b><big>%s is inviting you.</big></b>\n\nDo you accept the connection?" % addr)
|
|
| 170 |
self.gui.show_invited_dialog(text, _on_contact_request_dialog_response) |
|
| 171 |
|
|
| 172 |
def handle_cancel(self): |
|
| 173 |
if self.client is not None: |
|
| 174 |
self.client.disconnect() |
|
| 175 |
self.client = None |
|
| 176 |
self.gui.invited_dialog.hide() |
|
| 177 |
dialogs.ErrorDialog.create("Remote peer cancelled invitation.", parent=self.gui.main_window)
|
|
| 178 |
|
|
| 179 |
def handle_accept(self, message, addr): |
|
| 180 |
self.gui._unschedule_offerer_invite_timeout() |
|
| 181 |
# FIXME: this doesn't make sense here |
|
| 182 |
self.got_bye = False |
|
| 183 |
# TODO: Use session to contain settings and ports |
|
| 184 |
if self.client is not None: |
|
| 185 |
self.gui.hide_calling_dialog("accept")
|
|
| 186 |
self.send_video_port = message["videoport"] |
|
| 187 |
self.send_audio_port = message["audioport"] |
|
| 188 |
if self.streamer_manager.is_busy(): |
|
| 189 |
dialogs.ErrorDialog.create("A streaming session is already in progress.", parent=self.gui.main_window)
|
|
| 190 |
else: |
|
| 191 |
print("Got ACCEPT. Starting streamers as initiator.")
|
|
| 192 |
self.start_streamers(addr) |
|
| 193 |
self.send_ack() |
|
| 194 |
else: |
|
| 195 |
print("Error ! Connection lost.") # FIXME
|
|
| 196 |
|
|
| 197 |
def handle_refuse(self): |
|
| 198 |
self.gui._unschedule_offerer_invite_timeout() |
|
| 199 |
self.free_ports() |
|
| 200 |
self.gui.hide_calling_dialog("refuse")
|
|
| 201 |
|
|
| 202 |
def handle_ack(self, addr): |
|
| 203 |
print("Got ACK. Starting streamers as answerer.")
|
|
| 204 |
self.start_streamers(addr) |
|
| 205 |
|
|
| 206 |
def handle_bye(self): |
|
| 207 |
self.got_bye = True |
|
| 208 |
self.stop_streamers() |
|
| 209 |
if self.client is not None: |
|
| 210 |
print('disconnecting client and sending BYE')
|
|
| 211 |
self.client.send({"msg":"OK", "sid":0})
|
|
| 212 |
self.disconnect_client() |
|
| 213 |
|
|
| 214 |
def handle_ok(self): |
|
| 215 |
print("received ok. Everything has an end.")
|
|
| 216 |
print('disconnecting client')
|
|
| 217 |
self.disconnect_client() |
|
| 218 |
|
|
| 219 |
def on_server_receive_command(self, message, addr): |
|
| 220 |
# XXX |
|
| 221 |
msg = message["msg"] |
|
| 222 |
print("Got %s from %s" % (msg, addr))
|
|
| 223 |
|
|
| 224 |
if msg == "INVITE": |
|
| 225 |
self.handle_invite(message, addr) |
|
| 226 |
elif msg == "CANCEL": |
|
| 227 |
self.handle_cancel() |
|
| 228 |
elif msg == "ACCEPT": |
|
| 229 |
self.handle_accept(message, addr) |
|
| 230 |
elif msg == "REFUSE": |
|
| 231 |
self.handle_refuse() |
|
| 232 |
elif msg == "ACK": |
|
| 233 |
self.handle_ack(addr) |
|
| 234 |
elif msg == "BYE": |
|
| 235 |
self.handle_bye() |
|
| 236 |
elif msg == "OK": |
|
| 237 |
self.handle_ok() |
|
| 238 |
else: |
|
| 239 |
print ('WARNING: Unexpected message %s' % (msg))
|
|
| 240 |
|
|
| 241 |
# -------------------------- actions on streamer manager -------- |
|
| 242 |
|
|
| 243 |
def start_streamers(self, addr): |
|
| 244 |
self._has_session = True |
|
| 245 |
self.streamer_manager.start(addr, self.config) |
|
| 246 |
|
|
| 247 |
def stop_streamers(self): |
|
| 248 |
self.streamer_manager.stop() |
|
| 249 |
|
|
| 250 |
def on_streamers_stopped(self, addr): |
|
| 251 |
""" |
|
| 252 |
We call this when all streamers are stopped. |
|
| 253 |
""" |
|
| 254 |
print("on_streamers_stopped got called")
|
|
| 255 |
self._has_session = False |
|
| 256 |
self.free_ports() |
|
| 257 |
|
|
| 258 |
# ---------------------- sending messages ----------- |
|
| 259 |
|
|
| 260 |
def disconnect_client(self): |
|
| 261 |
""" |
|
| 262 |
Disconnects the SIC sender. |
|
| 263 |
@rettype: L{Deferred}
|
|
| 264 |
""" |
|
| 265 |
def _cb(result, d1): |
|
| 266 |
self.client = None |
|
| 267 |
d1.callback(True) |
|
| 268 |
def _cl(d1): |
|
| 269 |
if self.client is not None: |
|
| 270 |
d2 = self.client.disconnect() |
|
| 271 |
d2.addCallback(_cb, d1) |
|
| 272 |
else: |
|
| 273 |
d1.callback(True) |
|
| 274 |
if self.client is not None: |
|
| 275 |
d = defer.Deferred() |
|
| 276 |
reactor.callLater(0, _cl, d) |
|
| 277 |
return d |
|
| 278 |
else: |
|
| 279 |
return defer.succeed(True) |
|
| 280 |
|
|
| 281 |
def send_invite(self): |
|
| 282 |
self.allocate_ports() |
|
| 283 |
if self.streamer_manager.is_busy(): |
|
| 284 |
dialogs.ErrorDialog.create("Impossible to invite a contact to start streaming. A streaming session is already in progress.", parent=self.gui.main_window)
|
|
| 285 |
else: |
|
| 286 |
# UPDATE when initiating session |
|
| 287 |
self.gui._gather_configuration() |
|
| 288 |
msg = {
|
|
| 289 |
"msg":"INVITE", |
|
| 290 |
"sid":0, |
|
| 291 |
"videoport": self.recv_video_port, |
|
| 292 |
"audioport": self.recv_audio_port, |
|
| 293 |
"please_send_to_port": self.config.negotiation_port |
|
| 294 |
} |
|
| 295 |
port = self.config.negotiation_port |
|
| 296 |
ip = self.address_book.selected_contact["address"] |
|
| 297 |
|
|
| 298 |
def _on_connected(proto): |
|
| 299 |
self.gui._schedule_offerer_invite_timeout() |
|
| 300 |
self.client.send(msg) |
|
| 301 |
return proto |
|
| 302 |
def _on_error(reason): |
|
| 303 |
print ("error trying to connect to %s:%s : %s" % (ip, port, reason))
|
|
| 304 |
self.gui.calling_dialog.hide() |
|
| 305 |
self.client = None |
|
| 306 |
return None |
|
| 307 |
|
|
| 308 |
print ("sending %s to %s:%s" % (msg, ip, port))
|
|
| 309 |
self.client = communication.Client(self, port) |
|
| 310 |
deferred = self.client.connect(ip) |
|
| 311 |
deferred.addCallback(_on_connected).addErrback(_on_error) |
|
| 312 |
self.gui.calling_dialog.show() |
|
| 313 |
# window will be hidden when we receive ACCEPT or REFUSE |
|
| 314 |
|
|
| 315 |
def send_accept(self, message, addr): |
|
| 316 |
# UPDATE config once we accept the invitie |
|
| 317 |
self.gui._gather_configuration() |
|
| 318 |
self.allocate_ports() |
|
| 319 |
self.client.send({"msg":"ACCEPT", "videoport":self.recv_video_port, "audioport":self.recv_audio_port, "sid":0})
|
|
| 320 |
# TODO: Use session to contain settings and ports |
|
| 321 |
self.send_video_port = message["videoport"] |
|
| 322 |
self.send_audio_port = message["audioport"] |
|
| 323 |
|
|
| 324 |
def send_ack(self): |
|
| 325 |
self.client.send({"msg":"ACK", "sid":0})
|
|
| 326 |
|
|
| 327 |
def send_bye(self): |
|
| 328 |
""" |
|
| 329 |
Sends BYE |
|
| 330 |
BYE stops the streaming on the remote host. |
|
| 331 |
""" |
|
| 332 |
if self.client is not None: |
|
| 333 |
self.client.send({"msg":"BYE", "sid":0})
|
|
| 334 |
|
|
| 335 |
def send_cancel_and_disconnect(self): |
|
| 336 |
""" |
|
| 337 |
Sends CANCEL |
|
| 338 |
CANCEL cancels the invite on the remote host. |
|
| 339 |
""" |
|
| 340 |
if self.client is not None: |
|
| 341 |
self.client.send({"msg":"CANCEL", "sid":0})
|
|
| 342 |
self.client.disconnect() |
|
| 343 |
self.client = None |
|
| 344 |
else: |
|
| 345 |
print('Warning: Trying to send CANCEL even though client is None')
|
|
| 346 |
|
|
| 347 |
def send_refuse_and_disconnect(self): |
|
| 348 |
""" |
|
| 349 |
Sends REFUSE |
|
| 350 |
REFUSE tells the offerer we can't have a session. |
|
| 351 |
""" |
|
| 352 |
if self.client is not None: |
|
| 353 |
self.client.send({"msg":"REFUSE", "sid":0})
|
|
| 354 |
self.client.disconnect() |
|
| 355 |
self.client = None |
|
| 356 |
else: |
|
| 357 |
print('Warning: Trying to send REFUSE even though client is None')
|
|
| 358 |
|
|
| 359 |
# ------------------- streaming events handlers ---------------- |
|
| 360 |
|
|
| 361 |
def on_streamer_state_changed(self, streamer, new_state): |
|
| 362 |
""" |
|
| 363 |
Slot for scenic.streamer.StreamerManager.state_changed_signal |
|
| 364 |
""" |
|
| 365 |
if new_state in [process.STATE_STOPPED]: |
|
| 366 |
if not self.got_bye: |
|
| 367 |
""" got_bye means our peer sent us a BYE, so we shouldn't send one back """ |
|
| 368 |
print("Local StreamerManager stopped. Sending BYE")
|
|
| 369 |
self.send_bye() |
|
| 370 |
|
|
| 371 |
def on_client_socket_error(self, client, err, msg): |
|
| 372 |
# XXX |
|
| 373 |
self.gui.hide_calling_dialog(msg) |
|
| 374 |
text = _("%s: %s") % (str(err), str(msg))
|
|
| 375 |
self.gui.show_error_dialog(text) |
|
| b/py/maugis/scenic/gui.py | ||
|---|---|---|
| 51 | 51 |
import webbrowser |
| 52 | 52 |
import gettext |
| 53 | 53 |
|
| 54 |
from twisted.internet import defer |
|
| 55 |
from twisted.internet import error |
|
| 56 | 54 |
from twisted.internet import reactor |
| 57 | 55 |
|
| 58 |
from scenic import communication |
|
| 59 |
from scenic import saving |
|
| 60 | 56 |
from scenic import process # just for constants |
| 61 |
from scenic.streamer import StreamerManager |
|
| 62 | 57 |
from scenic import dialogs |
| 63 |
from scenic import ports |
|
| 64 | 58 |
from scenic import data |
| 59 |
|
|
| 65 | 60 |
PACKAGE_DATA = os.path.dirname(data.__file__) |
| 66 | 61 |
|
| 67 | 62 |
### MULTILINGUAL SUPPORT ### |
| ... | ... | |
| 71 | 66 |
gtk.glade.bindtextdomain(APP_NAME, os.path.join(PACKAGE_DATA, "locale")) |
| 72 | 67 |
gtk.glade.textdomain(APP_NAME) |
| 73 | 68 |
|
| 74 |
class Config(saving.ConfigStateSaving): |
|
| 75 |
""" |
|
| 76 |
Class attributes are default. |
|
| 77 |
""" |
|
| 78 |
# Default values |
|
| 79 |
negotiation_port = 17446 # receiving TCP (SIC) messages on it. |
|
| 80 |
smtpserver = "smtp.sat.qc.ca" |
|
| 81 |
email_info = "scenic@sat.qc.ca" |
|
| 82 |
audio_source = "jackaudiosrc" |
|
| 83 |
audio_sink = "jackaudiosink" |
|
| 84 |
audio_codec = "raw" |
|
| 85 |
audio_channels = 8 |
|
| 86 |
video_source = "v4l2src" |
|
| 87 |
video_device = "/dev/video0" |
|
| 88 |
video_sink = "xvimagesink" |
|
| 89 |
video_codec = "mpeg4" |
|
| 90 |
video_display = ":0.0" |
|
| 91 |
video_bitrate = "3000000" |
|
| 92 |
video_width = 640 |
|
| 93 |
video_height = 480 |
|
| 94 |
confirm_quit = False |
|
| 95 |
theme = "Darklooks" |
|
| 96 |
|
|
| 97 |
def __init__(self): |
|
| 98 |
config_file = 'scenic.cfg' |
|
| 99 |
if os.path.isfile('/etc/' + config_file):
|
|
| 100 |
config_dir = '/etc' |
|
| 101 |
else: |
|
| 102 |
config_dir = os.environ['HOME'] + '/.scenic' |
|
| 103 |
config_file_path = os.path.join(config_dir, config_file) |
|
| 104 |
saving.ConfigStateSaving.__init__(self, config_file_path) |
|
| 105 |
|
|
| 106 | 69 |
def _get_combobox_value(widget): |
| 107 | 70 |
""" |
| 108 | 71 |
Returns the current value of a GTK ComboBox widget. |
| ... | ... | |
| 157 | 120 |
Each peer decides what to receive from the other peer. Next, one peer can invite an other one to stream high-quality audio and video. |
| 158 | 121 |
""" |
| 159 | 122 |
|
| 160 |
|
|
| 161 | 123 |
class Gui(object): |
| 162 | 124 |
""" |
| 163 | 125 |
Main application (arguably God) class |
| ... | ... | |
| 796 | 758 |
self._offerer_invite_timeout = reactor.callLater(5, _cl_offerer_invite_timed_out) |
| 797 | 759 |
else: |
| 798 | 760 |
print("Warning: Already scheduled a timeout as we're already inviting a contact")
|
| 799 |
|
|
| 800 |
########################################################### |
|
| 801 |
|
|
| 802 |
class Application(object): |
|
| 803 |
def __init__(self, kiosk_mode=False, fullscreen=False): |
|
| 804 |
self.config = Config() |
|
| 805 |
self.send_video_port = None |
|
| 806 |
self.recv_video_port = None |
|
| 807 |
self.send_audio_port = None |
|
| 808 |
self.recv_audio_port = None |
|
| 809 |
self.ports_allocator = ports.PortsAllocator() |
|
| 810 |
self.address_book = saving.AddressBook() |
|
| 811 |
self.streamer_manager = StreamerManager(self) |
|
| 812 |
self._has_session = False |
|
| 813 |
self.streamer_manager.state_changed_signal.connect(self.on_streamer_state_changed) # XXX |
|
| 814 |
print("Starting SIC server on port %s" % (self.config.negotiation_port))
|
|
| 815 |
self.server = communication.Server(self, self.config.negotiation_port) # XXX |
|
| 816 |
self.client = None |
|
| 817 |
self.got_bye = False |
|
| 818 |
# starting the GUI: |
|
| 819 |
self.gui = Gui(self, kiosk_mode=kiosk_mode, fullscreen=fullscreen) |
|
| 820 |
reactor.addSystemEventTrigger("before", "shutdown", self.before_shutdown)
|
|
| 821 |
try: |
|
| 822 |
self.server.start_listening() |
|
| 823 |
except error.CannotListenError, e: |
|
| 824 |
print("Cannot start SIC server.")
|
|
| 825 |
print(str(e)) |
|
| 826 |
raise |
|
| 827 |
|
|
| 828 |
def before_shutdown(self): |
|
| 829 |
""" |
|
| 830 |
Last things done before quitting. |
|
| 831 |
""" |
|
| 832 |
print("The application is shutting down.")
|
|
| 833 |
# TODO: stop streamers |
|
| 834 |
if self.client is not None: |
|
| 835 |
if not self.got_bye: |
|
| 836 |
self.send_bye() |
|
| 837 |
self.stop_streamers() |
|
| 838 |
self.disconnect_client() |
|
| 839 |
print('stopping server')
|
|
| 840 |
self.server.close() |
|
| 841 |
# ------------------------- session occuring ------------- |
|
| 842 |
def has_session(self): |
|
| 843 |
""" |
|
| 844 |
@rettype: bool |
|
| 845 |
""" |
|
| 846 |
return self._has_session |
|
| 847 |
# -------------------- streamer ports ----------------- |
|
| 848 |
|
|
| 849 |
def allocate_ports(self): |
|
| 850 |
# TODO: start_session |
|
| 851 |
self.recv_video_port = self.ports_allocator.allocate() |
|
| 852 |
self.recv_audio_port = self.ports_allocator.allocate() |
|
| 853 |
|
|
| 854 |
def free_ports(self): |
|
| 855 |
# TODO: stop_session |
|
| 856 |
for port in [self.recv_video_port, self.recv_audio_port]: |
|
| 857 |
try: |
|
| 858 |
self.ports_allocator.free(port) |
|
| 859 |
except ports.PortsAllocatorError, e: |
|
| 860 |
print(e) |
|
| 861 |
|
|
| 862 |
def save_configuration(self): |
|
| 863 |
""" |
|
| 864 |
Saves the configuration to a file. |
|
| 865 |
Reads the widget value prior to do it. |
|
| 866 |
""" |
|
| 867 |
self.gui._gather_configuration() # need to get the value of the configuration widgets. |
|
| 868 |
self.config.save() |
|
| 869 |
self.address_book.save() # addressbook values are already stored. |
|
| 870 |
# --------------------------- network receives ------------ |
|
| 871 |
def handle_invite(self, message, addr): |
|
| 872 |
self.got_bye = False |
|
| 873 |
send_to_port = message["please_send_to_port"] |
|
| 874 |
|
|
| 875 |
def _on_contact_request_dialog_response(response): |
|
| 876 |
""" |
|
| 877 |
User is accetping or declining an offer. |
|
| 878 |
@param result: Answer to the dialog. |
|
| 879 |
""" |
|
| 880 |
if response == gtk.RESPONSE_OK: |
|
| 881 |
if self.client is not None: |
|
| 882 |
self.send_accept(message, addr) |
|
| 883 |
else: |
|
| 884 |
print("Error: connection lost, so we could not accept.")# FIXME
|
|
| 885 |
elif response == gtk.RESPONSE_CANCEL or gtk.RESPONSE_DELETE_EVENT: |
|
| 886 |
self.send_refuse_and_disconnect() |
|
| 887 |
else: |
|
| 888 |
pass |
|
| 889 |
return True |
|
| 890 |
|
|
| 891 |
if self.streamer_manager.is_busy(): |
|
| 892 |
print("Got invitation, but we are busy.")
|
|
| 893 |
communication.connect_send_and_disconnect(addr, send_to_port, {'msg':'REFUSE', 'sid':0}) #FIXME: where do we get the port number from?
|
|
| 894 |
else: |
|
| 895 |
self.client = communication.Client(self, send_to_port) |
|
| 896 |
self.client.connect(addr) |
|
| 897 |
# TODO: if a contact in the addressbook has this address, displays it! |
|
| 898 |
text = _("<b><big>%s is inviting you.</big></b>\n\nDo you accept the connection?" % addr)
|
|
| 899 |
self.gui.show_invited_dialog(text, _on_contact_request_dialog_response) |
|
| 900 |
|
|
| 901 |
def handle_cancel(self): |
|
| 902 |
if self.client is not None: |
|
| 903 |
self.client.disconnect() |
|
| 904 |
self.client = None |
|
| 905 |
self.gui.invited_dialog.hide() |
|
| 906 |
dialogs.ErrorDialog.create("Remote peer cancelled invitation.", parent=self.gui.main_window)
|
|
| 907 |
|
|
| 908 |
def handle_accept(self, message, addr): |
|
| 909 |
self.gui._unschedule_offerer_invite_timeout() |
|
| 910 |
# FIXME: this doesn't make sense here |
|
| 911 |
self.got_bye = False |
|
| 912 |
# TODO: Use session to contain settings and ports |
|
| 913 |
if self.client is not None: |
|
| 914 |
self.gui.hide_calling_dialog("accept")
|
|
| 915 |
self.send_video_port = message["videoport"] |
|
| 916 |
self.send_audio_port = message["audioport"] |
|
| 917 |
if self.streamer_manager.is_busy(): |
|
| 918 |
dialogs.ErrorDialog.create("A streaming session is already in progress.", parent=self.gui.main_window)
|
|
| 919 |
else: |
|
| 920 |
print("Got ACCEPT. Starting streamers as initiator.")
|
|
| 921 |
self.start_streamers(addr) |
|
| 922 |
self.send_ack() |
|
| 923 |
else: |
|
| 924 |
print("Error ! Connection lost.") # FIXME
|
|
| 925 |
|
|
| 926 |
def handle_refuse(self): |
|
| 927 |
self.gui._unschedule_offerer_invite_timeout() |
|
| 928 |
self.free_ports() |
|
| 929 |
self.gui.hide_calling_dialog("refuse")
|
|
| 930 |
|
|
| 931 |
def handle_ack(self, addr): |
|
| 932 |
print("Got ACK. Starting streamers as answerer.")
|
|
| 933 |
self.start_streamers(addr) |
|
| 934 |
|
|
| 935 |
def handle_bye(self): |
|
| 936 |
self.got_bye = True |
|
| 937 |
self.stop_streamers() |
|
| 938 |
if self.client is not None: |
|
| 939 |
print('disconnecting client and sending BYE')
|
|
| 940 |
self.client.send({"msg":"OK", "sid":0})
|
|
| 941 |
self.disconnect_client() |
|
| 942 |
|
|
| 943 |
def handle_ok(self): |
|
| 944 |
print("received ok. Everything has an end.")
|
|
| 945 |
print('disconnecting client')
|
|
| 946 |
self.disconnect_client() |
|
| 947 |
|
|
| 948 |
def on_server_receive_command(self, message, addr): |
|
| 949 |
# XXX |
|
| 950 |
msg = message["msg"] |
|
| 951 |
print("Got %s from %s" % (msg, addr))
|
|
| 952 |
|
|
| 953 |
if msg == "INVITE": |
|
| 954 |
self.handle_invite(message, addr) |
|
| 955 |
elif msg == "CANCEL": |
|
| 956 |
self.handle_cancel() |
|
| 957 |
elif msg == "ACCEPT": |
|
| 958 |
self.handle_accept(message, addr) |
|
| 959 |
elif msg == "REFUSE": |
|
| 960 |
self.handle_refuse() |
|
| 961 |
elif msg == "ACK": |
|
| 962 |
self.handle_ack(addr) |
|
| 963 |
elif msg == "BYE": |
|
| 964 |
self.handle_bye() |
|
| 965 |
elif msg == "OK": |
|
| 966 |
self.handle_ok() |
|
| 967 |
else: |
|
| 968 |
print ('WARNING: Unexpected message %s' % (msg))
|
|
| 969 |
|
|
| 970 |
# -------------------------- actions on streamer manager -------- |
|
| 971 |
|
|
| 972 |
def start_streamers(self, addr): |
|
| 973 |
self._has_session = True |
|
| 974 |
self.streamer_manager.start(addr, self.config) |
|
| 975 |
|
|
| 976 |
def stop_streamers(self): |
|
| 977 |
self.streamer_manager.stop() |
|
| 978 |
|
|
| 979 |
def on_streamers_stopped(self, addr): |
|
| 980 |
""" |
|
| 981 |
We call this when all streamers are stopped. |
|
| 982 |
""" |
|
| 983 |
print("on_streamers_stopped got called")
|
|
| 984 |
self._has_session = False |
|
| 985 |
self.free_ports() |
|
| 986 |
|
|
| 987 |
# ---------------------- sending messages ----------- |
|
| 988 |
|
|
| 989 |
def disconnect_client(self): |
|
| 990 |
""" |
|
| 991 |
Disconnects the SIC sender. |
|
| 992 |
@rettype: L{Deferred}
|
|
| 993 |
""" |
|
| 994 |
def _cb(result, d1): |
|
| 995 |
self.client = None |
|
| 996 |
d1.callback(True) |
|
| 997 |
def _cl(d1): |
|
| 998 |
if self.client is not None: |
|
| 999 |
d2 = self.client.disconnect() |
|
| 1000 |
d2.addCallback(_cb, d1) |
|
| 1001 |
else: |
|
| 1002 |
d1.callback(True) |
|
| 1003 |
if self.client is not None: |
|
| 1004 |
d = defer.Deferred() |
|
| 1005 |
reactor.callLater(0, _cl, d) |
|
| 1006 |
return d |
|
| 1007 |
else: |
|
| 1008 |
return defer.succeed(True) |
|
| 1009 |
|
|
| 1010 |
def send_invite(self): |
|
| 1011 |
self.allocate_ports() |
|
| 1012 |
if self.streamer_manager.is_busy(): |
|
| 1013 |
dialogs.ErrorDialog.create("Impossible to invite a contact to start streaming. A streaming session is already in progress.", parent=self.gui.main_window)
|
|
| 1014 |
else: |
|
| 1015 |
# UPDATE when initiating session |
|
| 1016 |
self.gui._gather_configuration() |
|
| 1017 |
msg = {
|
|
| 1018 |
"msg":"INVITE", |
|
| 1019 |
"sid":0, |
|
| 1020 |
"videoport": self.recv_video_port, |
|
| 1021 |
"audioport": self.recv_audio_port, |
|
| 1022 |
"please_send_to_port": self.config.negotiation_port |
|
| 1023 |
} |
|
| 1024 |
port = self.config.negotiation_port |
|
| 1025 |
ip = self.address_book.selected_contact["address"] |
|
| 1026 |
|
|
| 1027 |
def _on_connected(proto): |
|
| 1028 |
self.gui._schedule_offerer_invite_timeout() |
|
| 1029 |
self.client.send(msg) |
|
| 1030 |
return proto |
|
| 1031 |
def _on_error(reason): |
|
| 1032 |
print ("error trying to connect to %s:%s : %s" % (ip, port, reason))
|
|
| 1033 |
self.gui.calling_dialog.hide() |
|
| 1034 |
self.client = None |
|
| 1035 |
return None |
|
| 1036 |
|
|
| 1037 |
print ("sending %s to %s:%s" % (msg, ip, port))
|
|
| 1038 |
self.client = communication.Client(self, port) |
|
| 1039 |
deferred = self.client.connect(ip) |
|
| 1040 |
deferred.addCallback(_on_connected).addErrback(_on_error) |
|
| 1041 |
self.gui.calling_dialog.show() |
|
| 1042 |
# window will be hidden when we receive ACCEPT or REFUSE |
|
| 1043 |
|
|
| 1044 |
def send_accept(self, message, addr): |
|
| 1045 |
# UPDATE config once we accept the invitie |
|
| 1046 |
self.gui._gather_configuration() |
|
| 1047 |
self.allocate_ports() |
|
| 1048 |
self.client.send({"msg":"ACCEPT", "videoport":self.recv_video_port, "audioport":self.recv_audio_port, "sid":0})
|
|
| 1049 |
# TODO: Use session to contain settings and ports |
|
| 1050 |
self.send_video_port = message["videoport"] |
|
| 1051 |
self.send_audio_port = message["audioport"] |
|
| 1052 |
|
|
| 1053 |
def send_ack(self): |
|
| 1054 |
self.client.send({"msg":"ACK", "sid":0})
|
|
| 1055 |
|
|
| 1056 |
def send_bye(self): |
|
| 1057 |
""" |
|
| 1058 |
Sends BYE |
|
| 1059 |
BYE stops the streaming on the remote host. |
|
| 1060 |
""" |
|
| 1061 |
if self.client is not None: |
|
| 1062 |
self.client.send({"msg":"BYE", "sid":0})
|
|
| 1063 |
|
|
| 1064 |
def send_cancel_and_disconnect(self): |
|
| 1065 |
""" |
|
| 1066 |
Sends CANCEL |
|
| 1067 |
CANCEL cancels the invite on the remote host. |
|
| 1068 |
""" |
|
| 1069 |
if self.client is not None: |
|
| 1070 |
self.client.send({"msg":"CANCEL", "sid":0})
|
|
| 1071 |
self.client.disconnect() |
|
| 1072 |
self.client = None |
|
| 1073 |
else: |
|
| 1074 |
print('Warning: Trying to send CANCEL even though client is None')
|
|
| 1075 |
|
|
| 1076 |
def send_refuse_and_disconnect(self): |
|
| 1077 |
""" |
|
| 1078 |
Sends REFUSE |
|
| 1079 |
REFUSE tells the offerer we can't have a session. |
|
| 1080 |
""" |
|
| 1081 |
if self.client is not None: |
|
| 1082 |
self.client.send({"msg":"REFUSE", "sid":0})
|
|
| 1083 |
self.client.disconnect() |
|
| 1084 |
self.client = None |
|
| 1085 |
else: |
|
| 1086 |
print('Warning: Trying to send REFUSE even though client is None')
|
|
| 1087 |
|
|
| 1088 |
# ------------------- streaming events handlers ---------------- |
|
| 1089 |
|
|
| 1090 |
def on_streamer_state_changed(self, streamer, new_state): |
|
| 1091 |
""" |
|
| 1092 |
Slot for scenic.streamer.StreamerManager.state_changed_signal |
|
| 1093 |
""" |
|
| 1094 |
if new_state in [process.STATE_STOPPED]: |
|
| 1095 |
if not self.got_bye: |
|
| 1096 |
""" got_bye means our peer sent us a BYE, so we shouldn't send one back """ |
|
| 1097 |
print("Local StreamerManager stopped. Sending BYE")
|
|
| 1098 |
self.send_bye() |
|
| 1099 |
|
|
| 1100 |
def on_client_socket_error(self, client, err, msg): |
|
| 1101 |
# XXX |
|
| 1102 |
self.gui.hide_calling_dialog(msg) |
|
| 1103 |
text = _("%s: %s") % (str(err), str(msg))
|
|
| 1104 |
self.gui.show_error_dialog(text) |
|
| b/py/maugis/scenic/runner.py | ||
|---|---|---|
| 29 | 29 |
from twisted.internet import reactor |
| 30 | 30 |
from twisted.internet import error |
| 31 | 31 |
from twisted.python import log |
| 32 |
from scenic import gui |
|
| 32 |
|
|
| 33 |
from scenic import application |
|
| 34 |
from scenic import version |
|
| 33 | 35 |
|
| 34 | 36 |
def start_logging_to_stdout(): |
| 35 | 37 |
log.startLogging(sys.stdout) |
| 36 | 38 |
|
| 37 | 39 |
def run(): |
| 38 | 40 |
# command line parsing |
| 39 |
parser = OptionParser(usage="%prog", version=str(gui.__version__))
|
|
| 41 |
parser = OptionParser(usage="%prog", version=str(version.__version__))
|
|
| 40 | 42 |
parser.add_option("-k", "--kiosk", action="store_true", help="Run in kiosk mode")
|
| 41 | 43 |
parser.add_option("-f", "--fullscreen", action="store_true", help="Run in fullscreen mode")
|
| 42 | 44 |
(options, args) = parser.parse_args() |
| 43 | 45 |
start_logging_to_stdout() |
| 44 | 46 |
try: |
| 45 |
app = gui.Application(kiosk_mode=options.kiosk, fullscreen=options.fullscreen)
|
|
| 47 |
app = application.Application(kiosk_mode=options.kiosk, fullscreen=options.fullscreen)
|
|
| 46 | 48 |
except error.CannotListenError, e: |
| 47 | 49 |
print("There must be an other Scenic running.")
|
| 48 | 50 |
print(str(e)) |
Also available in: Unified diff