Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2008-10-18 23:39:20 (GMT)
committer Jonas Smedegaard <dr@jones.dk>2008-10-18 23:39:20 (GMT)
commit3ad472598ed4c130b3ee8357279d2a73a9f0ae80 (patch)
tree50bb4d627566a26f6f81ceeda0f82b034860b085 /src
parentb522b3c9a4e8296f195a222600c08c404dc6f0a1 (diff)
parentb3fde4a8e9a91425646bf9403dc27f00149a8980 (diff)
Merge commit 'v0.82.11' into upstream
Diffstat (limited to 'src')
-rw-r--r--src/sugar/Makefile.am9
-rw-r--r--src/sugar/_sugarext.defs116
-rw-r--r--src/sugar/_sugarext.override6
-rw-r--r--src/sugar/_sugarextmodule.c10
-rw-r--r--src/sugar/acme-volume-alsa.c317
-rw-r--r--src/sugar/acme-volume-alsa.h47
-rw-r--r--src/sugar/acme-volume.c127
-rw-r--r--src/sugar/acme-volume.h63
-rw-r--r--src/sugar/activity/Makefile.am1
-rw-r--r--src/sugar/activity/activity.py43
-rw-r--r--src/sugar/activity/activityfactory.py42
-rw-r--r--src/sugar/activity/bundlebuilder.py264
-rw-r--r--src/sugar/activity/main.py137
-rw-r--r--src/sugar/bundle/activitybundle.py19
-rw-r--r--src/sugar/bundle/bundle.py37
-rw-r--r--src/sugar/bundle/contentbundle.py4
-rw-r--r--src/sugar/eggsmclient-private.h2
-rw-r--r--src/sugar/graphics/alert.py3
-rw-r--r--src/sugar/graphics/palette.py46
-rw-r--r--src/sugar/graphics/radiotoolbutton.py6
-rw-r--r--src/sugar/graphics/style.py13
-rw-r--r--src/sugar/graphics/toggletoolbutton.py6
-rw-r--r--src/sugar/graphics/toolbutton.py12
-rw-r--r--src/sugar/graphics/tray.py6
-rw-r--r--src/sugar/gsm-session.c53
-rw-r--r--src/sugar/gsm-session.h2
-rw-r--r--src/sugar/profile.py4
-rw-r--r--src/sugar/sugar-grid.c120
-rw-r--r--src/sugar/sugar-grid.h63
-rw-r--r--src/sugar/sugar-menu.h1
-rw-r--r--src/sugar/sugar-preview.c29
-rw-r--r--src/sugar/sugar-preview.h2
-rw-r--r--src/sugar/util.py8
33 files changed, 1380 insertions, 238 deletions
diff --git a/src/sugar/Makefile.am b/src/sugar/Makefile.am
index ef91efe..ef7e634 100644
--- a/src/sugar/Makefile.am
+++ b/src/sugar/Makefile.am
@@ -14,6 +14,8 @@ pkgpyexecdir = $(pythondir)/sugar
pkgpyexec_LTLIBRARIES = _sugarext.la
_sugarext_la_CFLAGS = \
+ -DHAVE_ALSA \
+ $(WARN_CFLAGS) \
$(EXT_CFLAGS) \
$(PYTHON_INCLUDES)
@@ -23,6 +25,10 @@ _sugarext_la_LIBADD = $(EXT_LIBS) -lSM -lICE
_sugarext_la_SOURCES = \
$(BUILT_SOURCES) \
_sugarextmodule.c \
+ acme-volume.h \
+ acme-volume.c \
+ acme-volume-alsa.h \
+ acme-volume-alsa.c \
gsm-app.h \
gsm-app.c \
gsm-client.h \
@@ -45,6 +51,8 @@ _sugarext_la_SOURCES = \
sexy-icon-entry.c \
sugar-address-entry.c \
sugar-address-entry.h \
+ sugar-grid.c \
+ sugar-grid.h \
sugar-key-grabber.c \
sugar-key-grabber.h \
sugar-menu.h \
@@ -63,7 +71,6 @@ _sugarext.c: _sugarext.defs _sugarext.override
(cd $(srcdir)\
&& $(PYGTK_CODEGEN) \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
- --register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gtk-types.defs \
--override $*.override \
--prefix py$* $*.defs) > gen-$*.c \
diff --git a/src/sugar/_sugarext.defs b/src/sugar/_sugarext.defs
index 6e741dc..32ea18a 100644
--- a/src/sugar/_sugarext.defs
+++ b/src/sugar/_sugarext.defs
@@ -22,6 +22,13 @@
(gtype-id "SUGAR_TYPE_MENU")
)
+(define-object Grid
+ (in-module "Sugar")
+ (parent "GObject")
+ (c-name "SugarGrid")
+ (gtype-id "SUGAR_TYPE_GRID")
+)
+
(define-object Preview
(in-module "Sugar")
(parent "GObject")
@@ -57,6 +64,20 @@
(gtype-id "GSM_TYPE_SESSION")
)
+(define-object Volume
+ (in-module "Acme")
+ (parent "GObject")
+ (c-name "AcmeVolume")
+ (gtype-id "ACME_TYPE_VOLUME")
+)
+
+(define-object VolumeAlsa
+ (in-module "Acme")
+ (parent "AcmeVolume")
+ (c-name "AcmeVolumeAlsa")
+ (gtype-id "ACME_TYPE_VOLUME_ALSA")
+)
+
;; Enumerations and flags ...
(define-enum IconEntryPosition
@@ -95,6 +116,45 @@
(return-type "none")
)
+;; From sugar-grid.h
+
+(define-method setup
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_setup")
+ (return-type "none")
+ (parameters
+ '("gint" "width")
+ '("gint" "height")
+ )
+)
+
+(define-method add_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_add_weight")
+ (return-type "none")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
+(define-method remove_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_remove_weight")
+ (return-type "none")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
+(define-method compute_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_compute_weight")
+ (return-type "guint")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
;; From sugar-key-grabber.h
(define-function sugar_key_grabber_get_type
@@ -200,7 +260,7 @@
(c-name "sugar_preview_take_screenshot")
(return-type "none")
(parameters
- '("GdkDrawable" "drawable")
+ '("GtkWidget" "widget")
)
)
@@ -345,3 +405,57 @@
(return-type "GsmSession*")
)
+;; From acme-volume.h
+
+(define-function acme_volume_get_type
+ (c-name "acme_volume_get_type")
+ (return-type "GType")
+)
+
+(define-method get_volume
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_volume")
+ (return-type "int")
+)
+
+(define-method set_volume
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_set_volume")
+ (return-type "none")
+ (parameters
+ '("int" "val")
+ )
+)
+
+(define-method get_mute
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_mute")
+ (return-type "gboolean")
+)
+
+(define-method set_mute
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_set_mute")
+ (return-type "none")
+ (parameters
+ '("gboolean" "val")
+ )
+)
+
+(define-method mute_toggle
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_mute_toggle")
+ (return-type "none")
+)
+
+(define-method get_threshold
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_threshold")
+ (return-type "int")
+)
+
+(define-function acme_volume_new
+ (c-name "acme_volume_new")
+ (is-constructor-of "AcmeVolume")
+ (return-type "AcmeVolume*")
+)
diff --git a/src/sugar/_sugarext.override b/src/sugar/_sugarext.override
index db49e27..5a0608d 100644
--- a/src/sugar/_sugarext.override
+++ b/src/sugar/_sugarext.override
@@ -5,13 +5,15 @@ headers
#include "pygobject.h"
#include "sugar-address-entry.h"
+#include "sugar-grid.h"
#include "sugar-key-grabber.h"
#include "sugar-menu.h"
#include "sugar-preview.h"
#include "sexy-icon-entry.h"
#include "gsm-session.h"
+#include "gsm-xsmp.h"
+#include "acme-volume-alsa.h"
-#define EGG_SM_CLIENT_BACKEND_XSMP
#include "eggsmclient.h"
#include "eggsmclient-private.h"
@@ -22,11 +24,11 @@ headers
modulename sugar._sugarext
%%
import gobject.GObject as PyGObject_Type
+import gtk.Widget as PyGtkWidget_Type
import gtk.Entry as PyGtkEntry_Type
import gtk.Menu as PyGtkMenu_Type
import gtk.Container as PyGtkContainer_Type
import gtk.gdk.Window as PyGdkWindow_Type
-import gtk.gdk.Drawable as PyGdkDrawable_Type
import gtk.Image as PyGtkImage_Type
%%
ignore-glob
diff --git a/src/sugar/_sugarextmodule.c b/src/sugar/_sugarextmodule.c
index 6f6af6d..1bb8545 100644
--- a/src/sugar/_sugarextmodule.c
+++ b/src/sugar/_sugarextmodule.c
@@ -23,6 +23,7 @@
/* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h>
+#include <pygtk/pygtk.h>
extern PyMethodDef py_sugarext_functions[];
@@ -34,12 +35,13 @@ init_sugarext(void)
{
PyObject *m, *d;
- init_pygobject ();
+ init_pygobject();
+ init_pygtk();
- m = Py_InitModule ("_sugarext", py_sugarext_functions);
- d = PyModule_GetDict (m);
+ m = Py_InitModule("_sugarext", py_sugarext_functions);
+ d = PyModule_GetDict(m);
- py_sugarext_register_classes (d);
+ py_sugarext_register_classes(d);
py_sugarext_add_constants(m, "SEXY_");
if (PyErr_Occurred ()) {
diff --git a/src/sugar/acme-volume-alsa.c b/src/sugar/acme-volume-alsa.c
new file mode 100644
index 0000000..c1ea5cc
--- /dev/null
+++ b/src/sugar/acme-volume-alsa.c
@@ -0,0 +1,317 @@
+/* acme-volume-alsa.c
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifdef HAVE_CONFIG
+#include "config.h"
+#endif
+
+#include "acme-volume-alsa.h"
+
+#include <alsa/asoundlib.h>
+
+#ifndef DEFAULT_CARD
+#define DEFAULT_CARD "default"
+#endif
+
+#undef LOG
+#ifdef LOG
+#define D(x...) g_message (x)
+#else
+#define D(x...)
+#endif
+
+#define ROUND(x) ((x - (int)x > 0.5) ? x+1 : x)
+
+struct AcmeVolumeAlsaPrivate
+{
+ long pmin, pmax;
+ gboolean has_mute, has_master;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+ int saved_volume;
+ guint timer_id;
+};
+
+static int acme_volume_alsa_get_volume (AcmeVolume *self);
+static void acme_volume_alsa_set_volume (AcmeVolume *self, int val);
+static gboolean acme_volume_alsa_open (AcmeVolumeAlsa *self);
+static void acme_volume_alsa_close (AcmeVolumeAlsa *self);
+static gboolean acme_volume_alsa_close_real (AcmeVolumeAlsa *self);
+
+G_DEFINE_TYPE (AcmeVolumeAlsa, acme_volume_alsa, ACME_TYPE_VOLUME)
+
+static void
+acme_volume_alsa_finalize (GObject *object)
+{
+ AcmeVolumeAlsa *self;
+
+ self = ACME_VOLUME_ALSA (object);
+
+ if (self->_priv)
+ {
+ if (self->_priv->timer_id != 0)
+ {
+ g_source_remove (self->_priv->timer_id);
+ self->_priv->timer_id = 0;
+ }
+
+ acme_volume_alsa_close_real (self);
+ g_free (self->_priv);
+ self->_priv = NULL;
+ }
+
+ G_OBJECT_CLASS (acme_volume_alsa_parent_class)->finalize (object);
+}
+
+static void
+acme_volume_alsa_set_mute (AcmeVolume *vol, gboolean val)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return;
+
+ /* If we have a hardware mute */
+ if (self->_priv->has_mute)
+ {
+ snd_mixer_selem_set_playback_switch_all
+ (self->_priv->elem, !val);
+ acme_volume_alsa_close (self);
+ return;
+ }
+
+ acme_volume_alsa_close (self);
+
+ /* If we don't */
+ if (val == TRUE)
+ {
+ self->_priv->saved_volume = acme_volume_alsa_get_volume (vol);
+ acme_volume_alsa_set_volume (vol, 0);
+ } else {
+ if (self->_priv->saved_volume != -1)
+ acme_volume_alsa_set_volume (vol,
+ self->_priv->saved_volume);
+ }
+}
+
+static gboolean
+acme_volume_alsa_get_mute (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ int ival;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return FALSE;
+
+ if (self->_priv->has_mute)
+ {
+ snd_mixer_selem_get_playback_switch(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &ival);
+
+ acme_volume_alsa_close (self);
+
+ return !ival;
+ } else {
+ acme_volume_alsa_close (self);
+
+ return (acme_volume_alsa_get_volume (vol) == 0);
+ }
+}
+
+static int
+acme_volume_alsa_get_volume (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ long lval, rval;
+ int tmp;
+ float alsa_vol;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return 0;
+
+ snd_mixer_selem_get_playback_volume(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &lval);
+ snd_mixer_selem_get_playback_volume(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_RIGHT, &rval);
+
+ acme_volume_alsa_close (self);
+
+ alsa_vol = (lval + rval) / 2;
+ alsa_vol = alsa_vol * 100 / (self->_priv->pmax - self->_priv->pmin);
+ tmp = ROUND (alsa_vol);
+
+ return tmp;
+}
+
+static void
+acme_volume_alsa_set_volume (AcmeVolume *vol, int val)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ float volume;
+ int tmp;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return;
+
+ volume = (float) val / 100 * (self->_priv->pmax - self->_priv->pmin);
+ volume = CLAMP (volume, self->_priv->pmin, self->_priv->pmax);
+ tmp = ROUND (volume);
+
+ snd_mixer_selem_set_playback_volume_all (self->_priv->elem, tmp);
+
+ acme_volume_alsa_close (self);
+}
+
+static int
+acme_volume_alsa_get_threshold (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ int steps;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return 1;
+
+ acme_volume_alsa_close (self);
+
+ steps = self->_priv->pmax - self->_priv->pmin;
+ return (steps > 0) ? 100 / steps + 1 : 1;
+}
+
+static gboolean
+acme_volume_alsa_close_real (AcmeVolumeAlsa *self)
+{
+ if (self->_priv == NULL)
+ return FALSE;
+
+ if (self->_priv->handle != NULL)
+ {
+ snd_mixer_detach (self->_priv->handle, DEFAULT_CARD);
+ snd_mixer_free (self->_priv->handle);
+ self->_priv->handle = NULL;
+ self->_priv->elem = NULL;
+ }
+
+ self->_priv->timer_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+acme_volume_alsa_open (AcmeVolumeAlsa *self)
+{
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+
+ if (self->_priv->timer_id != 0)
+ {
+ g_source_remove (self->_priv->timer_id);
+ self->_priv->timer_id = 0;
+ return TRUE;
+ }
+
+ /* open the mixer */
+ if (snd_mixer_open (&handle, 0) < 0)
+ {
+ D("snd_mixer_open");
+ return FALSE;
+ }
+ /* attach the handle to the default card */
+ if (snd_mixer_attach (handle, DEFAULT_CARD) <0)
+ {
+ D("snd_mixer_attach");
+ goto bail;
+ }
+ /* ? */
+ if (snd_mixer_selem_register (handle, NULL, NULL) < 0)
+ {
+ D("snd_mixer_selem_register");
+ goto bail;
+ }
+ if (snd_mixer_load (handle) < 0)
+ {
+ D("snd_mixer_load");
+ goto bail;
+ }
+
+ snd_mixer_selem_id_alloca (&sid);
+ snd_mixer_selem_id_set_name (sid, "Master");
+ elem = snd_mixer_find_selem (handle, sid);
+ if (!elem)
+ {
+ snd_mixer_selem_id_alloca (&sid);
+ snd_mixer_selem_id_set_name (sid, "PCM");
+ elem = snd_mixer_find_selem (handle, sid);
+ if (!elem)
+ {
+ D("snd_mixer_find_selem");
+ goto bail;
+ }
+ }
+
+ if (!snd_mixer_selem_has_playback_volume (elem))
+ {
+ D("snd_mixer_selem_has_playback_volume");
+ goto bail;
+ }
+
+ snd_mixer_selem_get_playback_volume_range (elem,
+ &(self->_priv->pmin),
+ &(self->_priv->pmax));
+
+ self->_priv->has_mute = snd_mixer_selem_has_playback_switch (elem);
+ self->_priv->handle = handle;
+ self->_priv->elem = elem;
+
+ return TRUE;
+
+bail:
+ acme_volume_alsa_close_real (self);
+ return FALSE;
+}
+
+static void
+acme_volume_alsa_close (AcmeVolumeAlsa *self)
+{
+ self->_priv->timer_id = g_timeout_add (4000,
+ (GSourceFunc) acme_volume_alsa_close_real, self);
+}
+
+static void
+acme_volume_alsa_init (AcmeVolumeAlsa *self)
+{
+ self->_priv = g_new0 (AcmeVolumeAlsaPrivate, 1);
+}
+
+static void
+acme_volume_alsa_class_init (AcmeVolumeAlsaClass *klass)
+{
+ AcmeVolumeClass *volume_class = ACME_VOLUME_CLASS (klass);
+ G_OBJECT_CLASS (klass)->finalize = acme_volume_alsa_finalize;
+
+ volume_class->set_volume = acme_volume_alsa_set_volume;
+ volume_class->get_volume = acme_volume_alsa_get_volume;
+ volume_class->set_mute = acme_volume_alsa_set_mute;
+ volume_class->get_mute = acme_volume_alsa_get_mute;
+ volume_class->get_threshold = acme_volume_alsa_get_threshold;
+}
+
diff --git a/src/sugar/acme-volume-alsa.h b/src/sugar/acme-volume-alsa.h
new file mode 100644
index 0000000..b179a24
--- /dev/null
+++ b/src/sugar/acme-volume-alsa.h
@@ -0,0 +1,47 @@
+/* acme-volume-alsa.h
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include "acme-volume.h"
+
+#define ACME_TYPE_VOLUME_ALSA (acme_volume_alsa_get_type ())
+#define ACME_VOLUME_ALSA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsa))
+#define ACME_VOLUME_ALSA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsaClass))
+#define ACME_IS_VOLUME_ALSA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ACME_TYPE_VOLUME_ALSA))
+#define ACME_VOLUME_ALSA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsaClass))
+
+typedef struct AcmeVolumeAlsa AcmeVolumeAlsa;
+typedef struct AcmeVolumeAlsaClass AcmeVolumeAlsaClass;
+typedef struct AcmeVolumeAlsaPrivate AcmeVolumeAlsaPrivate;
+
+struct AcmeVolumeAlsa {
+ AcmeVolume parent;
+ AcmeVolumeAlsaPrivate *_priv;
+};
+
+struct AcmeVolumeAlsaClass {
+ AcmeVolumeClass parent;
+};
+
+GType acme_volume_alsa_get_type (void);
+
diff --git a/src/sugar/acme-volume.c b/src/sugar/acme-volume.c
new file mode 100644
index 0000000..09ae1d2
--- /dev/null
+++ b/src/sugar/acme-volume.c
@@ -0,0 +1,127 @@
+/* acme-volume.c
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifdef HAVE_CONFIG
+#include "config.h"
+#endif
+#include "acme-volume.h"
+#ifdef HAVE_OSS
+#include "acme-volume-oss.h"
+#endif
+#ifdef HAVE_ALSA
+#include "acme-volume-alsa.h"
+#endif
+#ifdef HAVE_GSTREAMER
+#include "acme-volume-gstreamer.h"
+#endif
+
+G_DEFINE_TYPE (AcmeVolume, acme_volume, G_TYPE_OBJECT)
+
+static void
+acme_volume_class_init (AcmeVolumeClass *klass)
+{
+}
+
+static void
+acme_volume_init (AcmeVolume *vol)
+{
+}
+
+int
+acme_volume_get_volume (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), 0);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_volume (self);
+}
+
+void
+acme_volume_set_volume (AcmeVolume *self, int val)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ ACME_VOLUME_GET_CLASS (self)->set_volume (self, val);
+}
+
+gboolean
+acme_volume_get_mute (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), FALSE);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_mute (self);
+}
+
+void
+acme_volume_set_mute (AcmeVolume *self, gboolean val)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ ACME_VOLUME_GET_CLASS (self)->set_mute (self, val);
+}
+
+void
+acme_volume_mute_toggle (AcmeVolume *self)
+{
+ gboolean muted;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ muted = ACME_VOLUME_GET_CLASS (self)->get_mute (self);
+ ACME_VOLUME_GET_CLASS (self)->set_mute (self, !muted);
+}
+
+int
+acme_volume_get_threshold (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), 0);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_threshold (self);
+}
+
+AcmeVolume *acme_volume_new (void)
+{
+ AcmeVolume *vol;
+
+#ifdef HAVE_GSTREAMER
+ vol = ACME_VOLUME (g_object_new (acme_volume_gstreamer_get_type (), NULL));
+ return vol;
+#endif
+#ifdef HAVE_ALSA
+ vol = ACME_VOLUME (g_object_new (acme_volume_alsa_get_type (), NULL));
+ if (vol != NULL && ACME_VOLUME_ALSA (vol)->_priv != NULL)
+ return vol;
+ if (ACME_VOLUME_ALSA (vol)->_priv == NULL)
+ g_object_unref (vol);
+#endif
+#ifdef HAVE_OSS
+ vol = ACME_VOLUME (g_object_new (acme_volume_oss_get_type (), NULL));
+ return vol;
+#endif
+ return NULL;
+}
+
diff --git a/src/sugar/acme-volume.h b/src/sugar/acme-volume.h
new file mode 100644
index 0000000..ec5ee3d
--- /dev/null
+++ b/src/sugar/acme-volume.h
@@ -0,0 +1,63 @@
+/* acme-volume.h
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifndef _ACME_VOLUME_H
+#define _ACME_VOLUME_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ACME_TYPE_VOLUME (acme_volume_get_type ())
+#define ACME_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ACME_TYPE_VOLUME, AcmeVolume))
+#define ACME_VOLUME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ACME_TYPE_VOLUME, AcmeVolumeClass))
+#define ACME_IS_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ACME_TYPE_VOLUME))
+#define ACME_VOLUME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ACME_TYPE_VOLUME, AcmeVolumeClass))
+
+typedef struct {
+ GObject parent;
+} AcmeVolume;
+
+typedef struct {
+ GObjectClass parent;
+
+ void (* set_volume) (AcmeVolume *self, int val);
+ int (* get_volume) (AcmeVolume *self);
+ void (* set_mute) (AcmeVolume *self, gboolean val);
+ int (* get_mute) (AcmeVolume *self);
+ int (* get_threshold) (AcmeVolume *self);
+} AcmeVolumeClass;
+
+GType acme_volume_get_type (void);
+int acme_volume_get_volume (AcmeVolume *self);
+void acme_volume_set_volume (AcmeVolume *self, int val);
+gboolean acme_volume_get_mute (AcmeVolume *self);
+void acme_volume_set_mute (AcmeVolume *self,
+ gboolean val);
+void acme_volume_mute_toggle (AcmeVolume *self);
+int acme_volume_get_threshold (AcmeVolume *self);
+AcmeVolume *acme_volume_new (void);
+
+G_END_DECLS
+
+#endif /* _ACME_VOLUME_H */
diff --git a/src/sugar/activity/Makefile.am b/src/sugar/activity/Makefile.am
index 9dfc8de..26a6782 100644
--- a/src/sugar/activity/Makefile.am
+++ b/src/sugar/activity/Makefile.am
@@ -6,4 +6,5 @@ sugar_PYTHON = \
activityhandle.py \
activityservice.py \
bundlebuilder.py \
+ main.py \
registry.py
diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py
index 9784b28..6571994 100644
--- a/src/sugar/activity/activity.py
+++ b/src/sugar/activity/activity.py
@@ -104,7 +104,7 @@ class ActivityToolbar(gtk.Toolbar):
if activity.metadata:
self.title = gtk.Entry()
- self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
+ self.title.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
self.title.set_text(activity.metadata['title'])
self.title.connect('changed', self.__title_changed_cb)
self._add_widget(self.title)
@@ -174,7 +174,6 @@ class ActivityToolbar(gtk.Toolbar):
self._activity.copy()
def __stop_clicked_cb(self, button):
- self._activity.take_screenshot()
self._activity.close()
def __jobject_updated_cb(self, jobject):
@@ -474,6 +473,12 @@ class Activity(Window, gtk.Container):
self.connect('realize', self.__realize_cb)
self.connect('delete-event', self.__delete_event_cb)
+ # watch visibility-notify-events to know when we can safely
+ # take a screenshot of the activity
+ self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect('visibility-notify-event', self.__visibility_notify_event_cb)
+ self._fully_obscured = True
+
self._active = False
self._activity_id = handle.activity_id
self._pservice = presenceservice.get_instance()
@@ -728,19 +733,12 @@ class Activity(Window, gtk.Container):
pixbuf = pixbuf.scale_simple(style.zoom(300), style.zoom(225),
gtk.gdk.INTERP_BILINEAR)
- # TODO: Find a way of taking a png out of the pixbuf without saving
- # to a temp file. Impementing gtk.gdk.Pixbuf.save_to_buffer in pygtk
- # would solve this.
- fd, file_path = tempfile.mkstemp('.png')
- os.close(fd)
+ preview_data = []
+ def save_func(buf, data):
+ data.append(buf)
- pixbuf.save(file_path, 'png')
- f = open(file_path)
- try:
- preview_data = f.read()
- finally:
- f.close()
- os.remove(file_path)
+ pixbuf.save_to_callback(save_func, 'png', user_data=preview_data)
+ preview_data = ''.join(preview_data)
self._preview.clear()
@@ -758,8 +756,8 @@ class Activity(Window, gtk.Container):
return {}
def take_screenshot(self):
- if self.canvas and self.canvas.window:
- self._preview.take_screenshot(self.canvas.window)
+ if self.canvas:
+ self._preview.take_screenshot(self.canvas)
def save(self):
"""Request that the activity is saved to the Journal.
@@ -817,6 +815,8 @@ class Activity(Window, gtk.Container):
copy work that needs to be done in write_file()
"""
logging.debug('Activity.copy: %r' % self._jobject.object_id)
+ if not self._fully_obscured:
+ self.take_screenshot()
self.save()
self._jobject.object_id = None
@@ -972,6 +972,8 @@ class Activity(Window, gtk.Container):
write_file() to do any state saving instead. If the application wants
to control wether it can close, it should override can_close().
"""
+ if not self._fully_obscured:
+ self.take_screenshot()
if not self.can_close():
return
@@ -991,6 +993,15 @@ class Activity(Window, gtk.Container):
self.close()
return True
+ def __visibility_notify_event_cb(self, widget, event):
+ """Visibility state is used when deciding if we can take screenshots.
+ Currently we allow screenshots whenever the activity window is fully
+ visible or partially obscured."""
+ if event.state is gtk.gdk.VISIBILITY_FULLY_OBSCURED:
+ self._fully_obscured = True
+ else:
+ self._fully_obscured = False
+
def get_metadata(self):
"""Returns the jobject metadata or None if there is no jobject.
diff --git a/src/sugar/activity/activityfactory.py b/src/sugar/activity/activityfactory.py
index c9729d4..90f55cd 100644
--- a/src/sugar/activity/activityfactory.py
+++ b/src/sugar/activity/activityfactory.py
@@ -54,15 +54,18 @@ _RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow"
_RAINBOW_ACTIVITY_FACTORY_PATH = "/"
_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow"
-_children_pid = []
-
-def _sigchild_handler(signum, frame):
- for child_pid in _children_pid:
- pid, status_ = os.waitpid(child_pid, os.WNOHANG)
- if pid > 0:
- _children_pid.remove(pid)
-
-signal.signal(signal.SIGCHLD, _sigchild_handler)
+# helper method to close all filedescriptors
+# borrowed from subprocess.py
+try:
+ MAXFD = os.sysconf("SC_OPEN_MAX")
+except:
+ MAXFD = 256
+def _close_fds():
+ for i in xrange(3, MAXFD):
+ try:
+ os.close(i)
+ except:
+ pass
def create_activity_id():
"""Generate a new, unique ID for this activity"""
@@ -245,9 +248,24 @@ class ActivityCreationHandler(gobject.GObject):
self._handle.uri)
if not self._use_rainbow:
- p = subprocess.Popen(command, env=environ, cwd=activity.path,
- stdout=log_file, stderr=log_file)
- _children_pid.append(p.pid)
+ # use gobject spawn functionality, so that zombies are
+ # automatically reaped by the gobject event loop.
+ def child_setup():
+ # clone logfile.fileno() onto stdout/stderr
+ os.dup2(log_file.fileno(), 1)
+ os.dup2(log_file.fileno(), 2)
+ # close all other fds
+ _close_fds()
+ # we need to sanitize and str-ize the various bits which
+ # dbus gives us.
+ gobject.spawn_async([str(s) for s in command],
+ envp=['%s=%s' % (k, str(v))
+ for k, v in environ.items()],
+ working_directory=str(activity.path),
+ child_setup=child_setup,
+ flags=(gobject.SPAWN_SEARCH_PATH |
+ gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN))
+ log_file.close()
else:
log_file.close()
system_bus = dbus.SystemBus()
diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py
index b8f87a7..baeb8b6 100644
--- a/src/sugar/activity/bundlebuilder.py
+++ b/src/sugar/activity/bundlebuilder.py
@@ -16,6 +16,7 @@
# Boston, MA 02111-1307, USA.
import os
+import sys
import zipfile
import tarfile
import shutil
@@ -29,6 +30,9 @@ from fnmatch import fnmatch
from sugar import env
from sugar.bundle.activitybundle import ActivityBundle
+IGNORE_DIRS = ['dist', '.git']
+IGNORE_FILES = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak', 'pseudo.po']
+
def list_files(base_dir, ignore_dirs=None, ignore_files=None):
result = []
@@ -51,7 +55,21 @@ def list_files(base_dir, ignore_dirs=None, ignore_files=None):
class Config(object):
def __init__(self, source_dir=None, dist_dir = None, dist_name = None):
self.source_dir = source_dir or os.getcwd()
-
+ self.dist_dir = dist_dir or os.path.join(self.source_dir, 'dist')
+ self.dist_name = dist_name
+ self.bundle = None
+ self.version = None
+ self.activity_name = None
+ self.bundle_id = None
+ self.bundle_name = None
+ self.bundle_root_dir = None
+ self.tar_root_dir = None
+ self.xo_name = None
+ self.tar_name = None
+
+ self.update()
+
+ def update(self):
self.bundle = bundle = ActivityBundle(self.source_dir)
self.version = bundle.get_activity_version()
self.activity_name = bundle.get_name()
@@ -59,14 +77,9 @@ class Config(object):
self.bundle_name = reduce(lambda x, y:x+y, self.activity_name.split())
self.bundle_root_dir = self.bundle_name + '.activity'
self.tar_root_dir = '%s-%d' % (self.bundle_name, self.version)
-
- if dist_dir:
- self.dist_dir = dist_dir
- else:
- self.dist_dir = os.path.join(self.source_dir, 'dist')
- if dist_name:
- self.xo_name = self.tar_name = dist_name
+ if self.dist_name:
+ self.xo_name = self.tar_name = self.dist_name
else:
self.xo_name = '%s-%d.xo' % (self.bundle_name, self.version)
self.tar_name = '%s-%d.tar.bz2' % (self.bundle_name, self.version)
@@ -85,8 +98,13 @@ class Builder(object):
logging.warn("Missing po/ dir, cannot build_locale")
return
+ locale_dir = os.path.join(self.config.source_dir, 'locale')
+
+ if os.path.exists(locale_dir):
+ shutil.rmtree(locale_dir)
+
for f in os.listdir(po_dir):
- if not f.endswith('.po'):
+ if not f.endswith('.po') or f == 'pseudo.po':
continue
file_name = os.path.join(po_dir, f)
@@ -110,15 +128,6 @@ class Builder(object):
f.write('[Activity]\nname = %s\n' % translated_name)
f.close()
-class Packager(object):
- def __init__(self, config):
- self.config = config
- self.package_path = None
-
- if not os.path.exists(self.config.dist_dir):
- os.mkdir(self.config.dist_dir)
-
-class BuildPackager(Packager):
def get_files(self):
files = self.config.bundle.get_files()
@@ -128,28 +137,43 @@ class BuildPackager(Packager):
files = self.config.bundle.get_files()
return files
-
- def _list_useful_files(self):
- ignore_dirs = ['dist', '.git']
- ignore_files = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak']
-
- return list_files(self.config.source_dir, ignore_dirs, ignore_files)
+
+ def check_manifest(self):
+ missing_files = []
+
+ allfiles = list_files(self.config.source_dir,
+ IGNORE_DIRS, IGNORE_FILES)
+ for path in allfiles:
+ if path not in self.config.bundle.manifest:
+ missing_files.append(path)
+
+ return missing_files
def fix_manifest(self):
+ self.build()
+
manifest = self.config.bundle.manifest
- allfiles = self._list_useful_files()
- for path in allfiles:
- if path not in manifest:
- manifest.append(path)
+ for path in self.check_manifest():
+ manifest.append(path)
f = open(os.path.join(self.config.source_dir, "MANIFEST"), "wb")
for line in manifest:
f.write(line + "\n")
-class XOPackager(BuildPackager):
+class Packager(object):
def __init__(self, config):
- BuildPackager.__init__(self, config)
+ self.config = config
+ self.package_path = None
+
+ if not os.path.exists(self.config.dist_dir):
+ os.mkdir(self.config.dist_dir)
+
+class XOPackager(Packager):
+ def __init__(self, builder):
+ Packager.__init__(self, builder.config)
+
+ self.builder = builder
self.package_path = os.path.join(self.config.dist_dir,
self.config.xo_name)
@@ -157,15 +181,22 @@ class XOPackager(BuildPackager):
bundle_zip = zipfile.ZipFile(self.package_path, 'w',
zipfile.ZIP_DEFLATED)
- for f in self.get_files():
+ missing_files = self.builder.check_manifest()
+ if missing_files:
+ logging.warn('These files are not included in the manifest ' \
+ 'and will not be present in the bundle:\n\n' +
+ '\n'.join(missing_files) +
+ '\n\nUse fix_manifest if you want to add them.')
+
+ for f in self.builder.get_files():
bundle_zip.write(os.path.join(self.config.source_dir, f),
os.path.join(self.config.bundle_root_dir, f))
bundle_zip.close()
-class SourcePackager(BuildPackager):
+class SourcePackager(Packager):
def __init__(self, config):
- BuildPackager.__init__(self, config)
+ Packager.__init__(self, config)
self.package_path = os.path.join(self.config.dist_dir,
self.config.tar_name)
@@ -174,7 +205,8 @@ class SourcePackager(BuildPackager):
cwd=self.config.source_dir)
if git_ls.wait():
# Fall back to filtered list
- return self._list_useful_files()
+ return list_files(self.config.source_dir,
+ IGNORE_DIRS, IGNORE_FILES)
return [path.strip() for path in git_ls.stdout.readlines()]
@@ -185,20 +217,50 @@ class SourcePackager(BuildPackager):
os.path.join(self.config.tar_root_dir, f))
tar.close()
-def cmd_help(config, options, args):
- print 'Usage: \n\
-setup.py build - build generated files \n\
-setup.py dev - setup for development \n\
-setup.py dist_xo - create a xo bundle package \n\
-setup.py dist_source - create a tar source package \n\
-setup.py install [dirname] - install the bundle \n\
-setup.py uninstall [dirname] - uninstall the bundle \n\
-setup.py genpot - generate the gettext pot file \n\
-setup.py release - do a new release of the bundle \n\
-setup.py help - print this message \n\
-'
-
-def cmd_dev(config, options, args):
+class Installer(object):
+ IGNORES = [ 'po/*', 'MANIFEST', 'AUTHORS' ]
+
+ def __init__(self, builder):
+ self.config = builder.config
+ self.builder = builder
+
+ def should_ignore(self, f):
+ for pattern in self.IGNORES:
+ if fnmatch(f, pattern):
+ return True
+ return False
+
+ def install(self, prefix):
+ self.builder.build()
+
+ activity_path = os.path.join(prefix, 'share', 'sugar', 'activities',
+ self.config.bundle_root_dir)
+
+ source_to_dest = {}
+ for f in self.builder.get_files():
+ if self.should_ignore(f):
+ pass
+ elif f.startswith('locale/') and f.endswith('.mo'):
+ source_to_dest[f] = os.path.join(prefix, 'share', f)
+ else:
+ source_to_dest[f] = os.path.join(activity_path, f)
+
+ for source, dest in source_to_dest.items():
+ print 'Install %s to %s.' % (source, dest)
+
+ path = os.path.dirname(dest)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ shutil.copy(source, dest)
+
+def cmd_dev(config, args):
+ '''Setup for development'''
+
+ if args:
+ print 'Usage: %prog dev'
+ return
+
bundle_path = env.get_user_activities_path()
if not os.path.isdir(bundle_path):
os.mkdir(bundle_path)
@@ -211,47 +273,57 @@ def cmd_dev(config, options, args):
else:
print 'ERROR - A bundle with the same name is already installed.'
-def cmd_dist_xo(config, options, args):
- builder = Builder(config)
- builder.build()
+def cmd_dist_xo(config, args):
+ '''Create a xo bundle package'''
- packager = XOPackager(config)
+ if args:
+ print 'Usage: %prog dist_xo'
+ return
+
+ packager = XOPackager(Builder(config))
packager.package()
-def cmd_dist(config, options, args):
- logging.warn("dist deprecated, use dist_xo.")
- cmd_dist_xo(config, options, args)
+def cmd_fix_manifest(config, args):
+ '''Add missing files to the manifest'''
-def cmd_dist_source(config, options, args):
- packager = SourcePackager(config)
- packager.package()
+ if args:
+ print 'Usage: %prog fix_manifest'
+ return
+
+ builder = Builder(config)
+ builder.fix_manifest()
+
+def cmd_dist_source(config, args):
+ '''Create a tar source package'''
-def cmd_install(config, options, args):
- path = args[0]
+ if args:
+ print 'Usage: %prog dist_source'
+ return
- packager = XOPackager(config)
+ packager = SourcePackager(config)
packager.package()
- root_path = os.path.join(args[0], config.bundle_root_dir)
- if os.path.isdir(root_path):
- shutil.rmtree(root_path)
+def cmd_install(config, args):
+ '''Install the activity in the system'''
- if not os.path.exists(path):
- os.mkdir(path)
+ parser = OptionParser(usage='usage: %prog install [options]')
+ parser.add_option('--prefix', dest='prefix', default=sys.prefix,
+ help='Prefix to install files to')
+ (suboptions, subargs) = parser.parse_args(args)
+ if subargs:
+ parser.print_help()
+ return
- zf = zipfile.ZipFile(packager.package_path)
+ installer = Installer(Builder(config))
+ installer.install(suboptions.prefix)
- for name in zf.namelist():
- full_path = os.path.join(path, name)
- if not os.path.exists(os.path.dirname(full_path)):
- os.makedirs(os.path.dirname(full_path))
+def cmd_genpot(config, args):
+ '''Generate the gettext pot file'''
- outfile = open(full_path, 'wb')
- outfile.write(zf.read(name))
- outfile.flush()
- outfile.close()
+ if args:
+ print 'Usage: %prog genpot'
+ return
-def cmd_genpot(config, options, args):
po_path = os.path.join(config.source_dir, 'po')
if not os.path.isdir(po_path):
os.mkdir(po_path)
@@ -283,7 +355,13 @@ def cmd_genpot(config, options, args):
if retcode:
print 'ERROR - xgettext failed with return code %i.' % retcode
-def cmd_release(config, options, args):
+def cmd_release(config, args):
+ '''Do a new release of the bundle'''
+
+ if args:
+ print 'Usage: %prog release'
+ return
+
if not os.path.isdir('.git'):
print 'ERROR - this command works only for git repositories'
return
@@ -309,6 +387,8 @@ def cmd_release(config, options, args):
f.write(info)
f.close()
+ config.update()
+
news_path = os.path.join(config.source_dir, 'NEWS')
if os.environ.has_key('SUGAR_NEWS'):
@@ -351,7 +431,7 @@ def cmd_release(config, options, args):
f.close()
print 'Creating the bundle...'
- packager = XOPackager(config)
+ packager = XOPackager(Builder(config))
packager.package()
print 'Committing to git...'
@@ -379,22 +459,40 @@ def cmd_release(config, options, args):
print 'Done.'
-def cmd_build(config, options, args):
+def cmd_build(config, args):
+ '''Build generated files'''
+
+ if args:
+ print 'Usage: %prog build'
+ return
+
builder = Builder(config)
builder.build()
+def print_commands():
+ print 'Available commands:\n'
+
+ for name, func in globals().items():
+ if name.startswith('cmd_'):
+ print "%-20s %s" % (name.replace('cmd_', ''), func.__doc__)
+
+ print '\n(Type "./setup.py <command> --help" for help about a ' \
+ 'particular command\'s options.'
+
def start(bundle_name=None):
if bundle_name:
logging.warn("bundle_name deprecated, now comes from activity.info")
- parser = OptionParser()
- (options, args) = parser.parse_args()
+
+ parser = OptionParser(usage='[action] [options]')
+ parser.disable_interspersed_args()
+ (options_, args) = parser.parse_args()
config = Config()
try:
- globals()['cmd_' + args[0]](config, options, args[1:])
+ globals()['cmd_' + args[0]](config, args[1:])
except (KeyError, IndexError):
- cmd_help(config, options, args)
+ print_commands()
if __name__ == '__main__':
start()
diff --git a/src/sugar/activity/main.py b/src/sugar/activity/main.py
new file mode 100644
index 0000000..2175ff3
--- /dev/null
+++ b/src/sugar/activity/main.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2008 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import os
+import sys
+import gettext
+from optparse import OptionParser
+
+import gtk
+import dbus
+import dbus.service
+import dbus.glib
+
+from sugar.activity import activityhandle
+from sugar.bundle.activitybundle import ActivityBundle
+from sugar import logger
+
+def create_activity_instance(constructor, handle):
+ activity = constructor(handle)
+ activity.show()
+
+def get_single_process_name(bundle_id):
+ return bundle_id
+
+def get_single_process_path(bundle_id):
+ return '/' + bundle_id.replace('.', '/')
+
+class SingleProcess(dbus.service.Object):
+ def __init__(self, name_service, constructor):
+ self.constructor = constructor
+
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(name_service, bus=bus)
+ object_path = get_single_process_path(name_service)
+ dbus.service.Object.__init__(self, bus_name, object_path)
+
+ @dbus.service.method("org.laptop.SingleProcess", in_signature="a{ss}")
+ def create(self, handle_dict):
+ handle = activityhandle.create_from_dict(handle_dict)
+ create_activity_instance(self.constructor, handle)
+
+def main():
+ parser = OptionParser()
+ parser.add_option("-b", "--bundle-id", dest="bundle_id",
+ help="identifier of the activity bundle")
+ parser.add_option("-a", "--activity-id", dest="activity_id",
+ help="identifier of the activity instance")
+ parser.add_option("-o", "--object-id", dest="object_id",
+ help="identifier of the associated datastore object")
+ parser.add_option("-u", "--uri", dest="uri",
+ help="URI to load")
+ parser.add_option('-s', '--single-process', dest='single_process',
+ action='store_true',
+ help='start all the instances in the same process')
+ (options, args) = parser.parse_args()
+
+ logger.start()
+
+ if 'SUGAR_BUNDLE_PATH' not in os.environ:
+ print 'SUGAR_BUNDLE_PATH is not defined in the environment.'
+ sys.exit(1)
+
+ if len(args) == 0:
+ print 'A python class must be specified as first argument.'
+ sys.exit(1)
+
+ bundle_path = os.environ['SUGAR_BUNDLE_PATH']
+ sys.path.append(bundle_path)
+
+ bundle = ActivityBundle(bundle_path)
+
+ os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id()
+ os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name()
+ os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version())
+
+ gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path())
+
+ gettext.bindtextdomain(bundle.get_bundle_id(),
+ bundle.get_locale_path())
+ gettext.textdomain(bundle.get_bundle_id())
+
+ splitted_module = args[0].rsplit('.', 1)
+ module_name = splitted_module[0]
+ class_name = splitted_module[1]
+
+ module = __import__(module_name)
+ for comp in module_name.split('.')[1:]:
+ module = getattr(module, comp)
+
+ activity_constructor = getattr(module, class_name)
+ activity_handle = activityhandle.ActivityHandle(
+ activity_id=options.activity_id,
+ object_id=options.object_id, uri=options.uri)
+
+ if options.single_process is True:
+ sessionbus = dbus.SessionBus()
+
+ service_name = get_single_process_name(options.bundle_id)
+ service_path = get_single_process_path(options.bundle_id)
+
+ bus_object = sessionbus.get_object(
+ 'org.freedesktop.DBus', '/org/freedesktop/DBus')
+ try:
+ name = bus_object.GetNameOwner(
+ service_name, dbus_interface='org.freedesktop.DBus')
+ except dbus.DBusException:
+ name = None
+
+ if not name:
+ service = SingleProcess(service_name, activity_constructor)
+ else:
+ single_process = sessionbus.get_object(service_name, service_path)
+ single_process.create(activity_handle.get_dict())
+
+ print 'Created %s in a single process.' % service_name
+ sys.exit(0)
+
+ if hasattr(module, 'start'):
+ module.start()
+
+ create_activity_instance(activity_constructor, activity_handle)
+
+ gtk.main()
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index 2c235d8..2cc4da7 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -205,13 +205,13 @@ class ActivityBundle(Bundle):
def get_locale_path(self):
"""Get the locale path inside the (installed) activity bundle."""
- if not self._unpacked:
+ if self._zip_file is not None:
raise NotInstalledException
return os.path.join(self._path, 'locale')
def get_icons_path(self):
"""Get the icons path inside the (installed) activity bundle."""
- if not self._unpacked:
+ if self._zip_file is not None:
raise NotInstalledException
return os.path.join(self._path, 'icons')
@@ -237,7 +237,7 @@ class ActivityBundle(Bundle):
def get_icon(self):
"""Get the activity icon name"""
icon_path = os.path.join('activity', self._icon + '.svg')
- if self._unpacked:
+ if self._zip_file is None:
return os.path.join(self._path, icon_path)
else:
icon_data = self.get_file(icon_path).read()
@@ -365,7 +365,7 @@ class ActivityBundle(Bundle):
raise RegistrationException
def uninstall(self, force=False):
- if self._unpacked:
+ if self._zip_file is None:
install_path = self._path
else:
if not self.is_installed():
@@ -396,11 +396,12 @@ class ActivityBundle(Bundle):
if mime_types is not None:
installed_icons_dir = os.path.join(xdg_data_home,
'icons/sugar/scalable/mimetypes')
- for f in os.listdir(installed_icons_dir):
- path = os.path.join(installed_icons_dir, f)
- if os.path.islink(path) and \
- os.readlink(path).startswith(install_path):
- os.remove(path)
+ if os.path.isdir(installed_icons_dir):
+ for f in os.listdir(installed_icons_dir):
+ path = os.path.join(installed_icons_dir, f)
+ if os.path.islink(path) and \
+ os.readlink(path).startswith(install_path):
+ os.remove(path)
self._uninstall(install_path)
diff --git a/src/sugar/bundle/bundle.py b/src/sugar/bundle/bundle.py
index 3b12932..0319b9e 100644
--- a/src/sugar/bundle/bundle.py
+++ b/src/sugar/bundle/bundle.py
@@ -19,6 +19,7 @@
import os
import logging
+import shutil
import StringIO
import zipfile
@@ -59,9 +60,9 @@ class Bundle:
self._zip_root_dir = None
if os.path.isdir(self._path):
- self._unpacked = True
+ self._zip_file = None
else:
- self._unpacked = False
+ self._zip_file = zipfile.ZipFile(self._path)
self._check_zip_bundle()
# manifest = self._get_file(self._infodir + '/contents')
@@ -72,9 +73,12 @@ class Bundle:
# if signature is None:
# raise MalformedBundleException('No signature file')
+ def __del__(self):
+ if self._zip_file is not None:
+ self._zip_file.close()
+
def _check_zip_bundle(self):
- zip_file = zipfile.ZipFile(self._path)
- file_names = zip_file.namelist()
+ file_names = self._zip_file.namelist()
if len(file_names) == 0:
raise MalformedBundleException('Empty zip file')
@@ -99,48 +103,42 @@ class Bundle:
def get_file(self, filename):
f = None
- if self._unpacked:
+ if self._zip_file is None:
path = os.path.join(self._path, filename)
try:
f = open(path,"rb")
except IOError:
return None
else:
- zip_file = zipfile.ZipFile(self._path)
path = os.path.join(self._zip_root_dir, filename)
try:
- data = zip_file.read(path)
+ data = self._zip_file.read(path)
f = StringIO.StringIO(data)
except KeyError:
logging.debug('%s not found.' % filename)
- zip_file.close()
return f
def is_file(self, filename):
- if self._unpacked:
+ if self._zip_file is None:
path = os.path.join(self._path, filename)
return os.path.isfile(path)
else:
- zip_file = zipfile.ZipFile(self._path)
path = os.path.join(self._zip_root_dir, filename)
try:
- zip_file.getinfo(path)
+ self._zip_file.getinfo(path)
except KeyError:
return False
- finally:
- zip_file.close()
return True
def is_dir(self, filename):
- if self._unpacked:
+ if self._zip_file is None:
path = os.path.join(self._path, filename)
return os.path.isdir(path)
else:
- zip_file = zipfile.ZipFile(self._path)
path = os.path.join(self._zip_root_dir, filename, "")
- for f in zip_file.namelist():
+ for f in self._zip_file.namelist():
if f.startswith(path):
return True
return False
@@ -150,7 +148,7 @@ class Bundle:
return self._path
def _unzip(self, install_dir):
- if self._unpacked:
+ if self._zip_file is None:
raise AlreadyInstalledException
if not os.path.isdir(install_dir):
@@ -163,10 +161,13 @@ class Bundle:
# FIXME: use manifest
if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', '-o', self._path,
'-x', 'mimetype', '-d', install_dir):
+ # clean up install dir after failure
+ shutil.rmtree(install_dir, ignore_errors=True)
+ # indicate failure.
raise ZipExtractException
def _zip(self, bundle_path):
- if not self._unpacked:
+ if self._zip_file is not None:
raise NotInstalledException
raise NotImplementedError
diff --git a/src/sugar/bundle/contentbundle.py b/src/sugar/bundle/contentbundle.py
index 32f38e3..09521be 100644
--- a/src/sugar/bundle/contentbundle.py
+++ b/src/sugar/bundle/contentbundle.py
@@ -194,7 +194,7 @@ class ContentBundle(Bundle):
return "file://" + urllib.pathname2url(self.get_start_path())
def is_installed(self):
- if self._unpacked:
+ if self._zip_file is None:
return True
elif os.path.isdir(self.get_root_dir()):
return True
@@ -206,7 +206,7 @@ class ContentBundle(Bundle):
self._run_indexer()
def uninstall(self):
- if self._unpacked:
+ if self._zip_file is None:
if not self.is_installed():
raise NotInstalledException
install_dir = self._path
diff --git a/src/sugar/eggsmclient-private.h b/src/sugar/eggsmclient-private.h
index 0b4ec40..d2958c9 100644
--- a/src/sugar/eggsmclient-private.h
+++ b/src/sugar/eggsmclient-private.h
@@ -25,6 +25,8 @@
G_BEGIN_DECLS
+#define EGG_SM_CLIENT_BACKEND_XSMP
+
GKeyFile *egg_sm_client_save_state (EggSMClient *client);
void egg_sm_client_quit_requested (EggSMClient *client);
void egg_sm_client_quit_cancelled (EggSMClient *client);
diff --git a/src/sugar/graphics/alert.py b/src/sugar/graphics/alert.py
index fad90bb..39c373c 100644
--- a/src/sugar/graphics/alert.py
+++ b/src/sugar/graphics/alert.py
@@ -85,7 +85,7 @@ class Alert(gtk.EventBox):
self._buttons_box.set_spacing(style.DEFAULT_SPACING)
self._hbox.pack_start(self._buttons_box)
- gtk.EventBox.__init__(self, **kwargs)
+ gobject.GObject.__init__(self, **kwargs)
self.set_visible_window(True)
self.add(self._hbox)
@@ -105,6 +105,7 @@ class Alert(gtk.EventBox):
if self._msg != value:
self._msg = value
self._msg_label.set_markup(self._msg)
+ self._msg_label.set_line_wrap(True)
elif pspec.name == 'icon':
if self._icon != value:
self._icon = value
diff --git a/src/sugar/graphics/palette.py b/src/sugar/graphics/palette.py
index 2c07389..c0a56fc 100644
--- a/src/sugar/graphics/palette.py
+++ b/src/sugar/graphics/palette.py
@@ -244,9 +244,7 @@ class Palette(gtk.Window):
self._menu_box = None
self._content = None
self._palette_popup_sid = None
- self._enter_invoker_hid = None
- self._leave_invoker_hid = None
- self._right_click_invoker_hid = None
+ self._invoker_hids = []
self.set_group_id("default")
@@ -274,8 +272,12 @@ class Palette(gtk.Window):
self._mouse_detector = MouseSpeedDetector(self, 200, 5)
self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
- def __destroy_cb(self, palette):
+ def __destroy_cb(self, palette):
self.set_group_id(None)
+
+ # Break the reference cycle. It looks like the gc is not able to free
+ # it, possibly because gtk.Menu memory handling is very special.
+ self.menu = None
if self._palette_popup_sid is not None:
_palette_observer.disconnect(self._palette_popup_sid)
@@ -312,21 +314,27 @@ class Palette(gtk.Window):
return gtk.gdk.Rectangle(x, y, width, height)
def _set_invoker(self, invoker):
- if self._invoker is not None:
- self._invoker.disconnect(self._enter_invoker_hid)
- self._invoker.disconnect(self._leave_invoker_hid)
- self._invoker.disconnect(self._right_click_invoker_hid)
+ for hid in self._invoker_hids[:]:
+ self._invoker.disconnect(hid)
+ self._invoker_hids.remove(hid)
self._invoker = invoker
if invoker is not None:
- self._enter_invoker_hid = self._invoker.connect(
- 'mouse-enter', self._invoker_mouse_enter_cb)
- self._leave_invoker_hid = self._invoker.connect(
- 'mouse-leave', self._invoker_mouse_leave_cb)
- self._right_click_invoker_hid = self._invoker.connect(
- 'right-click', self._invoker_right_click_cb)
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-enter', self._invoker_mouse_enter_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-leave', self._invoker_mouse_leave_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'right-click', self._invoker_right_click_cb))
if hasattr(invoker.props, 'widget'):
- self._label.props.accel_widget = invoker.props.widget
+ self._update_accel_widget()
+ logging.debug(('Setup widget', invoker.props.widget))
+ self._invoker_hids.append(self._invoker.connect(
+ 'notify::widget', self._invoker_widget_changed_cb))
+
+ def _update_accel_widget(self):
+ assert self.props.invoker is not None
+ self._label.props.accel_widget = self.props.invoker.props.widget
def set_primary_text(self, label, accel_path=None):
self._primary_text = label
@@ -611,6 +619,9 @@ class Palette(gtk.Window):
self.popup(immediate=immediate)
+ def _invoker_widget_changed_cb(self, invoker, spec):
+ self._update_accel_widget()
+
def _invoker_mouse_enter_cb(self, invoker):
self._mouse_detector.start()
@@ -750,6 +761,9 @@ class Invoker(gobject.GObject):
def detach(self):
self.parent = None
+ if self._palette is not None:
+ self._palette.destroy()
+ self._palette = None
def _get_position_for_alignment(self, alignment, palette_dim):
palette_halign = alignment[0]
@@ -939,6 +953,8 @@ class WidgetInvoker(Invoker):
else:
self._widget = parent
+ self.notify('widget')
+
self._enter_hid = self._widget.connect('enter-notify-event',
self.__enter_notify_event_cb)
self._leave_hid = self._widget.connect('leave-notify-event',
diff --git a/src/sugar/graphics/radiotoolbutton.py b/src/sugar/graphics/radiotoolbutton.py
index abdf54c..e2636dc 100644
--- a/src/sugar/graphics/radiotoolbutton.py
+++ b/src/sugar/graphics/radiotoolbutton.py
@@ -36,6 +36,12 @@ class RadioToolButton(gtk.RadioToolButton):
self._palette_invoker.attach_tool(self)
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
def set_tooltip(self, tooltip):
""" Set a simple palette with just a single label.
"""
diff --git a/src/sugar/graphics/style.py b/src/sugar/graphics/style.py
index 1b510d1..ce2abe8 100644
--- a/src/sugar/graphics/style.py
+++ b/src/sugar/graphics/style.py
@@ -46,15 +46,6 @@ def _compute_zoom_factor():
return 1.0
-def _compute_font_height(font):
- widget = gtk.Label('')
-
- context = widget.get_pango_context()
- pango_font = context.load_font(font.get_pango_desc())
- metrics = pango_font.get_metrics()
-
- return pango.PIXELS(metrics.get_ascent() + metrics.get_descent())
-
class Font(object):
def __init__(self, desc):
self._desc = desc
@@ -124,8 +115,8 @@ XLARGE_ICON_SIZE = zoom(55 * 2.75)
FONT_SIZE = zoom(7 * _XO_DPI / _get_screen_dpi())
FONT_NORMAL = Font('Bitstream Vera Sans %d' % FONT_SIZE)
FONT_BOLD = Font('Bitstream Vera Sans bold %d' % FONT_SIZE)
-FONT_NORMAL_H = _compute_font_height(FONT_NORMAL)
-FONT_BOLD_H = _compute_font_height(FONT_BOLD)
+FONT_NORMAL_H = zoom(24)
+FONT_BOLD_H = zoom(24)
TOOLBOX_SEPARATOR_HEIGHT = zoom(9)
TOOLBOX_HORIZONTAL_PADDING = zoom(75)
diff --git a/src/sugar/graphics/toggletoolbutton.py b/src/sugar/graphics/toggletoolbutton.py
index ec622b4..35c4bf1 100644
--- a/src/sugar/graphics/toggletoolbutton.py
+++ b/src/sugar/graphics/toggletoolbutton.py
@@ -30,6 +30,12 @@ class ToggleToolButton(gtk.ToggleToolButton):
self._palette_invoker = ToolInvoker(self)
self.set_named_icon(named_icon)
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
def set_named_icon(self, named_icon):
icon = Icon(icon_name=named_icon)
self.set_icon_widget(icon)
diff --git a/src/sugar/graphics/toolbutton.py b/src/sugar/graphics/toolbutton.py
index bf392c8..a80c67a 100644
--- a/src/sugar/graphics/toolbutton.py
+++ b/src/sugar/graphics/toolbutton.py
@@ -65,6 +65,18 @@ class ToolButton(gtk.ToolButton):
self.set_icon(icon_name)
self.connect('clicked', self.__button_clicked_cb)
+ self.get_child().connect('can-activate-accel',
+ self.__button_can_activate_accel_cb)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def __button_can_activate_accel_cb(self, button, signal_id):
+ # Accept activation via accelerators regardless of this widget's state
+ return True
def set_tooltip(self, tooltip):
""" Set a simple palette with just a single label.
diff --git a/src/sugar/graphics/tray.py b/src/sugar/graphics/tray.py
index 8296a53..d5e9b39 100644
--- a/src/sugar/graphics/tray.py
+++ b/src/sugar/graphics/tray.py
@@ -358,6 +358,12 @@ class TrayIcon(gtk.ToolItem):
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
def create_palette(self):
return None
diff --git a/src/sugar/gsm-session.c b/src/sugar/gsm-session.c
index 95dd7cb..3f0714c 100644
--- a/src/sugar/gsm-session.c
+++ b/src/sugar/gsm-session.c
@@ -151,53 +151,6 @@ end_phase (GsmSession *session)
}
static void
-app_condition_changed (GsmApp *app, gboolean condition, gpointer data)
-{
- GsmSession *session;
- GsmClient *client = NULL;
- GSList *cl = NULL;
-
- g_return_if_fail (data != NULL);
-
- session = (GsmSession *) data;
-
- /* Check for an existing session client for this app */
- for (cl = session->clients; cl; cl = cl->next)
- {
- GsmClient *c = GSM_CLIENT (cl->data);
-
- if (!strcmp (app->client_id, gsm_client_get_client_id (c)))
- client = c;
- }
-
- if (condition)
- {
- GError *error = NULL;
-
- if (app->pid <= 0 && client == NULL)
- gsm_app_launch (app, &error);
-
- if (error != NULL)
- {
- g_warning ("Not able to launch autostart app from its condition: %s",
- error->message);
-
- g_error_free (error);
- }
- }
- else
- {
- /* Kill client in case condition if false and make sure it won't
- * be automatically restarted by adding the client to
- * condition_clients */
- session->condition_clients =
- g_slist_prepend (session->condition_clients, client);
- gsm_client_die (client);
- app->pid = -1;
- }
-}
-
-static void
app_registered (GsmApp *app, gpointer data)
{
GsmSession *session = data;
@@ -241,10 +194,6 @@ phase_timeout (gpointer data)
static void
start_phase (GsmSession *session)
{
- GsmApp *app;
- GSList *a;
- GError *err = NULL;
-
g_debug ("starting phase %d\n", session->phase);
g_slist_free (session->pending_apps);
@@ -359,8 +308,6 @@ client_saved_state (GsmClient *client, gpointer data)
void
gsm_session_initiate_shutdown (GsmSession *session)
{
- gboolean logout_prompt;
-
if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
{
/* Already shutting down, nothing more to do */
diff --git a/src/sugar/gsm-session.h b/src/sugar/gsm-session.h
index 94577c9..d4880a9 100644
--- a/src/sugar/gsm-session.h
+++ b/src/sugar/gsm-session.h
@@ -73,6 +73,8 @@ typedef enum {
GSM_SESSION_LOGOUT_MODE_FORCE
} GsmSessionLogoutMode;
+GType gsm_session_get_type (void) G_GNUC_CONST;
+
void gsm_session_set_name (GsmSession *session,
const char *name);
diff --git a/src/sugar/profile.py b/src/sugar/profile.py
index 25b957e..1b08202 100644
--- a/src/sugar/profile.py
+++ b/src/sugar/profile.py
@@ -24,7 +24,7 @@ from sugar import env
from sugar import util
from sugar.graphics.xocolor import XoColor
-DEFAULT_JABBER_SERVER = 'olpc.collabora.co.uk'
+DEFAULT_JABBER_SERVER = ''
DEFAULT_VOLUME = 81
DEFAULT_TIMEZONE = 'UTC'
DEFAULT_HOT_CORNERS_DELAY = 0.0
@@ -104,7 +104,7 @@ class Profile(object):
_set_key(cp, 'Buddy', 'Color', self.color.to_string())
if self.backup1:
_set_key(cp, 'Server', 'Backup1', self.backup1)
- if self.jabber_server:
+ if self.jabber_server is not None:
_set_key(cp, 'Jabber', 'Server', self.jabber_server)
_set_key(cp, 'Date', 'Timezone', self.timezone)
diff --git a/src/sugar/sugar-grid.c b/src/sugar/sugar-grid.c
new file mode 100644
index 0000000..3fa7de5
--- /dev/null
+++ b/src/sugar/sugar-grid.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "sugar-grid.h"
+
+static void sugar_grid_class_init (SugarGridClass *grid_class);
+static void sugar_grid_init (SugarGrid *grid);
+
+
+G_DEFINE_TYPE(SugarGrid, sugar_grid, G_TYPE_OBJECT)
+
+void
+sugar_grid_setup(SugarGrid *grid, gint width, gint height)
+{
+ g_free(grid->weights);
+
+ grid->weights = g_new0(guchar, width * height);
+ grid->width = width;
+ grid->height = height;
+}
+
+static gboolean
+check_bounds(SugarGrid *grid, GdkRectangle *rect)
+{
+ return (grid->weights != NULL &&
+ grid->width >= rect->x + rect->width &&
+ grid->height >= rect->y + rect->height);
+}
+
+void
+sugar_grid_add_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to add weight outside the grid bounds.");
+ return;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ grid->weights[i + k * grid->width] += 1;
+ }
+ }
+}
+
+void
+sugar_grid_remove_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to remove weight outside the grid bounds.");
+ return;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ grid->weights[i + k * grid->width] -= 1;
+ }
+ }
+}
+
+guint
+sugar_grid_compute_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k, sum = 0;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to compute weight outside the grid bounds.");
+ return 0;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ sum += grid->weights[i + k * grid->width];
+ }
+ }
+
+ return sum;
+}
+
+static void
+sugar_grid_finalize(GObject *object)
+{
+ SugarGrid *grid = SUGAR_GRID(object);
+
+ g_free(grid->weights);
+}
+
+static void
+sugar_grid_class_init(SugarGridClass *grid_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS(grid_class);
+ gobject_class->finalize = sugar_grid_finalize;
+}
+
+static void
+sugar_grid_init(SugarGrid *grid)
+{
+ grid->weights = NULL;
+}
diff --git a/src/sugar/sugar-grid.h b/src/sugar/sugar-grid.h
new file mode 100644
index 0000000..d493a60
--- /dev/null
+++ b/src/sugar/sugar-grid.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SUGAR_GRID_H__
+#define __SUGAR_GRID_H__
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SugarGrid SugarGrid;
+typedef struct _SugarGridClass SugarGridClass;
+
+#define SUGAR_TYPE_GRID (sugar_grid_get_type())
+#define SUGAR_GRID(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_GRID, SugarGrid))
+#define SUGAR_GRID_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_GRID, SugarGridClass))
+#define SUGAR_IS_GRID(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_GRID))
+#define SUGAR_IS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_GRID))
+#define SUGAR_GRID_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_GRID, SugarGridClass))
+
+struct _SugarGrid {
+ GObject base_instance;
+
+ gint width;
+ gint height;
+ guchar *weights;
+};
+
+struct _SugarGridClass {
+ GObjectClass base_class;
+};
+
+GType sugar_grid_get_type (void);
+void sugar_grid_setup (SugarGrid *grid,
+ gint width,
+ gint height);
+void sugar_grid_add_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+void sugar_grid_remove_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+guint sugar_grid_compute_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+
+G_END_DECLS
+
+#endif /* __SUGAR_GRID_H__ */
diff --git a/src/sugar/sugar-menu.h b/src/sugar/sugar-menu.h
index 8773a31..74ce891 100644
--- a/src/sugar/sugar-menu.h
+++ b/src/sugar/sugar-menu.h
@@ -50,6 +50,7 @@ void sugar_menu_set_active (SugarMenu *menu,
gboolean active);
void sugar_menu_embed (SugarMenu *menu,
GtkContainer *parent);
+void sugar_menu_unembed (SugarMenu *menu);
G_END_DECLS
diff --git a/src/sugar/sugar-preview.c b/src/sugar/sugar-preview.c
index f54045b..44b83e4 100644
--- a/src/sugar/sugar-preview.c
+++ b/src/sugar/sugar-preview.c
@@ -19,6 +19,7 @@
#include <gdk/gdkx.h>
#include <gtk/gtkwindow.h>
+#include <X11/extensions/XShm.h>
#include "sugar-preview.h"
@@ -37,8 +38,6 @@ sugar_preview_set_size(SugarPreview *preview, int width, int height)
GdkPixbuf *
sugar_preview_get_pixbuf(SugarPreview *preview)
{
- GdkPixbuf *pixbuf;
-
if (preview->pixbuf != NULL) {
return preview->pixbuf;
}
@@ -70,16 +69,38 @@ sugar_preview_clear(SugarPreview *preview)
}
}
+static gboolean
+widget_is_off_screen(GtkWidget *widget)
+{
+ GdkScreen *screen;
+ gint x, y, width, height;
+
+ screen = gtk_widget_get_screen(widget);
+
+ gdk_window_get_geometry(widget->window, &x, &y, &width, &height, NULL);
+
+ return (x < 0 || y < 0 ||
+ x + width > gdk_screen_get_width(screen) ||
+ y + height > gdk_screen_get_height(screen));
+}
+
void
-sugar_preview_take_screenshot(SugarPreview *preview, GdkDrawable *drawable)
+sugar_preview_take_screenshot(SugarPreview *preview, GtkWidget *widget)
{
+ GdkDrawable *drawable;
GdkScreen *screen;
GdkVisual *visual;
GdkColormap *colormap;
gint width, height;
+ if (widget->window == NULL || widget_is_off_screen(widget)) {
+ return;
+ }
+
sugar_preview_clear(preview);
+ drawable = GDK_DRAWABLE(widget->window);
+
gdk_drawable_get_size(drawable, &width, &height);
screen = gdk_drawable_get_screen(drawable);
@@ -92,7 +113,7 @@ sugar_preview_take_screenshot(SugarPreview *preview, GdkDrawable *drawable)
XShmGetImage(GDK_SCREEN_XDISPLAY(screen),
GDK_DRAWABLE_XID(drawable),
gdk_x11_image_get_ximage(preview->image),
- 0, 0, AllPlanes, ZPixmap);
+ 0, 0, AllPlanes);
}
static void
diff --git a/src/sugar/sugar-preview.h b/src/sugar/sugar-preview.h
index 6029cc1..70ea156 100644
--- a/src/sugar/sugar-preview.h
+++ b/src/sugar/sugar-preview.h
@@ -50,7 +50,7 @@ struct _SugarPreviewClass {
GType sugar_preview_get_type (void);
void sugar_preview_take_screenshot (SugarPreview *preview,
- GdkDrawable *drawable);
+ GtkWidget *widget);
void sugar_preview_set_size (SugarPreview *preview,
int width,
int height);
diff --git a/src/sugar/util.py b/src/sugar/util.py
index 8f81210..ad02d86 100644
--- a/src/sugar/util.py
+++ b/src/sugar/util.py
@@ -21,9 +21,11 @@ import sha
import random
import binascii
import string
-from gettext import gettext as _
import gettext
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+_ngettext = lambda msg1, msg2, n: gettext.dngettext('sugar-toolkit', msg1, msg2, n)
+
def printable_hash(in_hash):
"""Convert binary hash data into printable characters."""
printable = ""
@@ -222,8 +224,8 @@ def timestamp_to_elapsed_string(timestamp, max_levels=2):
if levels > 0:
time_period += COMMA
- time_period += gettext.ngettext(name_singular, name_plural,
- elapsed_units) % elapsed_units
+ time_period += _ngettext(name_singular, name_plural,
+ elapsed_units) % elapsed_units
elapsed_seconds -= elapsed_units * factor