# 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
from sugar.graphics.combobox import ComboBox
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(150, 150)
recimage = gtk.Image()
recimage.set_from_icon_name("media-record", -1)
self.record.set_image(recimage)
self.stop = gtk.Button("Stop")
self.stop.connect("clicked", self.stop_cb)
self.stop.set_size_request(150, 150)
self.stop.set_sensitive(False)
stopimage = gtk.Image()
stopimage.set_from_icon_name("media-playback-stop", -1)
self.stop.set_image(stopimage)
# Record sound checkbox and quality selector
hbox2 = gtk.HBox(spacing=50)
self.audiocheckbox = gtk.CheckButton(label="record sound")
self.audiocheckbox.set_active(True)
hbox2.add(self.audiocheckbox)
self.qualitycombo = ComboBox()
self.qualitycombo.append_item("0", "high quality video")
self.qualitycombo.append_item("1", "medium quality video")
self.qualitycombo.append_item("2", "low quality video")
self.qualitycombo.set_active(2)
hbox2.add(self.qualitycombo)
options = gtk.Alignment(0.5, 0, 0, 0)
options.add(hbox2)
# Status label.
self.status = gtk.Label(_("Status: Stopped"))
hbox.pack_start(self.record, expand=False, padding=40)
hbox.pack_start(self.stop, expand=False, padding=40)
valign = gtk.Alignment(0.5, 0.4, 0, 0)
valign.add(vbox)
vbox.pack_end(self.status, expand=True, padding=40)
vbox.pack_end(hbox, expand=True, fill=False)
vbox.pack_end(options, expand=True, padding=40)
self.set_canvas(valign)
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)
self.audiocheckbox.set_sensitive(False)
self.qualitycombo.set_sensitive(False)
execargs = ["./recordmydesktop", "--no-frame", "--overwrite"]
if not self.audiocheckbox.get_active():
execargs.append("--no-sound")
if self.qualitycombo.get_active()==0:
execargs.append("-v_quality") # in later versions seems to be --v_quality instead
execargs.append("0")
elif self.qualitycombo.get_active()==1:
execargs.append("-v_quality") # in later versions seems to be --v_quality instead
execargs.append("31")
execargs.append("-o")
execargs.append(OUTFILE)
print execargs
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.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")
self.record.set_sensitive(True)
self.audiocheckbox.set_sensitive(True)
self.qualitycombo.set_sensitive(True)
if self._jobject.metadata['title_set_by_user'] == '1':
title = self.metadata['title']
else:
title = "My Screencast"
os.system("copy-to-journal /tmp/recordmydesktop.ogv -m video/ogg -t \"%s\""% title)
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