# Copyright 2008 Chris Ball. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Screencast Activity: An activity for producing XO tutorials.""" from gettext import gettext as _ from dbus.service import method, signal as dbus_signal import fcntl import gobject import gtk import logging import os import popen2 import re import shutil import signal from activity import ViewSourceActivity from sugar.activity.activity import ActivityToolbox, \ get_bundle_path, get_bundle_name from sugar.graphics.alert import NotifyAlert SERVICE = "org.laptop.Screencast" IFACE = SERVICE PATH = "/org/laptop/Screencast" OUTFILE = "/tmp/recordmydesktop.ogv" class ScreencastActivity(ViewSourceActivity): """Screencast Activity as specified in activity.info""" def __init__(self, handle): """Set up the Screencast activity.""" super(ScreencastActivity, self).__init__(handle) self._logger = logging.getLogger('screencast-activity') from sugar.graphics.menuitem import MenuItem from sugar.graphics.icon import Icon # Main layout. Record button, stop button, label. hbox = gtk.HBox() vbox = gtk.VBox() # Toolbar. toolbox = ActivityToolbox(self) activity_toolbar = toolbox.get_activity_toolbar() activity_toolbar.remove(activity_toolbar.share) activity_toolbar.share = None activity_toolbar.remove(activity_toolbar.keep) activity_toolbar.keep = None self.set_toolbox(toolbox) toolbox.show() # Recording buttons. self.record = gtk.Button("Record") self.record.connect("clicked", self.record_cb) self.record.set_size_request(100, 100) self.stop = gtk.Button("Stop") self.stop.connect("clicked", self.stop_cb) self.stop.set_size_request(100, 100) self.stop.set_sensitive(False) # Status label. self.status = gtk.Label(_("Status: Stopped")) hbox.pack_start(self.record, expand=False, padding=20) hbox.pack_start(self.stop, expand=False, padding=20) hbox.pack_start(self.status, expand=False, padding=20) vbox.pack_start(hbox, expand=True, fill=False) self.set_canvas(vbox) self.show_all() def write_file (self, file_path): print "Saving file to %s" % file_path self.metadata['mime_type'] = 'video/ogg' #try: # shutil.copy(OUTFILE, file_path) #except IOError, e: # print "unable to save to outfile: %s" % e # FIXME: This fails in /tmp. # that comment by probably cjb # I have no idea why it was saving to filepath #added copy-to-journal in check_status_cb #error msgs are OK probably just no video processed #Tony Forster #try: # os.remove(OUTFILE) #except OSError, e: # print "unable to remove outfile: %s" % e def can_close(self): if self.status.get_text().startswith("Status: Stopped"): return True else: self.alert("You need to finish operation before quitting.", self.status.get_text()) def alert(self, title, text=None): alert = NotifyAlert(timeout=10) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self.alert_cancel_cb) alert.show() def alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def record_cb(self, record): self.stop.set_sensitive(True) self.record.set_sensitive(False) execargs = ["./recordmydesktop", "--overwrite", "-o", OUTFILE] self.childp = popen2.Popen3(execargs, "t", 0) flags = fcntl.fcntl(self.childp.childerr, fcntl.F_GETFL) fcntl.fcntl(self.childp.childerr, fcntl.F_SETFL, flags|os.O_NONBLOCK) self.timed_id = gobject.timeout_add(1000, self.check_status_cb) self.status.set_text("Status: Recording") def stop_cb(self, stop): print "Stop button clicked" exitret = os.waitpid(self.childp.pid, os.WNOHANG) if exitret[0] == 0: os.kill(self.childp.pid, signal.SIGTERM) self.record.set_sensitive(True) self.stop.set_sensitive(False) def check_status_cb(self): if self.childp.pid: exitret = os.waitpid(self.childp.pid, os.WNOHANG) if exitret[0] != 0: print "no more record process" # The recording process exited self.status.set_text("Status: Stopped") os.system('copy-to-journal /tmp/recordmydesktop.ogv -m video/ogg -t "My Screencast"') return False else: # Maybe we have new stderr. while True: try: err_line = self.childp.childerr.readline() print err_line if err_line.startswith("STATE:ENCODING"): self.status.set_text("Status: Encoding, please wait") except: break return True ############# TEMPLATES AND INLINE FILES ############## ACTIVITY_INFO_TEMPLATE = """ [Activity] name = %(title)s bundle_id = %(bundle_id)s service_name = %(bundle_id)s class = %(class)s icon = activity-icon activity_version = %(version)d mime_types = %(mime_types)s show_launcher = yes %(extra_info)s """ PIPPY_ICON = \ """ ]> """ PIPPY_DEFAULT_ICON = \ """ ]> """ ############# ACTIVITY META-INFORMATION ############### # this is used by Screencast to generate a bundle for itself. def pippy_activity_version(): """Returns the version number of the generated activity bundle.""" return 1 def pippy_activity_extra_files(): """Returns a map of 'extra' files which should be included in the generated activity bundle.""" # Cheat here and generate the map from the fs contents. extra = {} bp = get_bundle_path() for d in [ 'po', 'data' ]: # everybody gets library already for root, dirs, files in os.walk(os.path.join(bp, d)): for name in files: fn = os.path.join(root, name).replace(bp+'/', '') extra[fn] = open(os.path.join(root, name), 'r').read() extra['activity/activity-default.svg'] = PIPPY_DEFAULT_ICON return extra def pippy_activity_news(): """Return the NEWS file for this activity.""" # Cheat again. return open(os.path.join(get_bundle_path(), 'NEWS')).read() def pippy_activity_icon(): """Return an SVG document specifying the icon for this activity.""" return PIPPY_ICON def pippy_activity_class(): """Return the class which should be started to run this activity.""" return 'pippy_app.ScreencastActivity' def pippy_activity_bundle_id(): """Return the bundle_id for the generated activity.""" return 'org.laptop.Screencast' def pippy_activity_mime_types(): """Return the mime types handled by the generated activity, as a list.""" return None