Rename the module to sugar3

The old gtk-2 based module will be present in
the 0.94 branch in the sugar-toolkit.

Signed-off-by: Simon Schampijer <simon@laptop.org>
Acked-by: Sascha Silbe <silbe@activitycentral.com>
This commit is contained in:
Simon Schampijer
2011-10-29 10:19:34 +02:00
parent 516d7fc700
commit 000ed75cbe
103 changed files with 25 additions and 25 deletions
+4
View File
@@ -0,0 +1,4 @@
sugar-marshal.c
sugar-marshal.h
_sugarext.c
_sugarext.c
+1
View File
@@ -0,0 +1 @@
LGPL
+83
View File
@@ -0,0 +1,83 @@
SUBDIRS = activity bundle graphics presence datastore
sugardir = $(pythondir)/sugar3
sugar_PYTHON = \
env.py \
network.py \
profile.py \
session.py \
util.py \
wm.py
pkgpyexecdir = $(pythondir)/sugar3
pkgpyexec_LTLIBRARIES = _sugarext.la
_sugarext_la_CFLAGS = \
-DHAVE_ALSA \
$(WARN_CFLAGS) \
$(EXT_CFLAGS) \
$(PYTHON_INCLUDES)
_sugarext_la_LDFLAGS = -module -avoid-version
_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 \
gsm-client.c \
gsm-client-xsmp.h \
gsm-client-xsmp.c \
gsm-xsmp.h \
gsm-xsmp.c \
gsm-session.h \
gsm-session.c \
eggaccelerators.c \
eggaccelerators.h \
eggdesktopfile.h \
eggdesktopfile.c \
eggsmclient.h \
eggsmclient.c \
eggsmclient-private.h \
eggsmclient-xsmp.c \
sugar-grid.c \
sugar-grid.h \
sugar-key-grabber.c \
sugar-key-grabber.h \
sugar-menu.h \
sugar-menu.c
BUILT_SOURCES = \
_sugarext.c \
sugar-marshal.c \
sugar-marshal.h
_sugarext.c: _sugarext.defs _sugarext.override
.defs.c:
(cd $(srcdir)\
&& $(PYGTK_CODEGEN) \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gtk-types.defs \
--override $*.override \
--prefix py$* $*.defs) > gen-$*.c \
&& cp gen-$*.c $*.c \
&& rm -f gen-$*.c
sugar-marshal.c: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header --body > sugar-marshal.c
sugar-marshal.h: sugar-marshal.list
$(GLIB_GENMARSHAL) --prefix=sugar_marshal \
$(srcdir)/sugar-marshal.list --header > sugar-marshal.h
CLEANFILES = $(BUILT_SOURCES)
EXTRA_DIST = sugar-marshal.list _sugarext.defs _sugarext.override
+339
View File
@@ -0,0 +1,339 @@
;; -*- scheme -*-
; object definitions
(define-object KeyGrabber
(in-module "Sugar")
(parent "GObject")
(c-name "SugarKeyGrabber")
(gtype-id "SUGAR_TYPE_KEY_GRABBER")
)
(define-object Menu
(in-module "Sugar")
(parent "GtkMenu")
(c-name "SugarMenu")
(gtype-id "SUGAR_TYPE_MENU")
)
(define-object Grid
(in-module "Sugar")
(parent "GObject")
(c-name "SugarGrid")
(gtype-id "SUGAR_TYPE_GRID")
)
(define-object SMClientXSMP
(in-module "Egg")
(parent "EggSMClient")
(c-name "EggSMClientXSMP")
(gtype-id "EGG_TYPE_SM_CLIENT_XSMP")
)
(define-object SMClient
(in-module "Egg")
(parent "GObject")
(c-name "EggSMClient")
(gtype-id "EGG_TYPE_SM_CLIENT")
)
(define-object Session
(in-module "Gsm")
(parent "GObject")
(c-name "GsmSession")
(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")
)
;; From sugar-menu.h
(define-method set_active
(of-object "SugarMenu")
(c-name "sugar_menu_set_active")
(return-type "none")
(parameters
'("gboolean" "active")
)
)
(define-method embed
(of-object "SugarMenu")
(c-name "sugar_menu_embed")
(return-type "none")
(parameters
'("GtkContainer" "container")
)
)
(define-method unembed
(of-object "SugarMenu")
(c-name "sugar_menu_unembed")
(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
(c-name "sugar_key_grabber_get_type")
(return-type "GType")
)
(define-method grab_keys
(of-object "SugarKeyGrabber")
(c-name "sugar_key_grabber_grab_keys")
(return-type "none")
(parameters
'("const-char*[]" "keys")
)
)
(define-method get_key
(of-object "SugarKeyGrabber")
(c-name "sugar_key_grabber_get_key")
(return-type "char*")
(parameters
'("guint" "keycode")
'("guint" "state")
)
)
(define-method is_modifier
(of-object "SugarKeyGrabber")
(c-name "sugar_key_grabber_is_modifier")
(return-type "gboolean")
(parameters
'("guint" "keycode")
'("guint" "mask" (default "-1"))
)
)
;; From eggsmclient.h
(define-function egg_sm_client_get_type
(c-name "egg_sm_client_get_type")
(return-type "GType")
)
(define-function egg_sm_client_get_option_group
(c-name "egg_sm_client_get_option_group")
(return-type "GOptionGroup*")
)
(define-method is_resumed
(of-object "EggSMClient")
(c-name "egg_sm_client_is_resumed")
(return-type "gboolean")
)
(define-method get_state_file
(of-object "EggSMClient")
(c-name "egg_sm_client_get_state_file")
(return-type "GKeyFile*")
)
(define-method set_restart_command
(of-object "EggSMClient")
(c-name "egg_sm_client_set_restart_command")
(return-type "none")
(parameters
'("int" "argc")
'("const-char**" "argv")
)
)
(define-method startup
(of-object "EggSMClient")
(c-name "egg_sm_client_startup")
(return-type "none")
)
(define-method will_quit
(of-object "EggSMClient")
(c-name "egg_sm_client_will_quit")
(return-type "none")
(parameters
'("gboolean" "will_quit")
)
)
(define-function egg_sm_client_end_session
(c-name "egg_sm_client_end_session")
(return-type "gboolean")
(parameters
'("EggSMClientEndStyle" "style")
'("gboolean" "request_confirmation")
)
)
;; From xsmp.h
(define-function xsmp_init
(c-name "gsm_xsmp_init")
(return-type "char*")
)
(define-function xsmp_run
(c-name "gsm_xsmp_run")
(return-type "none")
)
(define-function xsmp_shutdown
(c-name "gsm_xsmp_shutdown")
(return-type "none")
)
;; From session.h
(define-method set_name
(of-object "GsmSession")
(c-name "gsm_session_set_name")
(return-type "none")
(parameters
'("const-char*" "name")
)
)
(define-method start
(of-object "GsmSession")
(c-name "gsm_session_start")
(return-type "none")
)
(define-method get_phase
(of-object "GsmSession")
(c-name "gsm_session_get_phase")
(return-type "GsmSessionPhase")
)
(define-method initiate_shutdown
(of-object "GsmSession")
(c-name "gsm_session_initiate_shutdown")
(return-type "none")
)
(define-method cancel_shutdown
(of-object "GsmSession")
(c-name "gsm_session_cancel_shutdown")
(return-type "none")
)
(define-method register_client
(of-object "GsmSession")
(c-name "gsm_session_register_client")
(return-type "char*")
(parameters
'("GsmClient*" "client")
'("const-char*" "previous_id")
)
)
(define-function session_create_global
(c-name "gsm_session_create_global")
(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*")
)
+79
View File
@@ -0,0 +1,79 @@
/* -*- Mode: C; c-basic-offset: 4 -*- */
%%
headers
#include <Python.h>
#include "pygobject.h"
#include "sugar-grid.h"
#include "sugar-key-grabber.h"
#include "sugar-menu.h"
#include "gsm-session.h"
#include "gsm-xsmp.h"
#include "acme-volume-alsa.h"
#include "eggsmclient.h"
#include "eggsmclient-private.h"
#include <pygtk/pygtk.h>
#include <glib.h>
%%
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.Image as PyGtkImage_Type
%%
ignore-glob
*_get_type
_*
%%
override sugar_key_grabber_grab_keys kwargs
static PyObject *
_wrap_sugar_key_grabber_grab_keys(PyGObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = { "key", NULL };
PyObject *py_keys;
char **keys;
int i, len;
if (!PyArg_ParseTupleAndKeywords(args,kwargs,
"O:SugarKeyGrabber.grab_keys",
kwlist, &py_keys))
return NULL;
if (!PySequence_Check(py_keys) || (len = PySequence_Size(py_keys)) < 0) {
PyErr_SetString(PyExc_ValueError,
"keys should be a sequence of strings");
return NULL;
}
keys = g_new(char*, len + 1);
for (i = 0; i < len; i++) {
PyObject *item = PySequence_GetItem(py_keys, i);
if (!item) {
g_free(keys);
return NULL;
}
if (!PyString_Check(item)) {
PyErr_SetString(PyExc_TypeError, "key must be a string");
g_free(keys);
Py_DECREF(item);
return NULL;
}
keys[i] = PyString_AsString(item);
Py_DECREF(item);
}
keys[len] = NULL;
sugar_key_grabber_grab_keys (SUGAR_KEY_GRABBER(self->obj), (const char**) keys);
Py_INCREF(Py_None);
return Py_None;
}
%%
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2006-2007, 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h>
#include <pygtk/pygtk.h>
extern PyMethodDef py_sugarext_functions[];
void py_sugarext_register_classes (PyObject *d);
DL_EXPORT(void)
init_sugarext(void)
{
PyObject *m, *d;
init_pygobject();
init_pygtk();
m = Py_InitModule("_sugarext", py_sugarext_functions);
d = PyModule_GetDict(m);
py_sugarext_register_classes(d);
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugarext");
}
}
+317
View File
@@ -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_seconds (4,
(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;
}
+47
View File
@@ -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);
+127
View File
@@ -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;
}
+63
View File
@@ -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 */
+12
View File
@@ -0,0 +1,12 @@
sugardir = $(pythondir)/sugar3/activity
sugar_PYTHON = \
__init__.py \
activity.py \
activityfactory.py \
activityhandle.py \
activityservice.py \
bundlebuilder.py \
i18n.py \
main.py \
namingalert.py \
widgets.py
+55
View File
@@ -0,0 +1,55 @@
# Copyright (C) 2006-2007, 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.
"""Activity implementation code for Sugar-based activities
Each activity within the OLPC environment must provide two
dbus services. The first, patterned after the
sugar.activity.activityfactory.ActivityFactory
class is responsible for providing a "create" method which
takes a small dictionary with values corresponding to a
sugar.activity.activityhandle.ActivityHandle
describing an individual instance of the activity.
Each activity so registered is described by a
sugar.activity.bundle.Bundle
instance, which parses a specially formatted activity.info
file (stored in the activity directory's ./activity
subdirectory). The
sugar.activity.bundlebuilder
module provides facilities for the standard setup.py module
which produces and registers bundles from activity source
directories.
Once instantiated by the ActivityFactory's create method,
each activity must provide an introspection API patterned
after the
sugar.activity.activityservice.ActivityService
class. This class allows for querying the ID of the root
window, requesting sharing across the network, and basic
"what type of application are you" queries.
"""
File diff suppressed because it is too large Load Diff
+374
View File
@@ -0,0 +1,374 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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.
"""Shell side object which manages request to start activity
UNSTABLE. Activities are currently not allowed to run other activities so at
the moment there is no reason to stabilize this API.
"""
import logging
import uuid
import dbus
import gobject
from sugar.activity.activityhandle import ActivityHandle
from sugar import util
from sugar import env
from sugar.datastore import datastore
from errno import EEXIST, ENOSPC
import os
import tempfile
import subprocess
import pwd
_SHELL_SERVICE = 'org.laptop.Shell'
_SHELL_PATH = '/org/laptop/Shell'
_SHELL_IFACE = 'org.laptop.Shell'
_ACTIVITY_FACTORY_INTERFACE = 'org.laptop.ActivityFactory'
# helper method to close all filedescriptors
# borrowed from subprocess.py
try:
MAXFD = os.sysconf('SC_OPEN_MAX')
except ValueError:
MAXFD = 256
def _close_fds():
for i in xrange(3, MAXFD):
try:
os.close(i)
# pylint: disable=W0704
except Exception:
pass
def create_activity_id():
"""Generate a new, unique ID for this activity"""
return util.unique_id(uuid.getnode())
def get_environment(activity):
environ = os.environ.copy()
bin_path = os.path.join(activity.get_path(), 'bin')
activity_root = env.get_profile_path(activity.get_bundle_id())
if not os.path.exists(activity_root):
os.mkdir(activity_root)
data_dir = os.path.join(activity_root, 'instance')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
data_dir = os.path.join(activity_root, 'data')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
tmp_dir = os.path.join(activity_root, 'tmp')
if not os.path.exists(tmp_dir):
os.mkdir(tmp_dir)
environ['SUGAR_BUNDLE_PATH'] = activity.get_path()
environ['SUGAR_BUNDLE_ID'] = activity.get_bundle_id()
environ['SUGAR_ACTIVITY_ROOT'] = activity_root
environ['PATH'] = bin_path + ':' + environ['PATH']
if activity.get_path().startswith(env.get_user_activities_path()):
environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(),
'locale')
return environ
def get_command(activity, activity_id=None, object_id=None, uri=None,
activity_invite=False):
if not activity_id:
activity_id = create_activity_id()
command = activity.get_command().split(' ')
command.extend(['-b', activity.get_bundle_id()])
command.extend(['-a', activity_id])
if object_id is not None:
command.extend(['-o', object_id])
if uri is not None:
command.extend(['-u', uri])
if activity_invite:
command.append('-i')
# if the command is in $BUNDLE_ROOT/bin, execute the absolute path so there
# is no need to mangle with the shell's PATH
if '/' not in command[0]:
bin_path = os.path.join(activity.get_path(), 'bin')
absolute_path = os.path.join(bin_path, command[0])
if os.path.exists(absolute_path):
command[0] = absolute_path
logging.debug('launching: %r', command)
return command
def open_log_file(activity):
i = 1
while True:
path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i))
try:
fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0644)
f = os.fdopen(fd, 'w', 0)
return (path, f)
except OSError, e:
if e.errno == EEXIST:
i += 1
elif e.errno == ENOSPC:
# not the end of the world; let's try to keep going.
return ('/dev/null', open('/dev/null', 'w'))
else:
raise e
class ActivityCreationHandler(gobject.GObject):
"""Sugar-side activity creation interface
This object uses a dbus method on the ActivityFactory
service to create the new activity. It generates
GObject events in response to the success/failure of
activity startup using callbacks to the service's
create call.
"""
def __init__(self, bundle, handle):
"""Initialise the handler
bundle -- the ActivityBundle to launch
activity_handle -- stores the values which are to
be passed to the service to uniquely identify
the activity to be created and the sharing
service that may or may not be connected with it
sugar.activity.activityhandle.ActivityHandle instance
calls the "create" method on the service for this
particular activity type and registers the
_reply_handler and _error_handler methods on that
call's results.
The specific service which creates new instances of this
particular type of activity is created during the activity
registration process in shell bundle registry which creates
service definition files for each registered bundle type.
If the file '/etc/olpc-security' exists, then activity launching
will be delegated to the prototype 'Rainbow' security service.
"""
gobject.GObject.__init__(self)
self._bundle = bundle
self._service_name = bundle.get_bundle_id()
self._handle = handle
bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
self._shell = dbus.Interface(bus_object, _SHELL_IFACE)
if handle.activity_id is not None and handle.object_id is None:
datastore.find({'activity_id': self._handle.activity_id},
reply_handler=self._find_object_reply_handler,
error_handler=self._find_object_error_handler)
else:
self._launch_activity()
def _launch_activity(self):
if self._handle.activity_id != None:
self._shell.ActivateActivity(self._handle.activity_id,
reply_handler=self._activate_reply_handler,
error_handler=self._activate_error_handler)
else:
self._create_activity()
def _create_activity(self):
if self._handle.activity_id is None:
self._handle.activity_id = create_activity_id()
self._shell.NotifyLaunch(
self._service_name, self._handle.activity_id,
reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_error_handler)
environ = get_environment(self._bundle)
(log_path, log_file) = open_log_file(self._bundle)
command = get_command(self._bundle, self._handle.activity_id,
self._handle.object_id, self._handle.uri,
self._handle.invited)
dev_null = file('/dev/null', 'w')
environment_dir = None
rainbow_found = subprocess.call(['which', 'rainbow-run'],
stdout=dev_null, stderr=dev_null) == 0
use_rainbow = rainbow_found and os.path.exists('/etc/olpc-security')
if use_rainbow:
environment_dir = tempfile.mkdtemp()
command = ['sudo', '-E', '--',
'rainbow-run',
'-v', '-v',
'-a', 'rainbow-sugarize',
'-s', '/var/spool/rainbow/2',
'-f', '1',
'-f', '2',
'-c', self._bundle.get_path(),
'-u', pwd.getpwuid(os.getuid()).pw_name,
'-i', environ['SUGAR_BUNDLE_ID'],
'-e', environment_dir,
'--',
] + command
for key, value in environ.items():
file_path = os.path.join(environment_dir, str(key))
open(file_path, 'w').write(str(value))
log_file.write(' '.join(command) + '\n\n')
dev_null = file('/dev/null', 'r')
child = subprocess.Popen([str(s) for s in command],
env=environ,
cwd=str(self._bundle.get_path()),
close_fds=True,
stdin=dev_null.fileno(),
stdout=log_file.fileno(),
stderr=log_file.fileno())
gobject.child_watch_add(child.pid,
_child_watch_cb,
(environment_dir, log_file,
self._handle.activity_id))
def _no_reply_handler(self, *args):
pass
def _notify_launch_failure_error_handler(self, err):
logging.error('Notify launch failure failed %s', err)
def _notify_launch_error_handler(self, err):
logging.debug('Notify launch failed %s', err)
def _activate_reply_handler(self, activated):
if not activated:
self._create_activity()
def _activate_error_handler(self, err):
logging.error('Activity activation request failed %s', err)
def _create_reply_handler(self):
logging.debug('Activity created %s (%s).',
self._handle.activity_id, self._service_name)
def _create_error_handler(self, err):
logging.error("Couldn't create activity %s (%s): %s",
self._handle.activity_id, self._service_name, err)
self._shell.NotifyLaunchFailure(
self._handle.activity_id, reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_failure_error_handler)
def _find_object_reply_handler(self, jobjects, count):
if count > 0:
if count > 1:
logging.debug('Multiple objects has the same activity_id.')
self._handle.object_id = jobjects[0]['uid']
self._launch_activity()
def _find_object_error_handler(self, err):
logging.error('Datastore find failed %s', err)
self._launch_activity()
def create(bundle, activity_handle=None):
"""Create a new activity from its name."""
if not activity_handle:
activity_handle = ActivityHandle()
return ActivityCreationHandler(bundle, activity_handle)
def create_with_uri(bundle, uri):
"""Create a new activity and pass the uri as handle."""
activity_handle = ActivityHandle(uri=uri)
return ActivityCreationHandler(bundle, activity_handle)
def create_with_object_id(bundle, object_id):
"""Create a new activity and pass the object id as handle."""
activity_handle = ActivityHandle(object_id=object_id)
return ActivityCreationHandler(bundle, activity_handle)
def _child_watch_cb(pid, condition, user_data):
# FIXME we use standalone method here instead of ActivityCreationHandler's
# member to have workaround code, see #1123
environment_dir, log_file, activity_id = user_data
if environment_dir is not None:
subprocess.call(['/bin/rm', '-rf', environment_dir])
if os.WIFEXITED(condition):
status = os.WEXITSTATUS(condition)
signum = None
message = 'Exited with status %s' % status
elif os.WIFSIGNALED(condition):
status = None
signum = os.WTERMSIG(condition)
message = 'Terminated by signal %s' % signum
else:
status = None
signum = os.WTERMSIG(condition)
message = 'Undefined status with signal %s' % signum
try:
log_file.write('%s, pid %s data %s\n' % (message, pid, user_data))
finally:
log_file.close()
# try to reap zombies in case SIGCHLD has not been set to SIG_IGN
try:
os.waitpid(pid, 0)
except OSError:
# SIGCHLD = SIG_IGN, no zombies
pass
if status or signum:
# XXX have to recreate dbus object since we can't reuse
# ActivityCreationHandler's one, see
# https://bugs.freedesktop.org/show_bug.cgi?id=23507
bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
shell = dbus.Interface(bus_object, _SHELL_IFACE)
def reply_handler_cb(*args):
pass
def error_handler_cb(error):
logging.error('Cannot send NotifyLaunchFailure to the shell')
# TODO send launching failure but activity could already show
# main window, see http://bugs.sugarlabs.org/ticket/1447#comment:19
shell.NotifyLaunchFailure(activity_id,
reply_handler=reply_handler_cb,
error_handler=error_handler_cb)
+75
View File
@@ -0,0 +1,75 @@
# Copyright (C) 2006-2007 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.
"""
STABLE.
"""
class ActivityHandle(object):
"""Data structure storing simple activity metadata"""
def __init__(self, activity_id=None, object_id=None, uri=None,
invited=False):
"""Initialise the handle from activity_id
activity_id -- unique id for the activity to be
created
object_id -- identity of the journal object
associated with the activity. It was used by
the journal prototype implementation, might
change when we do the real one.
When you resume an activity from the journal
the object_id will be passed in. It's optional
since new activities does not have an
associated object (yet).
XXX Not clear how this relates to the activity
id yet, i.e. not sure we really need both. TBF
uri -- URI associated with the activity. Used when
opening an external file or resource in the
activity, rather than a journal object
(downloads stored on the file system for
example or web pages)
invited -- the activity is being launched for handling an invite
from the network
"""
self.activity_id = activity_id
self.object_id = object_id
self.uri = uri
self.invited = invited
def get_dict(self):
"""Retrieve our settings as a dictionary"""
result = {'activity_id': self.activity_id,
'invited': self.invited}
if self.object_id:
result['object_id'] = self.object_id
if self.uri:
result['uri'] = self.uri
return result
def create_from_dict(handle_dict):
"""Create a handle from a dictionary of parameters"""
result = ActivityHandle(handle_dict['activity_id'],
object_id=handle_dict.get('object_id'),
uri=handle_dict.get('uri'),
invited=handle_dict.get('invited'))
return result
+83
View File
@@ -0,0 +1,83 @@
# Copyright (C) 2006-2007 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.
"""
UNSTABLE. It should really be internal to the Activity class.
"""
import logging
import dbus
import dbus.service
_ACTIVITY_SERVICE_NAME = 'org.laptop.Activity'
_ACTIVITY_SERVICE_PATH = '/org/laptop/Activity'
_ACTIVITY_INTERFACE = 'org.laptop.Activity'
class ActivityService(dbus.service.Object):
"""Base dbus service object that each Activity uses to export dbus methods.
The dbus service is separate from the actual Activity object so that we can
tightly control what stuff passes through the dbus python bindings."""
def __init__(self, activity):
"""Initialise the service for the given activity
activity -- sugar.activity.activity.Activity instance
Creates dbus services that use the instance's activity_id
as discriminants among all active services
of this type. That is, the services are all available
as names/paths derived from the instance's activity_id.
The various methods exposed on dbus are just forwarded
to the client Activity object's equally-named methods.
"""
activity.realize()
activity_id = activity.get_id()
service_name = _ACTIVITY_SERVICE_NAME + activity_id
object_path = _ACTIVITY_SERVICE_PATH + '/' + activity_id
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(service_name, bus=bus)
dbus.service.Object.__init__(self, bus_name, object_path)
self._activity = activity
@dbus.service.method(_ACTIVITY_INTERFACE)
def SetActive(self, active):
logging.debug('ActivityService.set_active: %s.', active)
self._activity.props.active = active
@dbus.service.method(_ACTIVITY_INTERFACE)
def InviteContact(self, account_path, contact_id):
self._activity.invite(account_path, contact_id)
@dbus.service.method(_ACTIVITY_INTERFACE)
def HandleViewSource(self):
self._activity.handle_view_source()
@dbus.service.method(_ACTIVITY_INTERFACE,
async_callbacks=('async_cb', 'async_err_cb'))
def GetDocumentPath(self, async_cb, async_err_cb):
try:
self._activity.get_document_path(async_cb, async_err_cb)
except Exception, e:
async_err_cb(e)
+399
View File
@@ -0,0 +1,399 @@
# 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.
"""
STABLE.
"""
import operator
import os
import sys
import zipfile
import tarfile
import shutil
import subprocess
import re
import gettext
from optparse import OptionParser
import logging
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 = []
base_dir = os.path.abspath(base_dir)
for root, dirs, files in os.walk(base_dir):
if ignore_files:
for pattern in ignore_files:
files = [f for f in files if not fnmatch(f, pattern)]
rel_path = root[len(base_dir) + 1:]
for f in files:
result.append(os.path.join(rel_path, f))
if ignore_dirs and root == base_dir:
for ignore in ignore_dirs:
if ignore in dirs:
dirs.remove(ignore)
return result
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_bundle_name()
self.bundle_id = bundle.get_bundle_id()
self.bundle_name = reduce(operator.add, self.activity_name.split())
self.bundle_root_dir = self.bundle_name + '.activity'
self.tar_root_dir = '%s-%s' % (self.bundle_name, self.version)
if self.dist_name:
self.xo_name = self.tar_name = self.dist_name
else:
self.xo_name = '%s-%s.xo' % (self.bundle_name, self.version)
self.tar_name = '%s-%s.tar.bz2' % (self.bundle_name, self.version)
class Builder(object):
def __init__(self, config):
self.config = config
def build(self):
self.build_locale()
def build_locale(self):
po_dir = os.path.join(self.config.source_dir, 'po')
if not self.config.bundle.is_dir(po_dir):
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') or f == 'pseudo.po':
continue
file_name = os.path.join(po_dir, f)
lang = f[:-3]
localedir = os.path.join(self.config.source_dir, 'locale', lang)
mo_path = os.path.join(localedir, 'LC_MESSAGES')
if not os.path.isdir(mo_path):
os.makedirs(mo_path)
mo_file = os.path.join(mo_path, '%s.mo' % self.config.bundle_id)
args = ['msgfmt', '--output-file=%s' % mo_file, file_name]
retcode = subprocess.call(args)
if retcode:
print 'ERROR - msgfmt failed with return code %i.' % retcode
cat = gettext.GNUTranslations(open(mo_file, 'r'))
translated_name = cat.gettext(self.config.activity_name)
linfo_file = os.path.join(localedir, 'activity.linfo')
f = open(linfo_file, 'w')
f.write('[Activity]\nname = %s\n' % translated_name)
f.close()
def get_files(self):
allfiles = list_files(self.config.source_dir,
IGNORE_DIRS, IGNORE_FILES)
return allfiles
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)
def get_files_in_git(self):
git_ls = subprocess.Popen(['git', 'ls-files'], stdout=subprocess.PIPE,
cwd=self.config.source_dir)
stdout, _ = git_ls.communicate()
if git_ls.returncode:
# Fall back to filtered list
return list_files(self.config.source_dir,
IGNORE_DIRS, IGNORE_FILES)
# pylint: disable=E1103
return [path.strip() for path in stdout.strip('\n').split('\n')]
class XOPackager(Packager):
def __init__(self, builder):
Packager.__init__(self, builder.config)
self.builder = builder
self.builder.build_locale()
self.package_path = os.path.join(self.config.dist_dir,
self.config.xo_name)
def package(self):
bundle_zip = zipfile.ZipFile(self.package_path, 'w',
zipfile.ZIP_DEFLATED)
for f in self.get_files_in_git():
bundle_zip.write(os.path.join(self.config.source_dir, f),
os.path.join(self.config.bundle_root_dir, f))
locale_dir = os.path.join(self.config.source_dir, 'locale')
locale_files = list_files(locale_dir, IGNORE_DIRS, IGNORE_FILES)
for f in locale_files:
bundle_zip.write(os.path.join(locale_dir, f),
os.path.join(self.config.bundle_root_dir,
'locale', f))
bundle_zip.close()
class SourcePackager(Packager):
def __init__(self, config):
Packager.__init__(self, config)
self.package_path = os.path.join(self.config.dist_dir,
self.config.tar_name)
def package(self):
tar = tarfile.open(self.package_path, 'w:bz2')
for f in self.get_files_in_git():
tar.add(os.path.join(self.config.source_dir, f),
os.path.join(self.config.tar_root_dir, f))
tar.close()
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)
self.config.bundle.install_mime_type(self.config.source_dir)
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)
bundle_path = os.path.join(bundle_path, config.bundle_root_dir)
try:
os.symlink(config.source_dir, bundle_path)
except OSError:
if os.path.islink(bundle_path):
print 'ERROR - The bundle has been already setup for development.'
else:
print 'ERROR - A bundle with the same name is already installed.'
def cmd_dist_xo(config, args):
"""Create a xo bundle package"""
if args:
print 'Usage: %prog dist_xo'
return
packager = XOPackager(Builder(config))
packager.package()
def cmd_fix_manifest(config, args):
'''Add missing files to the manifest (OBSOLETE)'''
print 'WARNING: The fix_manifest command is obsolete.'
print ' The MANIFEST file is no longer used in bundles,'
print ' please remove it.'
def cmd_dist_source(config, args):
"""Create a tar source package"""
if args:
print 'Usage: %prog dist_source'
return
packager = SourcePackager(config)
packager.package()
def cmd_install(config, args):
"""Install the activity in the system"""
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
installer = Installer(Builder(config))
installer.install(suboptions.prefix)
def cmd_genpot(config, args):
"""Generate the gettext pot file"""
if args:
print 'Usage: %prog genpot'
return
po_path = os.path.join(config.source_dir, 'po')
if not os.path.isdir(po_path):
os.mkdir(po_path)
python_files = []
for root, dirs_dummy, files in os.walk(config.source_dir):
for file_name in files:
if file_name.endswith('.py'):
file_path = os.path.relpath(os.path.join(root, file_name),
config.source_dir)
python_files.append(file_path)
# First write out a stub .pot file containing just the translated
# activity name, then have xgettext merge the rest of the
# translations into that. (We can't just append the activity name
# to the end of the .pot file afterwards, because that might
# create a duplicate msgid.)
pot_file = os.path.join('po', '%s.pot' % config.bundle_name)
escaped_name = re.sub('([\\\\"])', '\\\\\\1', config.activity_name)
f = open(pot_file, 'w')
f.write('#: activity/activity.info:2\n')
f.write('msgid "%s"\n' % escaped_name)
f.write('msgstr ""\n')
f.close()
args = ['xgettext', '--join-existing', '--language=Python',
'--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file]
args += python_files
retcode = subprocess.call(args)
if retcode:
print 'ERROR - xgettext failed with return code %i.' % retcode
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(usage='[action] [options]')
parser.disable_interspersed_args()
(options_, args) = parser.parse_args()
config = Config()
try:
globals()['cmd_' + args[0]](config, args[1:])
except (KeyError, IndexError):
print_commands()
if __name__ == '__main__':
start()
+162
View File
@@ -0,0 +1,162 @@
# Copyright (C) 2010 One Laptop Per Child
#
# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
#
# 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 gconf
from gettext import gettext
import locale
import os
import struct
import sys
import dateutil.parser
import time
_MO_BIG_ENDIAN = 0xde120495
_MO_LITTLE_ENDIAN = 0x950412de
def _read_bin(handle, format_string, byte_count):
read_bytes = handle.read(byte_count)
return_value = struct.unpack(format_string, read_bytes)
if len(return_value) == 1:
return return_value[0]
else:
return return_value
def _extract_header(file_path):
header = ''
handle = open(file_path, 'rb')
magic_number = _read_bin(handle, '<I', 4)
if magic_number == _MO_BIG_ENDIAN:
format_string = '>II'
elif magic_number == _MO_LITTLE_ENDIAN:
format_string = '<II'
else:
raise IOError('File does not seem to be a valid MO file')
version_, num_of_strings = _read_bin(handle, format_string, 8)
msgids_hash_offset, msgstrs_hash_offset = _read_bin(handle, \
format_string, 8)
handle.seek(msgids_hash_offset)
msgids_index = []
for i in range(num_of_strings):
msgids_index.append(_read_bin(handle, format_string, 8))
handle.seek(msgstrs_hash_offset)
msgstrs_index = []
for i in range(num_of_strings):
msgstrs_index.append(_read_bin(handle, format_string, 8))
for i in range(num_of_strings):
handle.seek(msgids_index[i][1])
msgid = handle.read(msgids_index[i][0])
if msgid == '':
handle.seek(msgstrs_index[i][1])
msgstr = handle.read(msgstrs_index[i][0])
header = msgstr
break
else:
continue
handle.close()
return header
def _extract_modification_time(file_path):
header = _extract_header(file_path)
items = header.split('\n')
for item in items:
if item.startswith('PO-Revision-Date:'):
time_str = item.split(': ')[1]
parsed_time = dateutil.parser.parse(time_str)
return time.mktime(parsed_time.timetuple())
raise ValueError('Could not find a revision date')
# We ship our own version of pgettext() because Python 2.x will never contain
# it: http://bugs.python.org/issue2504#msg122482
def pgettext(context, message):
"""
Return the localized translation of message, based on context and
the current global domain, language, and locale directory.
Similar to gettext(). Context is a string used to disambiguate
messages that are the same in the source language (usually english),
but might be different in one or more of the target languages.
"""
translation = gettext('\x04'.join([context, message]))
if '\x04' in translation:
return message
return translation
def get_locale_path(bundle_id):
""" Returns the locale path, which is the directory where the preferred
MO file is located.
The preferred MO file is the one with the latest translation.
@type bundle_id: string
@param bundle_id: The bundle id of the activity in question
@rtype: string
@return: the preferred locale path
"""
# Note: We pre-assign weights to the directories so that if no translations
# exist, the appropriate fallbacks (eg: bn for bn_BD) can be loaded
# The directory with the highest weight is returned, and if a MO file is
# found, the weight of the directory is set to the MO's modification time
# (as described in the MO header, and _not_ the filesystem mtime)
candidate_dirs = {}
if 'SUGAR_LOCALEDIR' in os.environ:
candidate_dirs[os.environ['SUGAR_LOCALEDIR']] = 2
gconf_client = gconf.client_get_default()
package_dir = gconf_client.get_string('/desktop/sugar/i18n/langpackdir')
if package_dir is not None and package_dir is not '':
candidate_dirs[package_dir] = 1
candidate_dirs[os.path.join(sys.prefix, 'share', 'locale')] = 0
for candidate_dir in candidate_dirs.keys():
if os.path.exists(candidate_dir):
full_path = os.path.join(candidate_dir, \
locale.getdefaultlocale()[0], 'LC_MESSAGES', \
bundle_id + '.mo')
if os.path.exists(full_path):
try:
candidate_dirs[candidate_dir] = \
_extract_modification_time(full_path)
except (IOError, ValueError):
# The mo file is damaged or has not been initialized
# Set lowest priority
candidate_dirs[candidate_dir] = -1
available_paths = sorted(candidate_dirs.iteritems(), key=lambda (k, v): \
(v, k), reverse=True)
preferred_path = available_paths[0][0]
return preferred_path
+160
View File
@@ -0,0 +1,160 @@
# 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
import sugar
from sugar.activity import activityhandle
from sugar.activity import i18n
from sugar.bundle.activitybundle import ActivityBundle
from sugar.graphics import style
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{sv}')
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')
parser.add_option('-i', '--invited', dest='invited',
action='store_true', default=False,
help='the activity is being launched for handling an '
'invite from the network')
(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())
# This code can be removed when we grow an xsettings daemon (the GTK+
# init routines will then automatically figure out the font settings)
settings = gtk.settings_get_default()
settings.set_property('gtk-font-name',
'%s %f' % (style.FONT_FACE, style.FONT_SIZE))
locale_path = i18n.get_locale_path(bundle.get_bundle_id())
gettext.bindtextdomain(bundle.get_bundle_id(), locale_path)
gettext.bindtextdomain('sugar-toolkit', sugar.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,
invited=options.invited)
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:
SingleProcess(service_name, activity_constructor)
else:
single_process = sessionbus.get_object(service_name, service_path)
single_process.create(activity_handle.get_dict(),
dbus_interface='org.laptop.SingleProcess')
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()
+306
View File
@@ -0,0 +1,306 @@
# Copyright (C) 2009 One Laptop Per Child
#
# 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 gettext
import os
import gio
import gtk
import gobject
import gconf
from sugar.graphics import style
from sugar.graphics.icon import Icon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.icon import get_icon_file_name
from sugar.graphics.toolbutton import ToolButton
from sugar.bundle.activitybundle import ActivityBundle
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def _get_icon_name(metadata):
file_name = None
mime_type = metadata.get('mime_type', '')
if not file_name and mime_type:
icons = gio.content_type_get_icon(mime_type)
for icon_name in icons.props.names:
file_name = get_icon_file_name(icon_name)
if file_name is not None:
break
if file_name is None or not os.path.exists(file_name):
file_name = get_icon_file_name('application-octet-stream')
return file_name
class NamingToolbar(gtk.Toolbar):
""" Toolbar of the naming alert
"""
__gtype_name__ = 'SugarNamingToolbar'
__gsignals__ = {
'keep-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
gtk.Toolbar.__init__(self)
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
icon = Icon()
icon.set_from_icon_name('activity-journal',
gtk.ICON_SIZE_LARGE_TOOLBAR)
icon.props.xo_color = color
self._add_widget(icon)
self._add_separator()
self._title = gtk.Label(_('Name this entry'))
self._add_widget(self._title)
self._add_separator(True)
self._keep_button = ToolButton('dialog-ok', tooltip=_('Keep'))
self._keep_button.props.accelerator = 'Return'
self._keep_button.connect('clicked', self.__keep_button_clicked_cb)
self.insert(self._keep_button, -1)
self._keep_button.show()
def _add_separator(self, expand=False):
separator = gtk.SeparatorToolItem()
separator.props.draw = False
if expand:
separator.set_expand(True)
else:
separator.set_size_request(style.DEFAULT_SPACING, -1)
self.insert(separator, -1)
separator.show()
def _add_widget(self, widget, expand=False):
tool_item = gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()
def __keep_button_clicked_cb(self, widget, data=None):
self.emit('keep-clicked')
class FavoriteIcon(gtk.ToggleButton):
def __init__(self):
gtk.ToggleButton.__init__(self)
self.set_relief(gtk.RELIEF_NONE)
self.set_focus_on_click(False)
self._icon = Icon(icon_name='emblem-favorite',
pixel_size=style.SMALL_ICON_SIZE)
self.set_image(self._icon)
self.connect('toggled', self.__toggled_cb)
self.connect('leave-notify-event', self.__leave_notify_event_cb)
self.connect('enter-notify-event', self.__enter_notify_event_cb)
def __toggled_cb(self, widget):
if self.get_active():
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
self._icon.props.xo_color = color
else:
self._icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self._icon.props.fill_color = style.COLOR_WHITE.get_svg()
def __enter_notify_event_cb(self, icon, event):
if not self.get_active():
self._icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
def __leave_notify_event_cb(self, icon, event):
if not self.get_active():
self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
class NamingAlert(gtk.Window):
__gtype_name__ = 'SugarNamingAlert'
def __init__(self, activity, bundle_path):
gtk.Window.__init__(self)
self._bundle_path = bundle_path
self._favorite_icon = None
self._title = None
self._description = None
self._tags = None
accel_group = gtk.AccelGroup()
self.set_data('sugar-accel-group', accel_group)
self.add_accel_group(accel_group)
self.set_border_width(style.LINE_WIDTH)
offset = style.GRID_CELL_SIZE
width = gtk.gdk.screen_width() - offset * 2
height = gtk.gdk.screen_height() - offset * 2
self.set_size_request(width, height)
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
self.set_decorated(False)
self.set_resizable(False)
self.set_modal(True)
self.connect('realize', self.__realize_cb)
self._activity = activity
vbox = gtk.VBox()
self.add(vbox)
vbox.show()
toolbar = NamingToolbar()
toolbar.connect('keep-clicked', self.__keep_cb)
vbox.pack_start(toolbar, False)
toolbar.show()
body = self._create_body()
vbox.pack_start(body, expand=True, fill=True)
body.show()
self._title.grab_focus()
def _create_body(self):
body = gtk.VBox(spacing=style.DEFAULT_SPACING)
body.set_border_width(style.DEFAULT_SPACING * 3)
header = self._create_header()
body.pack_start(header, expand=False, padding=style.DEFAULT_PADDING)
body.pack_start(self._create_separator(style.DEFAULT_SPACING),
expand=False)
body.pack_start(self._create_label(_('Description:')), expand=False)
description = self._activity.metadata.get('description', '')
description_box, self._description = self._create_text_view(description)
body.pack_start(description_box, expand=True, fill=True)
body.pack_start(self._create_separator(style.DEFAULT_PADDING),
expand=False)
body.pack_start(self._create_label(_('Tags:')), expand=False)
tags = self._activity.metadata.get('tags', '')
tags_box, self._tags = self._create_text_view(tags)
body.pack_start(tags_box, expand=True, fill=True)
body.show_all()
return body
def _create_label(self, text):
text = gtk.Label(text)
text.set_alignment(0, 0.5)
text.modify_fg(gtk.STATE_NORMAL,
style.COLOR_BUTTON_GREY.get_gdk_color())
return text
def _create_separator(self, height):
separator = gtk.HSeparator()
separator.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
separator.set_size_request(-1, height)
return separator
def _create_header(self):
header = gtk.HBox(spacing=style.DEFAULT_SPACING)
self._favorite_icon = FavoriteIcon()
header.pack_start(self._favorite_icon, expand=False)
entry_icon = self._create_entry_icon()
header.pack_start(entry_icon, expand=False)
self._title = self._create_title()
header.pack_start(self._title, expand=True)
return header
def _create_entry_icon(self):
bundle_id = self._activity.metadata.get('activity', '')
if not bundle_id:
bundle_id = self._activity.metadata.get('bundle_id', '')
if bundle_id == '':
file_name = _get_icon_name(self._activity.metadata)
else:
activity_bundle = ActivityBundle(self._bundle_path)
file_name = activity_bundle.get_icon()
entry_icon = Icon(file=file_name, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
if self._activity.metadata.get('icon-color'):
entry_icon.props.xo_color = XoColor( \
self._activity.metadata['icon-color'])
return entry_icon
def _create_title(self):
title = gtk.Entry()
title.set_text(self._activity.metadata.get('title', _('Untitled')))
return title
def _create_text_view(self, text):
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scrolled_window.set_border_width(style.LINE_WIDTH)
scrolled_window.set_shadow_type(gtk.SHADOW_IN)
text_view = gtk.TextView()
text_view.set_left_margin(style.DEFAULT_PADDING)
text_view.set_wrap_mode(gtk.WRAP_WORD_CHAR)
text_view.set_accepts_tab(False)
text_view.get_buffer().set_text(text)
scrolled_window.add(text_view)
return scrolled_window, text_view
def __realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.window.set_accept_focus(True)
def __keep_cb(self, widget):
if self._favorite_icon.get_active():
self._activity.metadata['keep'] = 1
else:
self._activity.metadata['keep'] = 0
self._activity.metadata['title'] = self._title.get_text()
text_buffer = self._tags.get_buffer()
start, end = text_buffer.get_bounds()
new_tags = text_buffer.get_text(start, end)
self._activity.metadata['tags'] = new_tags
text_buffer = self._description.get_buffer()
start, end = text_buffer.get_bounds()
new_description = text_buffer.get_text(start, end)
self._activity.metadata['description'] = new_description
self._activity.metadata['title_set_by_user'] = '1'
self._activity.close()
self.destroy()
+362
View File
@@ -0,0 +1,362 @@
# Copyright (C) 2009, Aleksey Lim, Simon Schampijer
#
# 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 gtk
import gettext
import gconf
import logging
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.toolbarbox import ToolbarButton
from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton
from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.toolbox import Toolbox
from sugar.graphics.xocolor import XoColor
from sugar.graphics.icon import Icon
from sugar.bundle.activitybundle import ActivityBundle
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def _create_activity_icon(metadata):
if metadata is not None and metadata.get('icon-color'):
color = XoColor(metadata['icon-color'])
else:
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
from sugar.activity.activity import get_bundle_path
bundle = ActivityBundle(get_bundle_path())
icon = Icon(file=bundle.get_icon(), xo_color=color)
return icon
class ActivityButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, **kwargs)
icon = _create_activity_icon(activity.metadata)
self.set_icon_widget(icon)
icon.show()
self.props.tooltip = activity.metadata['title']
activity.metadata.connect('updated', self.__jobject_updated_cb)
def __jobject_updated_cb(self, jobject):
self.props.tooltip = jobject['title']
class ActivityToolbarButton(ToolbarButton):
def __init__(self, activity, **kwargs):
toolbar = ActivityToolbar(activity, orientation_left=True)
toolbar.stop.hide()
ToolbarButton.__init__(self, page=toolbar, **kwargs)
icon = _create_activity_icon(activity.metadata)
self.set_icon_widget(icon)
icon.show()
class StopButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, 'activity-stop', **kwargs)
self.props.tooltip = _('Stop')
self.props.accelerator = '<Ctrl>Q'
self.connect('clicked', self.__stop_button_clicked_cb, activity)
def __stop_button_clicked_cb(self, button, activity):
activity.close()
class UndoButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-undo', **kwargs)
self.props.tooltip = _('Undo')
self.props.accelerator = '<Ctrl>Z'
class RedoButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-redo', **kwargs)
self.props.tooltip = _('Redo')
class CopyButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-copy', **kwargs)
self.props.tooltip = _('Copy')
self.props.accelerator = '<Ctrl>C'
class PasteButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-paste', **kwargs)
self.props.tooltip = _('Paste')
self.props.accelerator = '<Ctrl>V'
class ShareButton(RadioMenuButton):
def __init__(self, activity, **kwargs):
palette = RadioPalette()
self.private = RadioToolButton(
icon_name='zoom-home')
palette.append(self.private, _('Private'))
self.neighborhood = RadioToolButton(
icon_name='zoom-neighborhood',
group=self.private)
self._neighborhood_handle = self.neighborhood.connect(
'clicked', self.__neighborhood_clicked_cb, activity)
palette.append(self.neighborhood, _('My Neighborhood'))
activity.connect('shared', self.__update_share_cb)
activity.connect('joined', self.__update_share_cb)
RadioMenuButton.__init__(self, **kwargs)
self.props.palette = palette
if activity.max_participants == 1:
self.props.sensitive = False
def __neighborhood_clicked_cb(self, button, activity):
activity.share()
def __update_share_cb(self, activity):
self.neighborhood.handler_block(self._neighborhood_handle)
try:
if activity.shared_activity is not None and \
not activity.shared_activity.props.private:
self.private.props.sensitive = False
self.neighborhood.props.sensitive = False
self.neighborhood.props.active = True
else:
self.private.props.sensitive = True
self.neighborhood.props.sensitive = True
self.private.props.active = True
finally:
self.neighborhood.handler_unblock(self._neighborhood_handle)
# DEPRECATED
class KeepButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, **kwargs)
logging.warning('KeepButton has been deprecated since Sugar 0.94'
' and should not be used in newly written code.')
self.props.tooltip = _('Keep')
self.props.accelerator = '<Ctrl>S'
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
keep_icon = Icon(icon_name='document-save', xo_color=color)
keep_icon.show()
self.set_icon_widget(keep_icon)
self.connect('clicked', self.__keep_button_clicked_cb, activity)
def __keep_button_clicked_cb(self, button, activity):
activity.copy()
class TitleEntry(gtk.ToolItem):
def __init__(self, activity, **kwargs):
gtk.ToolItem.__init__(self)
self.set_expand(False)
self.entry = gtk.Entry(**kwargs)
self.entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
self.entry.set_text(activity.metadata['title'])
self.entry.connect('focus-out-event', self.__title_changed_cb, activity)
self.entry.show()
self.add(self.entry)
activity.metadata.connect('updated', self.__jobject_updated_cb)
activity.connect('_closing', self.__closing_cb)
def modify_bg(self, state, color):
gtk.ToolItem.modify_bg(self, state, color)
self.entry.modify_bg(state, color)
def __jobject_updated_cb(self, jobject):
if self.entry.flags() & gtk.HAS_FOCUS:
return
if self.entry.get_text() == jobject['title']:
return
self.entry.set_text(jobject['title'])
def __closing_cb(self, activity):
self.save_title(activity)
return False
def __title_changed_cb(self, editable, event, activity):
self.save_title(activity)
return False
def save_title(self, activity):
title = self.entry.get_text()
if title == activity.metadata['title']:
return
activity.metadata['title'] = title
activity.metadata['title_set_by_user'] = '1'
activity.save()
activity.set_title(title)
shared_activity = activity.get_shared_activity()
if shared_activity is not None:
shared_activity.props.name = title
class ActivityToolbar(gtk.Toolbar):
"""The Activity toolbar with the Journal entry title, sharing
and Stop buttons
All activities should have this toolbar. It is easiest to add it to your
Activity by using the ActivityToolbox.
"""
def __init__(self, activity, orientation_left=False):
gtk.Toolbar.__init__(self)
self._activity = activity
if activity.metadata:
title_button = TitleEntry(activity)
title_button.show()
self.insert(title_button, -1)
self.title = title_button.entry
if orientation_left == False:
separator = gtk.SeparatorToolItem()
separator.props.draw = False
separator.set_expand(True)
self.insert(separator, -1)
separator.show()
self.share = ShareButton(activity)
self.share.show()
self.insert(self.share, -1)
# DEPRECATED
self.keep = KeepButton(activity)
self.stop = StopButton(activity)
self.insert(self.stop, -1)
self.stop.show()
class EditToolbar(gtk.Toolbar):
"""Provides the standard edit toolbar for Activities.
Members:
undo -- the undo button
redo -- the redo button
copy -- the copy button
paste -- the paste button
separator -- A separator between undo/redo and copy/paste
This class only provides the 'edit' buttons in a standard layout,
your activity will need to either hide buttons which make no sense for your
Activity, or you need to connect the button events to your own callbacks:
## Example from Read.activity:
# Create the edit toolbar:
self._edit_toolbar = EditToolbar(self._view)
# Hide undo and redo, they're not needed
self._edit_toolbar.undo.props.visible = False
self._edit_toolbar.redo.props.visible = False
# Hide the separator too:
self._edit_toolbar.separator.props.visible = False
# As long as nothing is selected, copy needs to be insensitive:
self._edit_toolbar.copy.set_sensitive(False)
# When the user clicks the button, call _edit_toolbar_copy_cb()
self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
# Add the edit toolbar:
toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
# And make it visible:
self._edit_toolbar.show()
"""
def __init__(self):
gtk.Toolbar.__init__(self)
self.undo = UndoButton()
self.insert(self.undo, -1)
self.undo.show()
self.redo = RedoButton()
self.insert(self.redo, -1)
self.redo.show()
self.separator = gtk.SeparatorToolItem()
self.separator.set_draw(True)
self.insert(self.separator, -1)
self.separator.show()
self.copy = CopyButton()
self.insert(self.copy, -1)
self.copy.show()
self.paste = PasteButton()
self.insert(self.paste, -1)
self.paste.show()
class ActivityToolbox(Toolbox):
"""Creates the Toolbox for the Activity
By default, the toolbox contains only the ActivityToolbar. After creating
the toolbox, you can add your activity specific toolbars, for example the
EditToolbar.
To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
# Create the Toolbar with the ActivityToolbar:
toolbox = activity.ActivityToolbox(self)
... your code, inserting all other toolbars you need, like EditToolbar
# Add the toolbox to the activity frame:
self.set_toolbar_box(toolbox)
# And make it visible:
toolbox.show()
"""
def __init__(self, activity):
Toolbox.__init__(self)
self._activity_toolbar = ActivityToolbar(activity)
self.add_toolbar(_('Activity'), self._activity_toolbar)
self._activity_toolbar.show()
def get_activity_toolbar(self):
return self._activity_toolbar
+7
View File
@@ -0,0 +1,7 @@
sugardir = $(pythondir)/sugar3/bundle
sugar_PYTHON = \
__init__.py \
bundle.py \
activitybundle.py \
bundleversion.py \
contentbundle.py
+16
View File
@@ -0,0 +1,16 @@
# Copyright (C) 2006-2007, 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.
+341
View File
@@ -0,0 +1,341 @@
# Copyright (C) 2007, 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.
"""Sugar activity bundles
UNSTABLE.
"""
from ConfigParser import ConfigParser
import locale
import os
import shutil
import tempfile
import logging
import warnings
from sugar import env
from sugar import util
from sugar.bundle.bundle import Bundle, \
MalformedBundleException, NotInstalledException
from sugar.bundle.bundleversion import NormalizedVersion
from sugar.bundle.bundleversion import InvalidVersionError
class ActivityBundle(Bundle):
"""A Sugar activity bundle
See http://wiki.laptop.org/go/Activity_bundles for details
"""
MIME_TYPE = 'application/vnd.olpc-sugar'
DEPRECATED_MIME_TYPE = 'application/vnd.olpc-x-sugar'
_zipped_extension = '.xo'
_unzipped_extension = '.activity'
_infodir = 'activity'
def __init__(self, path):
Bundle.__init__(self, path)
self.activity_class = None
self.bundle_exec = None
self._name = None
self._local_name = None
self._icon = None
self._bundle_id = None
self._mime_types = None
self._show_launcher = True
self._tags = None
self._activity_version = '0'
self._installation_time = os.stat(path).st_mtime
info_file = self.get_file('activity/activity.info')
if info_file is None:
raise MalformedBundleException('No activity.info file')
self._parse_info(info_file)
linfo_file = self._get_linfo_file()
if linfo_file:
self._parse_linfo(linfo_file)
if self._local_name == None:
self._local_name = self._name
def _parse_info(self, info_file):
cp = ConfigParser()
cp.readfp(info_file)
section = 'Activity'
if cp.has_option(section, 'bundle_id'):
self._bundle_id = cp.get(section, 'bundle_id')
# FIXME deprecated
elif cp.has_option(section, 'service_name'):
warnings.warn('use bundle_id instead of service_name ' \
'in your activity.info', DeprecationWarning)
self._bundle_id = cp.get(section, 'service_name')
else:
raise MalformedBundleException(
'Activity bundle %s does not specify a bundle id' %
self._path)
if cp.has_option(section, 'name'):
self._name = cp.get(section, 'name')
else:
raise MalformedBundleException(
'Activity bundle %s does not specify a name' % self._path)
# FIXME class is deprecated
if cp.has_option(section, 'class'):
warnings.warn('use exec instead of class ' \
'in your activity.info', DeprecationWarning)
self.activity_class = cp.get(section, 'class')
elif cp.has_option(section, 'exec'):
self.bundle_exec = cp.get(section, 'exec')
else:
raise MalformedBundleException(
'Activity bundle %s must specify either class or exec' %
self._path)
if cp.has_option(section, 'mime_types'):
mime_list = cp.get(section, 'mime_types').strip(';')
self._mime_types = [mime.strip() for mime in mime_list.split(';')]
if cp.has_option(section, 'show_launcher'):
if cp.get(section, 'show_launcher') == 'no':
self._show_launcher = False
if cp.has_option(section, 'tags'):
tag_list = cp.get(section, 'tags').strip(';')
self._tags = [tag.strip() for tag in tag_list.split(';')]
if cp.has_option(section, 'icon'):
self._icon = cp.get(section, 'icon')
if cp.has_option(section, 'activity_version'):
version = cp.get(section, 'activity_version')
try:
NormalizedVersion(version)
except InvalidVersionError:
raise MalformedBundleException(
'Activity bundle %s has invalid version number %s' %
(self._path, version))
self._activity_version = version
def _get_linfo_file(self):
lang = locale.getdefaultlocale()[0]
if not lang:
return None
linfo_path = os.path.join('locale', lang, 'activity.linfo')
linfo_file = self.get_file(linfo_path)
if linfo_file is not None:
return linfo_file
linfo_path = os.path.join('locale', lang[:2], 'activity.linfo')
linfo_file = self.get_file(linfo_path)
if linfo_file is not None:
return linfo_file
return None
def _parse_linfo(self, linfo_file):
cp = ConfigParser()
cp.readfp(linfo_file)
section = 'Activity'
if cp.has_option(section, 'name'):
self._local_name = cp.get(section, 'name')
if cp.has_option(section, 'tags'):
tag_list = cp.get(section, 'tags').strip(';')
self._tags = [tag.strip() for tag in tag_list.split(';')]
def get_locale_path(self):
"""Get the locale path inside the (installed) activity bundle."""
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 self._zip_file is not None:
raise NotInstalledException
return os.path.join(self._path, 'icons')
def get_path(self):
"""Get the activity bundle path."""
return self._path
def get_name(self):
"""Get the activity user-visible name."""
return self._local_name
def get_bundle_name(self):
"""Get the activity bundle name."""
return self._name
def get_installation_time(self):
"""Get a timestamp representing the time at which this activity was
installed."""
return self._installation_time
def get_bundle_id(self):
"""Get the activity bundle id"""
return self._bundle_id
def get_icon(self):
"""Get the activity icon name"""
# FIXME: this should return the icon data, not a filename, so that
# we don't need to create a temp file in the zip case
icon_path = os.path.join('activity', self._icon + '.svg')
if self._zip_file is None:
return os.path.join(self._path, icon_path)
else:
icon_data = self.get_file(icon_path).read()
temp_file, temp_file_path = tempfile.mkstemp(prefix=self._icon,
suffix='.svg')
os.write(temp_file, icon_data)
os.close(temp_file)
return util.TempFilePath(temp_file_path)
def get_activity_version(self):
"""Get the activity version"""
return self._activity_version
def get_command(self):
"""Get the command to execute to launch the activity factory"""
if self.bundle_exec:
command = os.path.expandvars(self.bundle_exec)
else:
command = 'sugar-activity ' + self.activity_class
return command
def get_mime_types(self):
"""Get the MIME types supported by the activity"""
return self._mime_types
def get_tags(self):
"""Get the tags that describe the activity"""
return self._tags
def get_show_launcher(self):
"""Get whether there should be a visible launcher for the activity"""
return self._show_launcher
def install(self, install_dir=None):
if install_dir is None:
install_dir = env.get_user_activities_path()
self._unzip(install_dir)
install_path = os.path.join(install_dir, self._zip_root_dir)
self.install_mime_type(install_path)
return install_path
def install_mime_type(self, install_path):
""" Update the mime type database and install the mime type icon
"""
xdg_data_home = os.getenv('XDG_DATA_HOME',
os.path.expanduser('~/.local/share'))
mime_path = os.path.join(install_path, 'activity', 'mimetypes.xml')
if os.path.isfile(mime_path):
mime_dir = os.path.join(xdg_data_home, 'mime')
mime_pkg_dir = os.path.join(mime_dir, 'packages')
if not os.path.isdir(mime_pkg_dir):
os.makedirs(mime_pkg_dir)
installed_mime_path = os.path.join(mime_pkg_dir,
'%s.xml' % self._bundle_id)
self._symlink(mime_path, installed_mime_path)
os.spawnlp(os.P_WAIT, 'update-mime-database',
'update-mime-database', mime_dir)
mime_types = self.get_mime_types()
if mime_types is not None:
installed_icons_dir = os.path.join(xdg_data_home,
'icons/sugar/scalable/mimetypes')
if not os.path.isdir(installed_icons_dir):
os.makedirs(installed_icons_dir)
for mime_type in mime_types:
mime_icon_base = os.path.join(install_path, 'activity',
mime_type.replace('/', '-'))
svg_file = mime_icon_base + '.svg'
info_file = mime_icon_base + '.icon'
self._symlink(svg_file,
os.path.join(installed_icons_dir,
os.path.basename(svg_file)))
self._symlink(info_file,
os.path.join(installed_icons_dir,
os.path.basename(info_file)))
def _symlink(self, src, dst):
if not os.path.isfile(src):
return
if not os.path.islink(dst) and os.path.exists(dst):
raise RuntimeError('Do not remove %s if it was not '
'installed by sugar', dst)
logging.debug('Link resource %s to %s', src, dst)
if os.path.lexists(dst):
logging.debug('Relink %s', dst)
os.unlink(dst)
os.symlink(src, dst)
def uninstall(self, install_path, force=False, delete_profile=False):
if os.path.islink(install_path):
# Don't remove the actual activity dir if it's a symbolic link
# because we may be removing user data.
os.unlink(install_path)
return
xdg_data_home = os.getenv('XDG_DATA_HOME',
os.path.expanduser('~/.local/share'))
mime_dir = os.path.join(xdg_data_home, 'mime')
installed_mime_path = os.path.join(mime_dir, 'packages',
'%s.xml' % self._bundle_id)
if os.path.exists(installed_mime_path):
os.remove(installed_mime_path)
os.spawnlp(os.P_WAIT, 'update-mime-database',
'update-mime-database', mime_dir)
mime_types = self.get_mime_types()
if mime_types is not None:
installed_icons_dir = os.path.join(xdg_data_home,
'icons/sugar/scalable/mimetypes')
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)
if delete_profile:
bundle_profile_path = env.get_profile_path(self._bundle_id)
if os.path.exists(bundle_profile_path):
os.chmod(bundle_profile_path, 0775)
shutil.rmtree(bundle_profile_path, ignore_errors=True)
self._uninstall(install_path)
def is_user_activity(self):
return self.get_path().startswith(env.get_user_activities_path())
+200
View File
@@ -0,0 +1,200 @@
# Copyright (C) 2007, 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.
"""Sugar bundle file handler
UNSTABLE.
"""
import os
import logging
import shutil
import StringIO
import zipfile
class AlreadyInstalledException(Exception):
pass
class NotInstalledException(Exception):
pass
class InvalidPathException(Exception):
pass
class ZipExtractException(Exception):
pass
class RegistrationException(Exception):
pass
class MalformedBundleException(Exception):
pass
class Bundle(object):
"""A Sugar activity, content module, etc.
The bundle itself may be either a zip file or a directory
hierarchy, with metadata about the bundle stored various files
inside it.
This is an abstract base class. See ActivityBundle and
ContentBundle for more details on those bundle types.
"""
_zipped_extension = None
_unzipped_extension = None
def __init__(self, path):
self._path = path
self._zip_root_dir = None
self._zip_file = None
if not os.path.isdir(self._path):
try:
self._zip_file = zipfile.ZipFile(self._path)
except zipfile.error, exception:
raise MalformedBundleException('Error accessing zip file %r: '
'%s' % (self._path, exception))
self._check_zip_bundle()
def __del__(self):
if self._zip_file is not None:
self._zip_file.close()
def _check_zip_bundle(self):
file_names = self._zip_file.namelist()
if len(file_names) == 0:
raise MalformedBundleException('Empty zip file')
if file_names[0] == 'mimetype':
del file_names[0]
self._zip_root_dir = file_names[0].split('/')[0]
if self._zip_root_dir.startswith('.'):
raise MalformedBundleException(
'root directory starts with .')
if self._unzipped_extension is not None:
(name_, ext) = os.path.splitext(self._zip_root_dir)
if ext != self._unzipped_extension:
raise MalformedBundleException(
'All files in the bundle must be inside a single ' +
'directory whose name ends with %r' %
self._unzipped_extension)
for file_name in file_names:
if not file_name.startswith(self._zip_root_dir):
raise MalformedBundleException(
'All files in the bundle must be inside a single ' +
'top-level directory')
def get_file(self, filename):
f = None
if self._zip_file is None:
path = os.path.join(self._path, filename)
try:
f = open(path, 'rb')
except IOError:
return None
else:
path = os.path.join(self._zip_root_dir, filename)
try:
data = self._zip_file.read(path)
f = StringIO.StringIO(data)
except KeyError:
logging.debug('%s not found.', filename)
return f
def is_file(self, filename):
if self._zip_file is None:
path = os.path.join(self._path, filename)
return os.path.isfile(path)
else:
path = os.path.join(self._zip_root_dir, filename)
try:
self._zip_file.getinfo(path)
except KeyError:
return False
return True
def is_dir(self, filename):
if self._zip_file is None:
path = os.path.join(self._path, filename)
return os.path.isdir(path)
else:
path = os.path.join(self._zip_root_dir, filename, "")
for f in self._zip_file.namelist():
if f.startswith(path):
return True
return False
def get_path(self):
"""Get the bundle path."""
return self._path
def _unzip(self, install_dir):
if self._zip_file is None:
raise AlreadyInstalledException
if not os.path.isdir(install_dir):
os.mkdir(install_dir, 0775)
# zipfile provides API that in theory would let us do this
# correctly by hand, but handling all the oddities of
# Windows/UNIX mappings, extension attributes, deprecated
# features, etc makes it impractical.
if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', '-o', self._path,
'-x', 'mimetype', '-d', install_dir):
# clean up install dir after failure
shutil.rmtree(os.path.join(install_dir, self._zip_root_dir),
ignore_errors=True)
# indicate failure.
raise ZipExtractException
def _zip(self, bundle_path):
if self._zip_file is not None:
raise NotInstalledException
raise NotImplementedError
def _uninstall(self, install_path):
if not os.path.isdir(install_path):
raise InvalidPathException
if self._unzipped_extension is not None:
(name_, ext) = os.path.splitext(install_path)
if ext != self._unzipped_extension:
raise InvalidPathException
for root, dirs, files in os.walk(install_path, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
path = os.path.join(root, name)
if os.path.islink(path):
os.remove(path)
else:
os.rmdir(path)
os.rmdir(install_path)
+157
View File
@@ -0,0 +1,157 @@
# Copyright (C) 2010, OLPC
#
# 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.
#
# Based on the implementation of PEP 386, but adapted to our
# numeration schema.
#
import re
class InvalidVersionError(Exception):
"""The passed activity version can not be normalized."""
pass
VERSION_RE = re.compile(r'''
^
(?P<version>\d+) # minimum 'N'
(?P<extraversion>(?:\.\d+)*) # any number of extra '.N' segments
(?:
(?P<local>\-[a-zA-Z]*) # ignore any string in the comparison
)?
$''', re.VERBOSE)
class NormalizedVersion(object):
"""A normalized version.
Good:
1
1.2
1.2.3
1.2.3-peru
Bad:
1.2peru # must be separated with -
1.2. # can't end with '.'
1.02.5 # can't have a leading zero
"""
def __init__(self, activity_version):
"""Create a NormalizedVersion instance from a version string.
Keyword arguments:
activity_version -- The version string
"""
self._activity_version = activity_version
self.parts = []
self._local = None
if not isinstance(self._activity_version, str):
raise InvalidVersionError(self._activity_version)
match = VERSION_RE.search(self._activity_version)
if not match:
raise InvalidVersionError(self._activity_version)
groups = match.groupdict()
version = self._parse_version(groups['version'])
self.parts.append(version)
if groups['extraversion'] not in ('', None):
versions = self._parse_extraversions(groups['extraversion'][1:])
self.parts.extend(versions)
self._local = groups['local']
def _parse_version(self, version_string):
"""Verify that there is no leading zero and convert to integer.
Keyword arguments:
version -- string to be parsed
Return: Version
"""
if len(version_string) > 1 and version_string[0] == '0':
raise InvalidVersionError("Can not have leading zero in segment"
" %s in %r" % (version_string,
self._activity_version))
return int(version_string)
def _parse_extraversions(self, extraversion_string):
"""Split into N versions and convert them to integers, verify
that there are no leading zeros and drop trailing zeros.
Keyword arguments:
extraversion -- 'N.N.N...' sequence to be parsed
Return: List of extra versions
"""
nums = []
for n in extraversion_string.split("."):
if len(n) > 1 and n[0] == '0':
raise InvalidVersionError("Can not have leading zero in "
"segment %s in %r" % (n,
self._activity_version))
nums.append(int(n))
while nums and nums[-1] == 0:
nums.pop()
return nums
def __str__(self):
version_string = '.'.join(str(v) for v in self.parts)
if self._local != None:
version_string += self._local
return version_string
def __repr__(self):
return "%s('%s')" % (self.__class__.__name__, self)
def _cannot_compare(self, other):
raise TypeError("Can not compare %s and %s"
% (type(self).__name__, type(other).__name__))
def __eq__(self, other):
if not isinstance(other, NormalizedVersion):
self._cannot_compare(other)
return self.parts == other.parts
def __lt__(self, other):
if not isinstance(other, NormalizedVersion):
self._cannot_compare(other)
return self.parts < other.parts
def __ne__(self, other):
return not self.__eq__(other)
def __gt__(self, other):
return not (self.__lt__(other) or self.__eq__(other))
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)
def __ge__(self, other):
return self.__eq__(other) or self.__gt__(other)
+243
View File
@@ -0,0 +1,243 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2009 Aleksey Lim
#
# 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.
"""Sugar content bundles
UNSTABLE.
"""
from ConfigParser import ConfigParser
import os
import urllib
from sugar import env
from sugar.bundle.bundle import Bundle, NotInstalledException, \
MalformedBundleException
from sugar.bundle.bundleversion import NormalizedVersion
from sugar.bundle.bundleversion import InvalidVersionError
class ContentBundle(Bundle):
"""A Sugar content bundle
See http://wiki.laptop.org/go/Content_bundles for details
"""
MIME_TYPE = 'application/vnd.olpc-content'
_zipped_extension = '.xol'
_unzipped_extension = None
_infodir = 'library'
def __init__(self, path):
Bundle.__init__(self, path)
self._locale = None
self._l10n = None
self._category = None
self._name = None
self._subcategory = None
self._category_class = None
self._category_icon = None
self._library_version = '0'
self._bundle_class = None
self._activity_start = None
self._global_name = None
info_file = self.get_file('library/library.info')
if info_file is None:
raise MalformedBundleException('No library.info file')
self._parse_info(info_file)
if (self.get_file('index.html') is None and
self.get_file('library/library.xml') is None):
raise MalformedBundleException(
'Content bundle %s has neither index.html nor library.xml' %
self._path)
def _parse_info(self, info_file):
cp = ConfigParser()
cp.readfp(info_file)
section = 'Library'
if cp.has_option(section, 'name'):
self._name = cp.get(section, 'name')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a name' % self._path)
if cp.has_option(section, 'library_version'):
version = cp.get(section, 'library_version')
try:
NormalizedVersion(version)
except InvalidVersionError:
raise MalformedBundleException(
'Content bundle %s has invalid version number %s' %
(self._path, version))
self._library_version = version
if cp.has_option(section, 'l10n'):
l10n = cp.get(section, 'l10n')
if l10n == 'true':
self._l10n = True
elif l10n == 'false':
self._l10n = False
else:
raise MalformedBundleException(
'Content bundle %s has invalid l10n key %r' %
(self._path, l10n))
else:
raise MalformedBundleException(
'Content bundle %s does not specify if it is localized' %
self._path)
if cp.has_option(section, 'locale'):
self._locale = cp.get(section, 'locale')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a locale' % self._path)
if cp.has_option(section, 'category'):
self._category = cp.get(section, 'category')
else:
raise MalformedBundleException(
'Content bundle %s does not specify a category' % self._path)
if cp.has_option(section, 'global_name'):
self._global_name = cp.get(section, 'global_name')
else:
self._global_name = None
if cp.has_option(section, 'category_icon'):
self._category_icon = cp.get(section, 'category_icon')
else:
self._category_icon = None
if cp.has_option(section, 'category_class'):
self._category_class = cp.get(section, 'category_class')
else:
self._category_class = None
if cp.has_option(section, 'subcategory'):
self._subcategory = cp.get(section, 'subcategory')
else:
self._subcategory = None
if cp.has_option(section, 'bundle_class'):
self._bundle_class = cp.get(section, 'bundle_class')
else:
self._bundle_class = None
if cp.has_option(section, 'activity_start'):
self._activity_start = cp.get(section, 'activity_start')
else:
self._activity_start = 'index.html'
if self._bundle_class is None and self._global_name is None:
raise MalformedBundleException(
'Content bundle %s must specify either global_name or '
'bundle_class' % self._path)
def get_name(self):
return self._name
def get_library_version(self):
return self._library_version
def get_l10n(self):
return self._l10n
def get_locale(self):
return self._locale
def get_category(self):
return self._category
def get_category_icon(self):
return self._category_icon
def get_category_class(self):
return self._category_class
def get_subcategory(self):
return self._subcategory
def get_bundle_class(self):
return self._bundle_class
def get_activity_start(self):
return self._activity_start
def _run_indexer(self):
xdg_data_dirs = os.getenv('XDG_DATA_DIRS',
'/usr/local/share/:/usr/share/')
for path in xdg_data_dirs.split(':'):
indexer = os.path.join(path, 'library-common', 'make_index.py')
if os.path.exists(indexer):
os.spawnlp(os.P_WAIT, 'python', 'python', indexer)
def get_root_dir(self):
return os.path.join(env.get_user_library_path(), self._zip_root_dir)
def get_start_path(self):
return os.path.join(self.get_root_dir(), self._activity_start)
def get_start_uri(self):
return 'file://' + urllib.pathname2url(self.get_start_path())
def get_bundle_id(self):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
if self._bundle_class is not None:
return self._bundle_class
else:
return self._global_name
def get_activity_version(self):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
return self._library_version
def is_installed(self):
if self._zip_file is None:
return True
elif os.path.isdir(self.get_root_dir()):
return ContentBundle(self.get_root_dir()).get_library_version() \
== self.get_library_version()
else:
return False
def install(self):
# TODO ignore passed install_path argument
# needs rethinking while fixing ContentBundle support
install_path = env.get_user_library_path()
self._unzip(install_path)
self._run_indexer()
return self.get_root_dir()
def uninstall(self):
if self._zip_file is None:
if not self.is_installed():
raise NotInstalledException
install_dir = self._path
else:
install_dir = os.path.join(self.get_root_dir())
self._uninstall(install_dir)
self._run_indexer()
+4
View File
@@ -0,0 +1,4 @@
sugardir = $(pythondir)/sugar3/datastore
sugar_PYTHON = \
__init__.py \
datastore.py
+16
View File
@@ -0,0 +1,16 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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.
+549
View File
@@ -0,0 +1,549 @@
# Copyright (C) 2007, One Laptop Per Child
# Copyright (C) 2010, Simon Schampijer
#
# 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.
"""
STABLE
"""
import logging
import time
from datetime import datetime
import os
import tempfile
import gobject
import gconf
import gio
import dbus
import dbus.glib
from sugar import env
from sugar import mime
from sugar import dispatch
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
_data_store = None
def _get_data_store():
global _data_store
if not _data_store:
_bus = dbus.SessionBus()
_data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE,
DS_DBUS_PATH),
DS_DBUS_INTERFACE)
_data_store.connect_to_signal('Created', __datastore_created_cb)
_data_store.connect_to_signal('Deleted', __datastore_deleted_cb)
_data_store.connect_to_signal('Updated', __datastore_updated_cb)
return _data_store
def __datastore_created_cb(object_id):
metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
updated.send(None, object_id=object_id, metadata=metadata)
def __datastore_updated_cb(object_id):
metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
updated.send(None, object_id=object_id, metadata=metadata)
def __datastore_deleted_cb(object_id):
deleted.send(None, object_id=object_id)
created = dispatch.Signal()
deleted = dispatch.Signal()
updated = dispatch.Signal()
_get_data_store()
class DSMetadata(gobject.GObject):
"""A representation of the metadata associated with a DS entry."""
__gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self, properties=None):
gobject.GObject.__init__(self)
if not properties:
self._properties = {}
else:
self._properties = properties
default_keys = ['activity', 'activity_id',
'mime_type', 'title_set_by_user']
for key in default_keys:
if key not in self._properties:
self._properties[key] = ''
def __getitem__(self, key):
return self._properties[key]
def __setitem__(self, key, value):
if key not in self._properties or self._properties[key] != value:
self._properties[key] = value
self.emit('updated')
def __delitem__(self, key):
del self._properties[key]
def __contains__(self, key):
return self._properties.__contains__(key)
def has_key(self, key):
logging.warning(".has_key() is deprecated, use 'in'")
return key in self._properties
def keys(self):
return self._properties.keys()
def get_dictionary(self):
return self._properties
def copy(self):
return DSMetadata(self._properties.copy())
def get(self, key, default=None):
if key in self._properties:
return self._properties[key]
else:
return default
def update(self, properties):
"""Update all of the metadata"""
for (key, value) in properties.items():
self[key] = value
class DSObject(object):
"""A representation of a DS entry."""
def __init__(self, object_id, metadata=None, file_path=None):
self._update_signal_match = None
self._object_id = None
self.set_object_id(object_id)
self._metadata = metadata
self._file_path = file_path
self._destroyed = False
self._owns_file = False
def get_object_id(self):
return self._object_id
def set_object_id(self, object_id):
if self._update_signal_match is not None:
self._update_signal_match.remove()
if object_id is not None:
self._update_signal_match = _get_data_store().connect_to_signal(
'Updated', self.__object_updated_cb, arg0=object_id)
self._object_id = object_id
object_id = property(get_object_id, set_object_id)
def __object_updated_cb(self, object_id):
properties = _get_data_store().get_properties(self._object_id,
byte_arrays=True)
self._metadata.update(properties)
def get_metadata(self):
if self._metadata is None and not self.object_id is None:
properties = _get_data_store().get_properties(self.object_id)
metadata = DSMetadata(properties)
self._metadata = metadata
return self._metadata
def set_metadata(self, metadata):
if self._metadata != metadata:
self._metadata = metadata
metadata = property(get_metadata, set_metadata)
def get_file_path(self, fetch=True):
if fetch and self._file_path is None and not self.object_id is None:
self.set_file_path(_get_data_store().get_filename(self.object_id))
self._owns_file = True
return self._file_path
def set_file_path(self, file_path):
if self._file_path != file_path:
if self._file_path and self._owns_file:
if os.path.isfile(self._file_path):
os.remove(self._file_path)
self._owns_file = False
self._file_path = file_path
file_path = property(get_file_path, set_file_path)
def destroy(self):
if self._destroyed:
logging.warning('This DSObject has already been destroyed!.')
return
self._destroyed = True
if self._file_path and self._owns_file:
if os.path.isfile(self._file_path):
os.remove(self._file_path)
self._owns_file = False
self._file_path = None
def __del__(self):
if not self._destroyed:
logging.warning('DSObject was deleted without cleaning up first. '
'Call DSObject.destroy() before disposing it.')
self.destroy()
def copy(self):
return DSObject(None, self._metadata.copy(), self._file_path)
class RawObject(object):
"""A representation for objects not in the DS but
in the file system.
"""
def __init__(self, file_path):
stat = os.stat(file_path)
client = gconf.client_get_default()
metadata = {
'uid': file_path,
'title': os.path.basename(file_path),
'timestamp': stat.st_mtime,
'mime_type': gio.content_type_guess(filename=file_path),
'activity': '',
'activity_id': '',
'icon-color': client.get_string('/desktop/sugar/user/color'),
'description': file_path,
}
self.object_id = file_path
self._metadata = DSMetadata(metadata)
self._file_path = None
self._destroyed = False
def get_metadata(self):
return self._metadata
metadata = property(get_metadata)
def get_file_path(self, fetch=True):
# we have to create symlink since its a common practice
# to create hardlinks to jobject files
# and w/o this, it wouldn't work since we have file from mounted device
if self._file_path is None:
data_path = os.path.join(env.get_profile_path(), 'data')
self._file_path = tempfile.mktemp(
prefix='rawobject', dir=data_path)
if not os.path.exists(data_path):
os.makedirs(data_path)
os.symlink(self.object_id, self._file_path)
return self._file_path
file_path = property(get_file_path)
def destroy(self):
if self._destroyed:
logging.warning('This RawObject has already been destroyed!.')
return
self._destroyed = True
if self._file_path is not None:
if os.path.exists(self._file_path):
os.remove(self._file_path)
self._file_path = None
def __del__(self):
if not self._destroyed:
logging.warning('RawObject was deleted without cleaning up. '
'Call RawObject.destroy() before disposing it.')
self.destroy()
def get(object_id):
"""Get the properties of the object with the ID given.
Keyword arguments:
object_id -- unique identifier of the object
Return: a DSObject
"""
logging.debug('datastore.get')
if object_id.startswith('/'):
return RawObject(object_id)
metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
ds_object = DSObject(object_id, DSMetadata(metadata), None)
# TODO: register the object for updates
return ds_object
def create():
"""Create a new DSObject.
Return: a DSObject
"""
metadata = DSMetadata()
metadata['mtime'] = datetime.now().isoformat()
metadata['timestamp'] = int(time.time())
return DSObject(object_id=None, metadata=metadata, file_path=None)
def _update_ds_entry(uid, properties, filename, transfer_ownership=False,
reply_handler=None, error_handler=None, timeout=-1):
debug_properties = properties.copy()
if 'preview' in debug_properties:
debug_properties['preview'] = '<omitted>'
logging.debug('dbus_helpers.update: %s, %s, %s, %s', uid, filename,
debug_properties, transfer_ownership)
if reply_handler and error_handler:
_get_data_store().update(uid, dbus.Dictionary(properties), filename,
transfer_ownership,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
_get_data_store().update(uid, dbus.Dictionary(properties),
filename, transfer_ownership)
def _create_ds_entry(properties, filename, transfer_ownership=False):
object_id = _get_data_store().create(dbus.Dictionary(properties), filename,
transfer_ownership)
return object_id
def write(ds_object, update_mtime=True, transfer_ownership=False,
reply_handler=None, error_handler=None, timeout=-1):
"""Write the DSObject given to the datastore. Creates a new entry if
the entry does not exist yet.
Keyword arguments:
update_mtime -- boolean if the mtime of the entry should be regenerated
(default True)
transfer_ownership -- set it to true if the ownership of the entry should
be passed - who is responsible to delete the file
when done with it (default False)
reply_handler -- will be called with the method's return values as
arguments (default None)
error_handler -- will be called with an instance of a DBusException
representing a remote exception (default None)
timeout -- dbus timeout for the caller to wait (default -1)
"""
logging.debug('datastore.write')
properties = ds_object.metadata.get_dictionary().copy()
if update_mtime:
properties['mtime'] = datetime.now().isoformat()
properties['timestamp'] = int(time.time())
file_path = ds_object.get_file_path(fetch=False)
if file_path is None:
file_path = ''
# FIXME: this func will be sync for creates regardless of the handlers
# supplied. This is very bad API, need to decide what to do here.
if ds_object.object_id:
_update_ds_entry(ds_object.object_id,
properties,
file_path,
transfer_ownership,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
if reply_handler or error_handler:
logging.warning('datastore.write() cannot currently be called' \
'async for creates, see ticket 3071')
ds_object.object_id = _create_ds_entry(properties, file_path,
transfer_ownership)
ds_object.metadata['uid'] = ds_object.object_id
# TODO: register the object for updates
logging.debug('Written object %s to the datastore.', ds_object.object_id)
def delete(object_id):
"""Delete the datastore entry with the given uid.
Keyword arguments:
object_id -- uid of the datastore entry
"""
logging.debug('datastore.delete')
_get_data_store().delete(object_id)
def find(query, sorting=None, limit=None, offset=None, properties=None,
reply_handler=None, error_handler=None):
"""Find DS entries that match the query provided.
Keyword arguments:
query -- a dictionary containing metadata key value pairs
for a fulltext search use the key 'query' e.g. {'query': 'blue*'}
other possible well-known properties are:
'activity': 'my.organization.MyActivity'
'activity_id': '6f7f3acacca87886332f50bdd522d805f0abbf1f'
'title': 'My new project'
'title_set_by_user': '0'
'keep': '0'
'ctime': '1972-05-12T18:41:08'
'mtime': '2007-06-16T03:42:33'
'timestamp': 1192715145
'preview': ByteArray(png file data, 300x225 px)
'icon-color': '#ff0000,#ffff00'
'mime_type': 'application/x-my-activity'
'share-scope': # if shared
'buddies': '{}'
'description': 'some longer text'
'tags': 'one two'
sorting -- key to order results by e.g. 'timestamp' (default None)
limit -- return only limit results (default None)
offset -- return only results starting at offset (default None)
properties -- you can specify here a list of metadata you want to be
present in the result e.g. ['title, 'keep'] (default None)
reply_handler -- will be called with the method's return values as
arguments (default None)
error_handler -- will be called with an instance of a DBusException
representing a remote exception (default None)
Return: DSObjects matching the query, number of matches
"""
query = query.copy()
if properties is None:
properties = []
if sorting:
query['order_by'] = sorting
if limit:
query['limit'] = limit
if offset:
query['offset'] = offset
if reply_handler and error_handler:
_get_data_store().find(query, properties,
reply_handler=reply_handler,
error_handler=error_handler,
byte_arrays=True)
return
else:
entries, total_count = _get_data_store().find(query, properties,
byte_arrays=True)
ds_objects = []
for entry in entries:
object_id = entry['uid']
del entry['uid']
ds_object = DSObject(object_id, DSMetadata(entry), None)
ds_objects.append(ds_object)
return ds_objects, total_count
def copy(ds_object, mount_point):
"""Copy a datastore entry
Keyword arguments:
ds_object -- DSObject to copy
mount_point -- mount point of the new datastore entry
"""
new_ds_object = ds_object.copy()
new_ds_object.metadata['mountpoint'] = mount_point
if 'title' in ds_object.metadata:
filename = ds_object.metadata['title']
if 'mime_type' in ds_object.metadata:
mime_type = ds_object.metadata['mime_type']
extension = mime.get_primary_extension(mime_type)
if extension:
filename += '.' + extension
new_ds_object.metadata['suggested_filename'] = filename
# this will cause the file be retrieved from the DS
new_ds_object.file_path = ds_object.file_path
write(new_ds_object)
def mount(uri, options, timeout=-1):
"""Deprecated. API private to the shell. Mount a device.
Keyword arguments:
uri -- identifier of the device
options -- mount options
timeout -- dbus timeout for the caller to wait (default -1)
Return: empty string
"""
return _get_data_store().mount(uri, options, timeout=timeout)
def unmount(mount_point_id):
"""Deprecated. API private to the shell.
Keyword arguments:
mount_point_id -- id of the mount point
Note: API private to the shell.
"""
_get_data_store().unmount(mount_point_id)
def mounts():
"""Deprecated. Returns the mount point of the datastore. We get mount
points through gio now. API private to the shell.
Return: datastore mount point
"""
return _get_data_store().mounts()
def complete_indexing():
"""Deprecated. API private to the shell."""
logging.warning('The method complete_indexing has been deprecated.')
def get_unique_values(key):
"""Retrieve an array of unique values for a field.
Keyword arguments:
key -- only the property activity is currently supported
Return: list of activities
"""
return _get_data_store().get_uniquevaluesfor(
key, dbus.Dictionary({}, signature='ss'))
+702
View File
@@ -0,0 +1,702 @@
/* eggaccelerators.c
* Copyright (C) 2002 Red Hat, Inc.; Copyright 1998, 2001 Tim Janik
* Developed by Havoc Pennington, Tim Janik
*
* This 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.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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 "eggaccelerators.h"
#include <stdlib.h>
#include <string.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
enum
{
EGG_MODMAP_ENTRY_SHIFT = 0,
EGG_MODMAP_ENTRY_LOCK = 1,
EGG_MODMAP_ENTRY_CONTROL = 2,
EGG_MODMAP_ENTRY_MOD1 = 3,
EGG_MODMAP_ENTRY_MOD2 = 4,
EGG_MODMAP_ENTRY_MOD3 = 5,
EGG_MODMAP_ENTRY_MOD4 = 6,
EGG_MODMAP_ENTRY_MOD5 = 7,
EGG_MODMAP_ENTRY_LAST = 8
};
#define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x))
typedef struct
{
EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST];
} EggModmap;
const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap);
static inline gboolean
is_alt (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'a' || string[1] == 'A') &&
(string[2] == 'l' || string[2] == 'L') &&
(string[3] == 't' || string[3] == 'T') &&
(string[4] == '>'));
}
static inline gboolean
is_ctl (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 't' || string[2] == 'T') &&
(string[3] == 'l' || string[3] == 'L') &&
(string[4] == '>'));
}
static inline gboolean
is_modx (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'm' || string[1] == 'M') &&
(string[2] == 'o' || string[2] == 'O') &&
(string[3] == 'd' || string[3] == 'D') &&
(string[4] >= '1' && string[4] <= '5') &&
(string[5] == '>'));
}
static inline gboolean
is_ctrl (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 't' || string[2] == 'T') &&
(string[3] == 'r' || string[3] == 'R') &&
(string[4] == 'l' || string[4] == 'L') &&
(string[5] == '>'));
}
static inline gboolean
is_shft (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'h' || string[2] == 'H') &&
(string[3] == 'f' || string[3] == 'F') &&
(string[4] == 't' || string[4] == 'T') &&
(string[5] == '>'));
}
static inline gboolean
is_shift (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'h' || string[2] == 'H') &&
(string[3] == 'i' || string[3] == 'I') &&
(string[4] == 'f' || string[4] == 'F') &&
(string[5] == 't' || string[5] == 'T') &&
(string[6] == '>'));
}
static inline gboolean
is_control (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'c' || string[1] == 'C') &&
(string[2] == 'o' || string[2] == 'O') &&
(string[3] == 'n' || string[3] == 'N') &&
(string[4] == 't' || string[4] == 'T') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == 'o' || string[6] == 'O') &&
(string[7] == 'l' || string[7] == 'L') &&
(string[8] == '>'));
}
static inline gboolean
is_release (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'r' || string[1] == 'R') &&
(string[2] == 'e' || string[2] == 'E') &&
(string[3] == 'l' || string[3] == 'L') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'a' || string[5] == 'A') &&
(string[6] == 's' || string[6] == 'S') &&
(string[7] == 'e' || string[7] == 'E') &&
(string[8] == '>'));
}
static inline gboolean
is_meta (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'm' || string[1] == 'M') &&
(string[2] == 'e' || string[2] == 'E') &&
(string[3] == 't' || string[3] == 'T') &&
(string[4] == 'a' || string[4] == 'A') &&
(string[5] == '>'));
}
static inline gboolean
is_super (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 's' || string[1] == 'S') &&
(string[2] == 'u' || string[2] == 'U') &&
(string[3] == 'p' || string[3] == 'P') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == '>'));
}
static inline gboolean
is_hyper (const gchar *string)
{
return ((string[0] == '<') &&
(string[1] == 'h' || string[1] == 'H') &&
(string[2] == 'y' || string[2] == 'Y') &&
(string[3] == 'p' || string[3] == 'P') &&
(string[4] == 'e' || string[4] == 'E') &&
(string[5] == 'r' || string[5] == 'R') &&
(string[6] == '>'));
}
static inline gboolean
is_keycode (const gchar *string)
{
return ((string[0] == '0') &&
(string[1] == 'x'));
}
/**
* egg_accelerator_parse_virtual:
* @accelerator: string representing an accelerator
* @accelerator_key: return location for accelerator keyval
* @accelerator_mods: return location for accelerator modifier mask
*
* Parses a string representing a virtual accelerator. The format
* looks like "&lt;Control&gt;a" or "&lt;Shift&gt;&lt;Alt&gt;F1" or
* "&lt;Release&gt;z" (the last one is for key release). The parser
* is fairly liberal and allows lower or upper case, and also
* abbreviations such as "&lt;Ctl&gt;" and "&lt;Ctrl&gt;".
*
* If the parse fails, @accelerator_key and @accelerator_mods will
* be set to 0 (zero) and %FALSE will be returned. If the string contains
* only modifiers, @accelerator_key will be set to 0 but %TRUE will be
* returned.
*
* The virtual vs. concrete accelerator distinction is a relic of
* how the X Window System works; there are modifiers Mod2-Mod5 that
* can represent various keyboard keys (numlock, meta, hyper, etc.),
* the virtual modifier represents the keyboard key, the concrete
* modifier the actual Mod2-Mod5 bits in the key press event.
*
* Returns: %TRUE on success.
*/
gboolean
egg_accelerator_parse_virtual (const gchar *accelerator,
guint *accelerator_key,
guint *keycode,
EggVirtualModifierType *accelerator_mods)
{
guint keyval;
GdkModifierType mods;
gint len;
gboolean bad_keyval;
if (accelerator_key)
*accelerator_key = 0;
if (accelerator_mods)
*accelerator_mods = 0;
if (keycode)
*keycode = 0;
g_return_val_if_fail (accelerator != NULL, FALSE);
bad_keyval = FALSE;
keyval = 0;
mods = 0;
len = strlen (accelerator);
while (len)
{
if (*accelerator == '<')
{
if (len >= 9 && is_release (accelerator))
{
accelerator += 9;
len -= 9;
mods |= EGG_VIRTUAL_RELEASE_MASK;
}
else if (len >= 9 && is_control (accelerator))
{
accelerator += 9;
len -= 9;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 7 && is_shift (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_SHIFT_MASK;
}
else if (len >= 6 && is_shft (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_SHIFT_MASK;
}
else if (len >= 6 && is_ctrl (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 6 && is_modx (accelerator))
{
static const guint mod_vals[] = {
EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK,
EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK
};
len -= 6;
accelerator += 4;
mods |= mod_vals[*accelerator - '1'];
accelerator += 2;
}
else if (len >= 5 && is_ctl (accelerator))
{
accelerator += 5;
len -= 5;
mods |= EGG_VIRTUAL_CONTROL_MASK;
}
else if (len >= 5 && is_alt (accelerator))
{
accelerator += 5;
len -= 5;
mods |= EGG_VIRTUAL_ALT_MASK;
}
else if (len >= 6 && is_meta (accelerator))
{
accelerator += 6;
len -= 6;
mods |= EGG_VIRTUAL_META_MASK;
}
else if (len >= 7 && is_hyper (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_HYPER_MASK;
}
else if (len >= 7 && is_super (accelerator))
{
accelerator += 7;
len -= 7;
mods |= EGG_VIRTUAL_SUPER_MASK;
}
else
{
gchar last_ch;
last_ch = *accelerator;
while (last_ch && last_ch != '>')
{
last_ch = *accelerator;
accelerator += 1;
len -= 1;
}
}
}
else
{
keyval = gdk_keyval_from_name (accelerator);
if (keyval == 0)
{
/* If keyval is 0, than maybe it's a keycode. Check for 0x## */
if (len >= 4 && is_keycode (accelerator))
{
char keystring[5];
gchar *endptr;
gint tmp_keycode;
memcpy (keystring, accelerator, 4);
keystring [4] = '\000';
tmp_keycode = strtol (keystring, &endptr, 16);
if (endptr == NULL || *endptr != '\000')
{
bad_keyval = TRUE;
}
else if (keycode != NULL)
{
*keycode = tmp_keycode;
/* 0x00 is an invalid keycode too. */
if (*keycode == 0)
bad_keyval = TRUE;
}
}
} else if (keycode != NULL)
*keycode = XKeysymToKeycode (GDK_DISPLAY(), keyval);
accelerator += len;
len -= len;
}
}
if (accelerator_key)
*accelerator_key = gdk_keyval_to_lower (keyval);
if (accelerator_mods)
*accelerator_mods = mods;
return !bad_keyval;
}
/**
* egg_virtual_accelerator_name:
* @accelerator_key: accelerator keyval
* @accelerator_mods: accelerator modifier mask
* @returns: a newly-allocated accelerator name
*
* Converts an accelerator keyval and modifier mask
* into a string parseable by egg_accelerator_parse_virtual().
* For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK,
* this function returns "&lt;Control&gt;q".
*
* The caller of this function must free the returned string.
*/
gchar*
egg_virtual_accelerator_name (guint accelerator_key,
guint keycode,
EggVirtualModifierType accelerator_mods)
{
static const gchar text_release[] = "<Release>";
static const gchar text_shift[] = "<Shift>";
static const gchar text_control[] = "<Control>";
static const gchar text_mod1[] = "<Alt>";
static const gchar text_mod2[] = "<Mod2>";
static const gchar text_mod3[] = "<Mod3>";
static const gchar text_mod4[] = "<Mod4>";
static const gchar text_mod5[] = "<Mod5>";
static const gchar text_meta[] = "<Meta>";
static const gchar text_super[] = "<Super>";
static const gchar text_hyper[] = "<Hyper>";
guint l;
gchar *keyval_name;
gchar *accelerator;
accelerator_mods &= EGG_VIRTUAL_MODIFIER_MASK;
if (!accelerator_key)
{
keyval_name = g_strdup_printf ("0x%02x", keycode);
}
else
{
keyval_name = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key));
if (!keyval_name)
keyval_name = "";
}
l = 0;
if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
l += sizeof (text_release) - 1;
if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
l += sizeof (text_shift) - 1;
if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
l += sizeof (text_control) - 1;
if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
l += sizeof (text_mod1) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
l += sizeof (text_mod2) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
l += sizeof (text_mod3) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
l += sizeof (text_mod4) - 1;
if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
l += sizeof (text_mod5) - 1;
if (accelerator_mods & EGG_VIRTUAL_META_MASK)
l += sizeof (text_meta) - 1;
if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
l += sizeof (text_hyper) - 1;
if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
l += sizeof (text_super) - 1;
l += strlen (keyval_name);
accelerator = g_new (gchar, l + 1);
l = 0;
accelerator[l] = 0;
if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
{
strcpy (accelerator + l, text_release);
l += sizeof (text_release) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
{
strcpy (accelerator + l, text_shift);
l += sizeof (text_shift) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
{
strcpy (accelerator + l, text_control);
l += sizeof (text_control) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
{
strcpy (accelerator + l, text_mod1);
l += sizeof (text_mod1) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
{
strcpy (accelerator + l, text_mod2);
l += sizeof (text_mod2) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
{
strcpy (accelerator + l, text_mod3);
l += sizeof (text_mod3) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
{
strcpy (accelerator + l, text_mod4);
l += sizeof (text_mod4) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
{
strcpy (accelerator + l, text_mod5);
l += sizeof (text_mod5) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_META_MASK)
{
strcpy (accelerator + l, text_meta);
l += sizeof (text_meta) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
{
strcpy (accelerator + l, text_hyper);
l += sizeof (text_hyper) - 1;
}
if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
{
strcpy (accelerator + l, text_super);
l += sizeof (text_super) - 1;
}
strcpy (accelerator + l, keyval_name);
return accelerator;
}
void
egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
EggVirtualModifierType virtual_mods,
GdkModifierType *concrete_mods)
{
GdkModifierType concrete;
int i;
const EggModmap *modmap;
g_return_if_fail (GDK_IS_KEYMAP (keymap));
g_return_if_fail (concrete_mods != NULL);
modmap = egg_keymap_get_modmap (keymap);
/* Not so sure about this algorithm. */
concrete = 0;
i = 0;
while (i < EGG_MODMAP_ENTRY_LAST)
{
if (modmap->mapping[i] & virtual_mods)
concrete |= (1 << i);
++i;
}
*concrete_mods = concrete;
}
void
egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
GdkModifierType concrete_mods,
EggVirtualModifierType *virtual_mods)
{
GdkModifierType virtual;
int i;
const EggModmap *modmap;
g_return_if_fail (GDK_IS_KEYMAP (keymap));
g_return_if_fail (virtual_mods != NULL);
modmap = egg_keymap_get_modmap (keymap);
/* Not so sure about this algorithm. */
virtual = 0;
i = 0;
while (i < EGG_MODMAP_ENTRY_LAST)
{
if ((1 << i) & concrete_mods)
{
EggVirtualModifierType cleaned;
cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK |
EGG_VIRTUAL_MOD3_MASK |
EGG_VIRTUAL_MOD4_MASK |
EGG_VIRTUAL_MOD5_MASK);
if (cleaned != 0)
{
virtual |= cleaned;
}
else
{
/* Rather than dropping mod2->mod5 if not bound,
* go ahead and use the concrete names
*/
virtual |= modmap->mapping[i];
}
}
++i;
}
*virtual_mods = virtual;
}
static void
reload_modmap (GdkKeymap *keymap,
EggModmap *modmap)
{
XModifierKeymap *xmodmap;
int map_size;
int i;
/* FIXME multihead */
xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ());
memset (modmap->mapping, 0, sizeof (modmap->mapping));
/* there are 8 modifiers, and the first 3 are shift, shift lock,
* and control
*/
map_size = 8 * xmodmap->max_keypermod;
i = 3 * xmodmap->max_keypermod;
while (i < map_size)
{
/* get the key code at this point in the map,
* see if its keysym is one we're interested in
*/
int keycode = xmodmap->modifiermap[i];
GdkKeymapKey *keys;
guint *keyvals;
int n_entries;
int j;
EggVirtualModifierType mask;
keys = NULL;
keyvals = NULL;
n_entries = 0;
gdk_keymap_get_entries_for_keycode (keymap,
keycode,
&keys, &keyvals, &n_entries);
mask = 0;
j = 0;
while (j < n_entries)
{
if (keyvals[j] == GDK_Num_Lock)
mask |= EGG_VIRTUAL_NUM_LOCK_MASK;
else if (keyvals[j] == GDK_Scroll_Lock)
mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK;
else if (keyvals[j] == GDK_Meta_L ||
keyvals[j] == GDK_Meta_R)
mask |= EGG_VIRTUAL_META_MASK;
else if (keyvals[j] == GDK_Hyper_L ||
keyvals[j] == GDK_Hyper_R)
mask |= EGG_VIRTUAL_HYPER_MASK;
else if (keyvals[j] == GDK_Super_L ||
keyvals[j] == GDK_Super_R)
mask |= EGG_VIRTUAL_SUPER_MASK;
else if (keyvals[j] == GDK_Mode_switch)
mask |= EGG_VIRTUAL_MODE_SWITCH_MASK;
++j;
}
/* Mod1Mask is 1 << 3 for example, i.e. the
* fourth modifier, i / keyspermod is the modifier
* index
*/
modmap->mapping[i/xmodmap->max_keypermod] |= mask;
g_free (keyvals);
g_free (keys);
++i;
}
/* Add in the not-really-virtual fixed entries */
modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_LOCK] |= EGG_VIRTUAL_LOCK_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD1] |= EGG_VIRTUAL_ALT_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD2] |= EGG_VIRTUAL_MOD2_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK;
modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK;
XFreeModifiermap (xmodmap);
}
const EggModmap*
egg_keymap_get_modmap (GdkKeymap *keymap)
{
EggModmap *modmap;
/* This is all a hack, much simpler when we can just
* modify GDK directly.
*/
modmap = g_object_get_data (G_OBJECT (keymap),
"egg-modmap");
if (modmap == NULL)
{
modmap = g_new0 (EggModmap, 1);
/* FIXME modify keymap change events with an event filter
* and force a reload if we get one
*/
reload_modmap (keymap, modmap);
g_object_set_data_full (G_OBJECT (keymap),
"egg-modmap",
modmap,
g_free);
}
g_assert (modmap != NULL);
return modmap;
}
+89
View File
@@ -0,0 +1,89 @@
/* eggaccelerators.h
* Copyright (C) 2002 Red Hat, Inc.
* Developed by Havoc Pennington
*
* This 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.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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 __EGG_ACCELERATORS_H__
#define __EGG_ACCELERATORS_H__
#include <gtk/gtkaccelgroup.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
/* Where a value is also in GdkModifierType we coincide,
* otherwise we don't overlap.
*/
typedef enum
{
EGG_VIRTUAL_SHIFT_MASK = 1 << 0,
EGG_VIRTUAL_LOCK_MASK = 1 << 1,
EGG_VIRTUAL_CONTROL_MASK = 1 << 2,
EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */
EGG_VIRTUAL_MOD2_MASK = 1 << 4,
EGG_VIRTUAL_MOD3_MASK = 1 << 5,
EGG_VIRTUAL_MOD4_MASK = 1 << 6,
EGG_VIRTUAL_MOD5_MASK = 1 << 7,
#if 0
GDK_BUTTON1_MASK = 1 << 8,
GDK_BUTTON2_MASK = 1 << 9,
GDK_BUTTON3_MASK = 1 << 10,
GDK_BUTTON4_MASK = 1 << 11,
GDK_BUTTON5_MASK = 1 << 12,
/* 13, 14 are used by Xkb for the keyboard group */
#endif
EGG_VIRTUAL_META_MASK = 1 << 24,
EGG_VIRTUAL_SUPER_MASK = 1 << 25,
EGG_VIRTUAL_HYPER_MASK = 1 << 26,
EGG_VIRTUAL_MODE_SWITCH_MASK = 1 << 27,
EGG_VIRTUAL_NUM_LOCK_MASK = 1 << 28,
EGG_VIRTUAL_SCROLL_LOCK_MASK = 1 << 29,
/* Also in GdkModifierType */
EGG_VIRTUAL_RELEASE_MASK = 1 << 30,
/* 28-31 24-27 20-23 16-19 12-15 8-11 4-7 0-3
* 7 f 0 0 0 0 f f
*/
EGG_VIRTUAL_MODIFIER_MASK = 0x7f0000ff
} EggVirtualModifierType;
gboolean egg_accelerator_parse_virtual (const gchar *accelerator,
guint *accelerator_key,
guint *keycode,
EggVirtualModifierType *accelerator_mods);
void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
EggVirtualModifierType virtual_mods,
GdkModifierType *concrete_mods);
void egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
GdkModifierType concrete_mods,
EggVirtualModifierType *virtual_mods);
gchar* egg_virtual_accelerator_name (guint accelerator_key,
guint keycode,
EggVirtualModifierType accelerator_mods);
G_END_DECLS
#endif /* __EGG_ACCELERATORS_H__ */
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
/* eggdesktopfile.h - Freedesktop.Org Desktop Files
* Copyright (C) 2007 Novell, 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; see the file COPYING.LIB. If not,
* write to the Free Software Foundation, Inc., 59 Temple Place -
* Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __EGG_DESKTOP_FILE_H__
#define __EGG_DESKTOP_FILE_H__
#include <glib.h>
G_BEGIN_DECLS
typedef struct EggDesktopFile EggDesktopFile;
typedef enum {
EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED,
EGG_DESKTOP_FILE_TYPE_APPLICATION,
EGG_DESKTOP_FILE_TYPE_LINK,
EGG_DESKTOP_FILE_TYPE_DIRECTORY,
} EggDesktopFileType;
EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path,
GError **error);
EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
GError **error);
EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file,
const char *source,
GError **error);
void egg_desktop_file_free (EggDesktopFile *desktop_file);
const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file) G_GNUC_PURE;
EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) G_GNUC_PURE;
const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file) G_GNUC_PURE;
const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file) G_GNUC_PURE;
gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
const char *desktop_environment);
gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file);
gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file);
gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file);
char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
GSList *documents,
GError **error);
gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file,
GSList *documents,
GError **error,
...) G_GNUC_NULL_TERMINATED;
typedef enum {
EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1,
EGG_DESKTOP_FILE_LAUNCH_PUTENV,
EGG_DESKTOP_FILE_LAUNCH_SCREEN,
EGG_DESKTOP_FILE_LAUNCH_WORKSPACE,
EGG_DESKTOP_FILE_LAUNCH_DIRECTORY,
EGG_DESKTOP_FILE_LAUNCH_TIME,
EGG_DESKTOP_FILE_LAUNCH_FLAGS,
EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC,
EGG_DESKTOP_FILE_LAUNCH_RETURN_PID,
EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE,
EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE,
EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE,
EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID
} EggDesktopFileLaunchOption;
/* Standard Keys */
#define EGG_DESKTOP_FILE_GROUP "Desktop Entry"
#define EGG_DESKTOP_FILE_KEY_TYPE "Type"
#define EGG_DESKTOP_FILE_KEY_VERSION "Version"
#define EGG_DESKTOP_FILE_KEY_NAME "Name"
#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName"
#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay"
#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment"
#define EGG_DESKTOP_FILE_KEY_ICON "Icon"
#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden"
#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn"
#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn"
#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec"
#define EGG_DESKTOP_FILE_KEY_EXEC "Exec"
#define EGG_DESKTOP_FILE_KEY_PATH "Path"
#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal"
#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType"
#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories"
#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify"
#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass"
#define EGG_DESKTOP_FILE_KEY_URL "URL"
/* Accessors */
gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file,
const char *key,
GError **error);
char *egg_desktop_file_get_string (EggDesktopFile *desktop_file,
const char *key,
GError **error) G_GNUC_MALLOC;
char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
const char *key,
const char *locale,
GError **error) G_GNUC_MALLOC;
gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
const char *key,
GError **error);
double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
const char *key,
GError **error);
char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
const char *key,
gsize *length,
GError **error) G_GNUC_MALLOC;
char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
const char *key,
const char *locale,
gsize *length,
GError **error) G_GNUC_MALLOC;
/* Errors */
#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark()
GQuark egg_desktop_file_error_quark (void);
typedef enum {
EGG_DESKTOP_FILE_ERROR_INVALID,
EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
} EggDesktopFileError;
/* Global application desktop file */
void egg_set_desktop_file (const char *desktop_file_path);
EggDesktopFile *egg_get_desktop_file (void);
G_END_DECLS
#endif /* __EGG_DESKTOP_FILE_H__ */
+56
View File
@@ -0,0 +1,56 @@
/* eggsmclient-private.h
* Copyright (C) 2007 Novell, 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 __EGG_SM_CLIENT_PRIVATE_H__
#define __EGG_SM_CLIENT_PRIVATE_H__
#include <gdkconfig.h>
#include "eggsmclient.h"
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);
void egg_sm_client_quit (EggSMClient *client);
#if defined (GDK_WINDOWING_X11)
# ifdef EGG_SM_CLIENT_BACKEND_XSMP
#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
GType egg_sm_client_xsmp_get_type (void);
EggSMClient *egg_sm_client_xsmp_new (void);
# endif
# ifdef EGG_SM_CLIENT_BACKEND_DBUS
GType egg_sm_client_dbus_get_type (void);
EggSMClient *egg_sm_client_dbus_new (void);
# endif
#elif defined (GDK_WINDOWING_WIN32)
GType egg_sm_client_win32_get_type (void);
EggSMClient *egg_sm_client_win32_new (void);
#elif defined (GDK_WINDOWING_QUARTZ)
GType egg_sm_client_osx_get_type (void);
EggSMClient *egg_sm_client_osx_new (void);
#endif
G_END_DECLS
#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */
File diff suppressed because it is too large Load Diff
+392
View File
@@ -0,0 +1,392 @@
/*
* Copyright (C) 2007 Novell, Inc.
*
* This 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.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <glib/gi18n.h>
#include "eggsmclient.h"
#include "eggsmclient-private.h"
static void egg_sm_client_debug_handler (const char *log_domain,
GLogLevelFlags log_level,
const char *message,
gpointer user_data);
enum {
SAVE_STATE,
QUIT_REQUESTED,
QUIT_CANCELLED,
QUIT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct _EggSMClientPrivate {
GKeyFile *state_file;
};
#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
static void
egg_sm_client_init (EggSMClient *client)
{
}
static void
egg_sm_client_class_init (EggSMClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
/**
* EggSMClient::save_state:
* @client: the client
* @state_file: a #GKeyFile to save state information into
*
* Emitted when the session manager has requested that the
* application save information about its current state. The
* application should save its state into @state_file, and then the
* session manager may then restart the application in a future
* session and tell it to initialize itself from that state.
*
* You should not save any data into @state_file's "start group"
* (ie, the %NULL group). Instead, applications should save their
* data into groups with names that start with the application name,
* and libraries that connect to this signal should save their data
* into groups with names that start with the library name.
*
* Alternatively, rather than (or in addition to) using @state_file,
* the application can save its state by calling
* egg_sm_client_set_restart_command() during the processing of this
* signal (eg, to include a list of files to open).
**/
signals[SAVE_STATE] =
g_signal_new ("save_state",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EggSMClientClass, save_state),
NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE,
1, G_TYPE_POINTER);
/**
* EggSMClient::quit_requested:
* @client: the client
*
* Emitted when the session manager requests that the application
* exit (generally because the user is logging out). The application
* should decide whether or not it is willing to quit (perhaps after
* asking the user what to do with documents that have unsaved
* changes) and then call egg_sm_client_will_quit(), passing %TRUE
* or %FALSE to give its answer to the session manager. (It does not
* need to give an answer before returning from the signal handler;
* it can interact with the user asynchronously and then give its
* answer later on.) If the application does not connect to this
* signal, then #EggSMClient will automatically return %TRUE on its
* behalf.
*
* The application should not save its session state as part of
* handling this signal; if the user has requested that the session
* be saved when logging out, then ::save_state will be emitted
* separately.
*
* If the application agrees to quit, it should then wait for either
* the ::quit_cancelled or ::quit signals to be emitted.
**/
signals[QUIT_REQUESTED] =
g_signal_new ("quit_requested",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EggSMClient::quit_cancelled:
* @client: the client
*
* Emitted when the session manager decides to cancel a logout after
* the application has already agreed to quit. After receiving this
* signal, the application can go back to what it was doing before
* receiving the ::quit_requested signal.
**/
signals[QUIT_CANCELLED] =
g_signal_new ("quit_cancelled",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EggSMClient::quit:
* @client: the client
*
* Emitted when the session manager wants the application to quit
* (generally because the user is logging out). The application
* should exit as soon as possible after receiving this signal; if
* it does not, the session manager may choose to forcibly kill it.
*
* Normally a GUI application would only be sent a ::quit if it
* agreed to quit in response to a ::quit_requested signal. However,
* this is not guaranteed; in some situations the session manager
* may decide to end the session without giving applications a
* chance to object.
**/
signals[QUIT] =
g_signal_new ("quit",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EggSMClientClass, quit),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static gboolean sm_client_disable = FALSE;
static char *sm_client_state_file = NULL;
static char *sm_client_id = NULL;
static GOptionEntry entries[] = {
{ "sm-client-disable", 0, 0,
G_OPTION_ARG_NONE, &sm_client_disable,
N_("Disable connection to session manager"), NULL },
{ "sm-client-state-file", 0, 0,
G_OPTION_ARG_STRING, &sm_client_state_file,
N_("Specify file containing saved configuration"), N_("FILE") },
{ "sm-client-id", 0, 0,
G_OPTION_ARG_STRING, &sm_client_id,
N_("Specify session management ID"), N_("ID") },
{ NULL }
};
/**
* egg_sm_client_is_resumed:
* @client: the client
*
* Checks whether or not the current session has been resumed from
* a previous saved session. If so, the application should call
* egg_sm_client_get_state_file() and restore its state from the
* returned #GKeyFile.
*
* Return value: %TRUE if the session has been resumed
**/
gboolean
egg_sm_client_is_resumed (EggSMClient *client)
{
return sm_client_state_file != NULL;
}
/**
* egg_sm_client_get_state_file:
* @client: the client
*
* If the application was resumed by the session manager, this will
* return the #GKeyFile containing its state from the previous
* session.
*
* Note that other libraries and #EggSMClient itself may also store
* state in the key file, so if you call egg_sm_client_get_groups(),
* on it, the return value will likely include groups that you did not
* put there yourself. (It is also not guaranteed that the first
* group created by the application will still be the "start group"
* when it is resumed.)
*
* Return value: the #GKeyFile containing the application's earlier
* state, or %NULL on error. You should not free this key file; it
* is owned by @client.
**/
GKeyFile *
egg_sm_client_get_state_file (EggSMClient *client)
{
EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
char *state_file_path;
GError *err = NULL;
if (!sm_client_state_file)
return NULL;
if (priv->state_file)
return priv->state_file;
if (!strncmp (sm_client_state_file, "file://", 7))
state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
else
state_file_path = g_strdup (sm_client_state_file);
priv->state_file = g_key_file_new ();
if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
{
g_warning ("Could not load SM state file '%s': %s",
sm_client_state_file, err->message);
g_clear_error (&err);
g_key_file_free (priv->state_file);
priv->state_file = NULL;
}
g_free (state_file_path);
return priv->state_file;
}
/**
* egg_sm_client_set_restart_command:
* @client: the client
* @argc: the length of @argv
* @argv: argument vector
*
* Sets the command used to restart @client if it does not have a
* .desktop file that can be used to find its restart command.
*
* This can also be used when handling the ::save_state signal, to
* save the current state via an updated command line. (Eg, providing
* a list of filenames to open when the application is resumed.)
**/
void
egg_sm_client_set_restart_command (EggSMClient *client,
int argc,
const char **argv)
{
g_return_if_fail (EGG_IS_SM_CLIENT (client));
if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
}
/**
* egg_sm_client_will_quit:
* @client: the client
* @will_quit: whether or not the application is willing to quit
*
* This MUST be called in response to the ::quit_requested signal, to
* indicate whether or not the application is willing to quit. The
* application may call it either directly from the signal handler, or
* at some later point (eg, after asynchronously interacting with the
* user).
*
* If the application does not connect to ::quit_requested,
* #EggSMClient will call this method on its behalf (passing %TRUE
* for @will_quit).
*
* After calling this method, the application should wait to receive
* either ::quit_cancelled or ::quit.
**/
void
egg_sm_client_will_quit (EggSMClient *client,
gboolean will_quit)
{
g_return_if_fail (EGG_IS_SM_CLIENT (client));
if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
}
/* Signal-emitting callbacks from platform-specific code */
GKeyFile *
egg_sm_client_save_state (EggSMClient *client)
{
GKeyFile *state_file;
char *group;
state_file = g_key_file_new ();
g_debug ("Emitting save_state");
g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
g_debug ("Done emitting save_state");
group = g_key_file_get_start_group (state_file);
if (group)
{
g_free (group);
return state_file;
}
else
{
g_key_file_free (state_file);
return NULL;
}
}
void
egg_sm_client_quit_requested (EggSMClient *client)
{
if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
{
g_debug ("Not emitting quit_requested because no one is listening");
egg_sm_client_will_quit (client, TRUE);
return;
}
g_debug ("Emitting quit_requested");
g_signal_emit (client, signals[QUIT_REQUESTED], 0);
g_debug ("Done emitting quit_requested");
}
void
egg_sm_client_quit_cancelled (EggSMClient *client)
{
g_debug ("Emitting quit_cancelled");
g_signal_emit (client, signals[QUIT_CANCELLED], 0);
g_debug ("Done emitting quit_cancelled");
}
void
egg_sm_client_quit (EggSMClient *client)
{
g_debug ("Emitting quit");
g_signal_emit (client, signals[QUIT], 0);
g_debug ("Done emitting quit");
/* FIXME: should we just call gtk_main_quit() here? */
}
void
egg_sm_client_startup (EggSMClient *client)
{
if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
}
static void
egg_sm_client_debug_handler (const char *log_domain,
GLogLevelFlags log_level,
const char *message,
gpointer user_data)
{
static int debug = -1;
if (debug < 0)
debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
if (debug)
g_log_default_handler (log_domain, log_level, message, NULL);
}
+112
View File
@@ -0,0 +1,112 @@
/* eggsmclient.h
* Copyright (C) 2007 Novell, 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 __EGG_SM_CLIENT_H__
#define __EGG_SM_CLIENT_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ())
#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient))
#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass))
#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT))
#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT))
#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass))
typedef struct _EggSMClient EggSMClient;
typedef struct _EggSMClientClass EggSMClientClass;
typedef struct _EggSMClientPrivate EggSMClientPrivate;
typedef enum {
EGG_SM_CLIENT_END_SESSION_DEFAULT,
EGG_SM_CLIENT_LOGOUT,
EGG_SM_CLIENT_REBOOT,
EGG_SM_CLIENT_SHUTDOWN
} EggSMClientEndStyle;
typedef enum {
EGG_SM_CLIENT_MODE_DISABLED,
EGG_SM_CLIENT_MODE_NO_RESTART,
EGG_SM_CLIENT_MODE_NORMAL
} EggSMClientMode;
struct _EggSMClient
{
GObject parent;
};
struct _EggSMClientClass
{
GObjectClass parent_class;
/* signals */
void (*save_state) (EggSMClient *client,
GKeyFile *state_file);
void (*quit_requested) (EggSMClient *client);
void (*quit_cancelled) (EggSMClient *client);
void (*quit) (EggSMClient *client);
/* virtual methods */
void (*startup) (EggSMClient *client,
const char *client_id);
void (*set_restart_command) (EggSMClient *client,
int argc,
const char **argv);
void (*will_quit) (EggSMClient *client,
gboolean will_quit);
gboolean (*end_session) (EggSMClient *client,
EggSMClientEndStyle style,
gboolean request_confirmation);
/* Padding for future expansion */
void (*_egg_reserved1) (void);
void (*_egg_reserved2) (void);
void (*_egg_reserved3) (void);
void (*_egg_reserved4) (void);
};
GType egg_sm_client_get_type (void) G_GNUC_CONST;
/* Resuming a saved session */
gboolean egg_sm_client_is_resumed (EggSMClient *client);
GKeyFile *egg_sm_client_get_state_file (EggSMClient *client);
/* Alternate means of saving state */
void egg_sm_client_set_restart_command (EggSMClient *client,
int argc,
const char **argv);
/* Handling "quit_requested" signal */
void egg_sm_client_will_quit (EggSMClient *client,
gboolean will_quit);
void egg_sm_client_startup (EggSMClient *client);
/* Initiate a logout/reboot/shutdown */
gboolean egg_sm_client_end_session (EggSMClientEndStyle style,
gboolean request_confirmation);
G_END_DECLS
#endif /* __EGG_SM_CLIENT_H__ */
+58
View File
@@ -0,0 +1,58 @@
"""Calculates file-paths for the Sugar working environment"""
# Copyright (C) 2006-2007 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.
"""
STABLE.
"""
import os
def is_emulator():
return os.environ.get('SUGAR_EMULATOR', 'no') == 'yes'
def get_profile_path(path=None):
profile_id = os.environ.get('SUGAR_PROFILE', 'default')
base = os.path.join(os.path.expanduser('~/.sugar'), profile_id)
if not os.path.isdir(base):
try:
os.makedirs(base, 0770)
except OSError:
print 'Could not create user directory.'
if path != None:
return os.path.join(base, path)
else:
return base
def get_logs_path(path=None):
base = os.environ.get('SUGAR_LOGS_DIR', get_profile_path('logs'))
if path != None:
return os.path.join(base, path)
else:
return base
def get_user_activities_path():
return os.path.expanduser('~/Activities')
def get_user_library_path():
return os.path.expanduser('~/Library')
+30
View File
@@ -0,0 +1,30 @@
sugardir = $(pythondir)/sugar3/graphics
sugar_PYTHON = \
alert.py \
animator.py \
canvastextview.py \
colorbutton.py \
combobox.py \
entry.py \
iconentry.py \
icon.py \
__init__.py \
menuitem.py \
notebook.py \
objectchooser.py \
palettegroup.py \
palette.py \
palettewindow.py \
panel.py \
radiopalette.py \
radiotoolbutton.py \
roundbox.py \
style.py \
toggletoolbutton.py \
toolbarbox.py \
toolbox.py \
toolbutton.py \
toolcombobox.py \
tray.py \
window.py \
xocolor.py
+18
View File
@@ -0,0 +1,18 @@
"""Graphics/controls for use in Sugar"""
# Copyright (C) 2006-2007, 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.
+489
View File
@@ -0,0 +1,489 @@
"""
Alerts appear at the top of the body of your activity.
At a high level, Alert and its different variations (TimeoutAlert,
ConfirmationAlert, etc.) have a title, an alert message and then several
buttons that the user can click. The Alert class will pass "response" events
to your activity when any of these buttons are clicked, along with a
response_id to help you identify what button was clicked.
Examples
--------
create a simple alert message.
.. code-block:: python
from sugar.graphics.alert import Alert
...
# Create a new simple alert
alert = Alert()
# Populate the title and text body of the alert.
alert.props.title=_('Title of Alert Goes Here')
alert.props.msg = _('Text message of alert goes here')
# Call the add_alert() method (inherited via the sugar.graphics.Window
# superclass of Activity) to add this alert to the activity window.
self.add_alert(alert)
alert.show()
STABLE.
"""
# Copyright (C) 2007, One Laptop Per Child
# Copyright (C) 2010, Anish Mangal <anishmangal2002@gmail.com>
#
# 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 gettext
import gtk
import gobject
import pango
import math
from sugar.graphics import style
from sugar.graphics.icon import Icon
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
class Alert(gtk.EventBox):
"""
UI interface for Alerts
Alerts are used inside the activity window instead of being a
separate popup window. They do not hide canvas content. You can
use add_alert(widget) and remove_alert(widget) inside your activity
to add and remove the alert. The position of the alert is below the
toolbox or top in fullscreen mode.
Properties:
'title': the title of the alert,
'message': the message of the alert,
'icon': the icon that appears at the far left
See __gproperties__
"""
__gtype_name__ = 'SugarAlert'
__gsignals__ = {
'response': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object])),
}
__gproperties__ = {
'title': (str, None, None, None, gobject.PARAM_READWRITE),
'msg': (str, None, None, None, gobject.PARAM_READWRITE),
'icon': (object, None, None, gobject.PARAM_WRITABLE),
}
def __init__(self, **kwargs):
self._title = None
self._msg = None
self._icon = None
self._buttons = {}
self._hbox = gtk.HBox()
self._hbox.set_border_width(style.DEFAULT_SPACING)
self._hbox.set_spacing(style.DEFAULT_SPACING)
self._msg_box = gtk.VBox()
self._title_label = gtk.Label()
self._title_label.set_alignment(0, 0.5)
self._msg_box.pack_start(self._title_label, False)
self._msg_label = gtk.Label()
self._msg_label.set_alignment(0, 0.5)
self._msg_box.pack_start(self._msg_label, False)
self._hbox.pack_start(self._msg_box, False)
self._buttons_box = gtk.HButtonBox()
self._buttons_box.set_layout(gtk.BUTTONBOX_END)
self._buttons_box.set_spacing(style.DEFAULT_SPACING)
self._hbox.pack_start(self._buttons_box)
gobject.GObject.__init__(self, **kwargs)
self.set_visible_window(True)
self.add(self._hbox)
self._title_label.show()
self._msg_label.show()
self._buttons_box.show()
self._msg_box.show()
self._hbox.show()
self.show()
def do_set_property(self, pspec, value):
"""
Set alert property
Parameters
----------
pspec :
value :
Returns
-------
None
"""
if pspec.name == 'title':
if self._title != value:
self._title = value
self._title_label.set_markup('<b>' + self._title + '</b>')
elif pspec.name == 'msg':
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
self._hbox.pack_start(self._icon, False)
self._hbox.reorder_child(self._icon, 0)
def do_get_property(self, pspec):
"""
Get alert property
Parameters
----------
pspec :
property for which the value will be returned
Returns
-------
value of the property specified
"""
if pspec.name == 'title':
return self._title
elif pspec.name == 'msg':
return self._msg
def add_button(self, response_id, label, icon=None, position=-1):
"""
Add a button to the alert
Parameters
----------
response_id :
will be emitted with the response signal a response ID should one
of the pre-defined GTK Response Type Constants or a positive number
label :
that will occure right to the buttom
icon :
this can be a SugarIcon or a gtk.Image
postion :
the position of the button in the box (optional)
Returns
-------
button :gtk.Button
"""
button = gtk.Button()
self._buttons[response_id] = button
if icon is not None:
button.set_image(icon)
button.set_label(label)
self._buttons_box.pack_start(button)
button.show()
button.connect('clicked', self.__button_clicked_cb, response_id)
if position != -1:
self._buttons_box.reorder_child(button, position)
return button
def remove_button(self, response_id):
"""
Remove a button from the alert by the given response id
Parameters
----------
response_id :
Returns
-------
None
"""
self._buttons_box.remove(self._buttons[response_id])
def _response(self, response_id):
"""Emitting response when we have a result
A result can be that a user has clicked a button or
a timeout has occured, the id identifies the button
that has been clicked and -1 for a timeout
"""
self.emit('response', response_id)
def __button_clicked_cb(self, button, response_id):
self._response(response_id)
class ConfirmationAlert(Alert):
"""
This is a ready-made two button (Cancel,Ok) alert.
A confirmation alert is a nice shortcut from a standard Alert because it
comes with 'OK' and 'Cancel' buttons already built-in. When clicked, the
'OK' button will emit a response with a response_id of gtk.RESPONSE_OK,
while the 'Cancel' button will emit gtk.RESPONSE_CANCEL.
Examples
--------
.. code-block:: python
from sugar.graphics.alert import ConfirmationAlert
...
#### Method: _alert_confirmation, create a Confirmation alert (with ok
and cancel buttons standard)
# and add it to the UI.
def _alert_confirmation(self):
alert = ConfirmationAlert()
alert.props.title=_('Title of Alert Goes Here')
alert.props.msg = _('Text message of alert goes here')
alert.connect('response', self._alert_response_cb)
self.add_alert(alert)
#### Method: _alert_response_cb, called when an alert object throws a
response event.
def _alert_response_cb(self, alert, response_id):
#remove the alert from the screen, since either a response button
#was clicked or there was a timeout
self.remove_alert(alert)
#Do any work that is specific to the type of button clicked.
if response_id is gtk.RESPONSE_OK:
print 'Ok Button was clicked. Do any work upon ok here ...'
elif response_id is gtk.RESPONSE_CANCEL:
print 'Cancel Button was clicked.'
"""
def __init__(self, **kwargs):
Alert.__init__(self, **kwargs)
icon = Icon(icon_name='dialog-cancel')
self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
icon.show()
icon = Icon(icon_name='dialog-ok')
self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
icon.show()
class ErrorAlert(Alert):
"""
This is a ready-made one button (Ok) alert.
An error alert is a nice shortcut from a standard Alert because it
comes with the 'OK' button already built-in. When clicked, the
'OK' button will emit a response with a response_id of gtk.RESPONSE_OK.
Examples
--------
.. code-block:: python
from sugar.graphics.alert import ErrorAlert
...
#### Method: _alert_error, create a Error alert (with ok
button standard)
# and add it to the UI.
def _alert_error(self):
alert = ErrorAlert()
alert.props.title=_('Title of Alert Goes Here')
alert.props.msg = _('Text message of alert goes here')
alert.connect('response', self._alert_response_cb)
self.add_alert(alert)
#### Method: _alert_response_cb, called when an alert object throws a
response event.
def _alert_response_cb(self, alert, response_id):
#remove the alert from the screen, since either a response button
#was clicked or there was a timeout
self.remove_alert(alert)
#Do any work that is specific to the response_id.
if response_id is gtk.RESPONSE_OK:
print 'Ok Button was clicked. Do any work upon ok here ...'
"""
def __init__(self, **kwargs):
Alert.__init__(self, **kwargs)
icon = Icon(icon_name='dialog-ok')
self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
icon.show()
class _TimeoutIcon(gtk.Alignment):
__gtype_name__ = 'SugarTimeoutIcon'
def __init__(self):
gtk.Alignment.__init__(self, 0, 0, 1, 1)
self.set_app_paintable(True)
self._text = gtk.Label()
self._text.set_alignment(0.5, 0.5)
attrlist = pango.AttrList()
attrlist.insert(pango.AttrWeight(pango.WEIGHT_BOLD))
self._text.set_attributes(attrlist)
self.add(self._text)
self._text.show()
self.connect("expose_event", self.__expose_cb)
def __expose_cb(self, widget, event):
context = widget.window.cairo_create()
self._draw(context)
return False
def do_size_request(self, requisition):
requisition.height, requisition.width = \
gtk.icon_size_lookup(gtk.ICON_SIZE_BUTTON)
self._text.size_request()
def _draw(self, context):
rect = self.get_allocation()
x = rect.x + rect.width * 0.5
y = rect.y + rect.height * 0.5
radius = rect.width / 2
context.arc(x, y, radius, 0, 2 * math.pi)
widget_style = self.get_style()
context.set_source_color(widget_style.bg[self.get_state()])
context.fill_preserve()
def set_text(self, text):
self._text.set_text(str(text))
class TimeoutAlert(Alert):
"""
This is a ready-made two button (Cancel,Continue) alert
It times out with a positive response after the given amount of seconds.
Examples
--------
.. code-block:: python
from sugar.graphics.alert import TimeoutAlert
...
#### Method: _alert_timeout, create a Timeout alert (with ok and cancel
buttons standard)
# and add it to the UI.
def _alert_timeout(self):
#Notice that for a TimeoutAlert, you pass the number of seconds in
#which to timeout. By default, this is 5.
alert = TimeoutAlert(10)
alert.props.title=_('Title of Alert Goes Here')
alert.props.msg = _('Text message of timeout alert goes here')
alert.connect('response', self._alert_response_cb)
self.add_alert(alert)
#### Method: _alert_response_cb, called when an alert object throws a
response event.
def _alert_response_cb(self, alert, response_id):
#remove the alert from the screen, since either a response button
#was clicked or there was a timeout
self.remove_alert(alert)
#Do any work that is specific to the type of button clicked.
if response_id is gtk.RESPONSE_OK:
print 'Ok Button was clicked. Do any work upon ok here ...'
elif response_id is gtk.RESPONSE_CANCEL:
print 'Cancel Button was clicked.'
elif response_id == -1:
print 'Timout occurred'
"""
def __init__(self, timeout=5, **kwargs):
Alert.__init__(self, **kwargs)
self._timeout = timeout
icon = Icon(icon_name='dialog-cancel')
self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
icon.show()
self._timeout_text = _TimeoutIcon()
self._timeout_text.set_text(self._timeout)
self.add_button(gtk.RESPONSE_OK, _('Continue'), self._timeout_text)
self._timeout_text.show()
gobject.timeout_add_seconds(1, self.__timeout)
def __timeout(self):
self._timeout -= 1
self._timeout_text.set_text(self._timeout)
if self._timeout == 0:
self._response(gtk.RESPONSE_OK)
return False
return True
class NotifyAlert(Alert):
"""
Timeout alert with only an "OK" button - just for notifications
Examples
--------
.. code-block:: python
from sugar.graphics.alert import NotifyAlert
...
#### Method: _alert_notify, create a Notify alert (with only an 'OK'
button)
# and add it to the UI.
def _alert_notify(self):
#Notice that for a NotifyAlert, you pass the number of seconds in
#which to notify. By default, this is 5.
alert = NotifyAlert(10)
alert.props.title=_('Title of Alert Goes Here')
alert.props.msg = _('Text message of notify alert goes here')
alert.connect('response', self._alert_response_cb)
self.add_alert(alert)
"""
def __init__(self, timeout=5, **kwargs):
Alert.__init__(self, **kwargs)
self._timeout = timeout
self._timeout_text = _TimeoutIcon()
self._timeout_text.set_text(self._timeout)
self.add_button(gtk.RESPONSE_OK, _('Ok'), self._timeout_text)
self._timeout_text.show()
gobject.timeout_add(1000, self.__timeout)
def __timeout(self):
self._timeout -= 1
self._timeout_text.set_text(self._timeout)
if self._timeout == 0:
self._response(gtk.RESPONSE_OK)
return False
return True
+151
View File
@@ -0,0 +1,151 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import time
import gobject
EASE_OUT_EXPO = 0
EASE_IN_EXPO = 1
class Animator(gobject.GObject):
__gsignals__ = {
'completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self, duration, fps=20, easing=EASE_OUT_EXPO):
gobject.GObject.__init__(self)
self._animations = []
self._duration = duration
self._interval = 1.0 / fps
self._easing = easing
self._timeout_sid = 0
self._start_time = None
def add(self, animation):
"""
Parameter
---------
animation :
"""
self._animations.append(animation)
def remove_all(self):
"""
Parameters
----------
None :
Returns
-------
None :
"""
self.stop()
self._animations = []
def start(self):
"""
Parameters
----------
None :
Returns
-------
None
"""
if self._timeout_sid:
self.stop()
self._start_time = time.time()
self._timeout_sid = gobject.timeout_add(
int(self._interval * 1000), self._next_frame_cb)
def stop(self):
"""
Parameters
----------
None :
Returns
-------
None :
"""
if self._timeout_sid:
gobject.source_remove(self._timeout_sid)
self._timeout_sid = 0
self.emit('completed')
def _next_frame_cb(self):
current_time = min(self._duration, time.time() - self._start_time)
current_time = max(current_time, 0.0)
for animation in self._animations:
animation.do_frame(current_time, self._duration, self._easing)
if current_time == self._duration:
self.stop()
return False
else:
return True
class Animation(object):
def __init__(self, start, end):
self.start = start
self.end = end
def do_frame(self, t, duration, easing):
"""
Parameters
----------
t:
duration:
easing:
Returns
None:
"""
start = self.start
change = self.end - self.start
if t == duration:
# last frame
frame = self.end
else:
if easing == EASE_OUT_EXPO:
frame = change * (-pow(2, -10 * t / duration) + 1) + start
elif easing == EASE_IN_EXPO:
frame = change * pow(2, 10 * (t / duration - 1)) + start
self.next_frame(frame)
def next_frame(self, frame):
pass
+41
View File
@@ -0,0 +1,41 @@
# Copyright (C) 2008 One Laptop Per Child
#
# 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 gtk
import hippo
from sugar.graphics import style
class CanvasTextView(hippo.CanvasWidget):
def __init__(self, text, **kwargs):
hippo.CanvasWidget.__init__(self, **kwargs)
self.text_view_widget = gtk.TextView()
self.text_view_widget.props.buffer.props.text = text
self.text_view_widget.props.left_margin = style.DEFAULT_SPACING
self.text_view_widget.props.right_margin = style.DEFAULT_SPACING
self.text_view_widget.props.wrap_mode = gtk.WRAP_WORD
self.text_view_widget.show()
# TODO: These fields should expand vertically instead of scrolling
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_shadow_type(gtk.SHADOW_OUT)
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scrolled_window.add(self.text_view_widget)
self.props.widget = scrolled_window
+536
View File
@@ -0,0 +1,536 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2008, Benjamin Berg <benjamin@sipsolutions.net>
#
# 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 gettext
import gtk
import gobject
import struct
import logging
from sugar.graphics import style
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker, WidgetInvoker
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def get_svg_color_string(color):
return '#%.2X%.2X%.2X' % (color.red / 257, color.green / 257,
color.blue / 257)
class _ColorButton(gtk.Button):
"""This is a ColorButton for Sugar. It is similar to the gtk.ColorButton,
but does not have any alpha support.
Instead of a color selector dialog it will pop up a Sugar palette.
As a preview an sugar.graphics.Icon is used. The fill color will be set to
the current color, and the stroke color is set to the font color.
"""
__gtype_name__ = 'SugarColorButton'
__gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
tuple())}
def __init__(self, **kwargs):
self._title = _('Choose a color')
self._color = gtk.gdk.Color(0, 0, 0)
self._has_palette = True
self._has_invoker = True
self._palette = None
self._accept_drag = True
self._preview = Icon(icon_name='color-preview',
icon_size=gtk.ICON_SIZE_BUTTON)
gobject.GObject.__init__(self, **kwargs)
if self._accept_drag:
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_HIGHLIGHT |
gtk.DEST_DEFAULT_DROP,
[('application/x-color', 0, 0)],
gtk.gdk.ACTION_COPY)
self.drag_source_set(gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK,
[('application/x-color', 0, 0)],
gtk.gdk.ACTION_COPY)
self.connect('drag_data_received', self.__drag_data_received_cb)
self.connect('drag_data_get', self.__drag_data_get_cb)
self._preview.fill_color = get_svg_color_string(self._color)
self._preview.stroke_color = \
get_svg_color_string(self.style.fg[gtk.STATE_NORMAL])
self.set_image(self._preview)
if self._has_palette and self._has_invoker:
self._invoker = WidgetInvoker(self)
# FIXME: This is a hack.
self._invoker.has_rectangle_gap = lambda: False
self._invoker.palette = self._palette
def create_palette(self):
if self._has_palette:
self._palette = _ColorPalette(color=self._color,
primary_text=self._title)
self._palette.connect('color-set', self.__palette_color_set_cb)
self._palette.connect('notify::color', self.
__palette_color_changed)
return self._palette
def __palette_color_set_cb(self, palette):
self.emit('color-set')
def __palette_color_changed(self, palette, pspec):
self.color = self._palette.color
def do_style_set(self, previous_style):
self._preview.stroke_color = \
get_svg_color_string(self.style.fg[gtk.STATE_NORMAL])
def do_clicked(self):
if self._palette:
if not self._palette.is_up():
self._palette.popup(immediate=True,
state=self._palette.SECONDARY)
else:
self._palette.popdown(immediate=True)
return True
def set_color(self, color):
assert isinstance(color, gtk.gdk.Color)
if self._color.red == color.red and \
self._color.green == color.green and \
self._color.blue == color.blue:
return
self._color = gtk.gdk.Color(color.red, color.green, color.blue)
self._preview.fill_color = get_svg_color_string(self._color)
if self._palette:
self._palette.props.color = self._color
self.notify('color')
def get_color(self):
return self._color
color = gobject.property(type=object, getter=get_color, setter=set_color)
def set_icon_name(self, icon_name):
self._preview.props.icon_name = icon_name
def get_icon_name(self):
return self._preview.props.icon_name
icon_name = gobject.property(type=str,
getter=get_icon_name, setter=set_icon_name)
def set_icon_size(self, icon_size):
self._preview.props.icon_size = icon_size
def get_icon_size(self):
return self._preview.props.icon_size
icon_size = gobject.property(type=int,
getter=get_icon_size, setter=set_icon_size)
def set_title(self, title):
self._title = title
if self._palette:
self._palette.primary_text = self._title
def get_title(self):
return self._title
title = gobject.property(type=str, getter=get_title, setter=set_title)
def _set_has_invoker(self, has_invoker):
self._has_invoker = has_invoker
def _get_has_invoker(self):
return self._has_invoker
has_invoker = gobject.property(type=bool, default=True,
flags=gobject.PARAM_READWRITE |
gobject.PARAM_CONSTRUCT_ONLY,
getter=_get_has_invoker,
setter=_set_has_invoker)
def _set_has_palette(self, has_palette):
self._has_palette = has_palette
def _get_has_palette(self):
return self._has_palette
has_palette = gobject.property(type=bool, default=True,
flags=gobject.PARAM_READWRITE |
gobject.PARAM_CONSTRUCT_ONLY,
getter=_get_has_palette,
setter=_set_has_palette)
def _set_accept_drag(self, accept_drag):
self._accept_drag = accept_drag
def _get_accept_drag(self):
return self._accept_drag
accept_drag = gobject.property(type=bool, default=True,
flags=gobject.PARAM_READWRITE |
gobject.PARAM_CONSTRUCT_ONLY,
getter=_get_accept_drag,
setter=_set_accept_drag)
def __drag_begin_cb(self, widget, context):
# Drag and Drop
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
style.SMALL_ICON_SIZE,
style.SMALL_ICON_SIZE)
red = self._color.red / 257
green = self._color.green / 257
blue = self._color.blue / 257
pixbuf.fill(red << 24 + green << 16 + blue << 8 + 0xff)
context.set_icon_pixbuf(pixbuf)
def __drag_data_get_cb(self, widget, context, selection_data, info, time):
data = struct.pack('=HHHH', self._color.red, self._color.green,
self._color.blue, 65535)
selection_data.set(selection_data.target, 16, data)
def __drag_data_received_cb(self, widget, context, x, y, selection_data, \
info, time):
if len(selection_data.data) != 8:
return
dropped = selection_data.data
red = struct.unpack_from('=H', dropped, 0)[0]
green = struct.unpack_from('=H', dropped, 2)[0]
blue = struct.unpack_from('=H', dropped, 4)[0]
# dropped[6] and dropped[7] is alpha, but we ignore the alpha channel
color = gtk.gdk.Color(red, green, blue)
self.set_color(color)
class _ColorPalette(Palette):
"""This is a color picker palette. It will usually be used indirectly
trough a sugar.graphics.ColorButton.
"""
_RED = 0
_GREEN = 1
_BLUE = 2
__gtype_name__ = 'SugarColorPalette'
# The color-set signal is emitted when the user is finished selecting
# a color.
__gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
tuple())}
def __init__(self, **kwargs):
self._color = gtk.gdk.Color(0, 0, 0)
self._previous_color = self._color.copy()
self._scales = None
Palette.__init__(self, **kwargs)
self.connect('popup', self.__popup_cb)
self.connect('popdown', self.__popdown_cb)
self._picker_hbox = gtk.HBox()
self.set_content(self._picker_hbox)
self._swatch_tray = gtk.Table()
self._picker_hbox.pack_start(self._swatch_tray)
self._picker_hbox.pack_start(gtk.VSeparator(),
padding=style.DEFAULT_SPACING)
self._chooser_table = gtk.Table(3, 2)
self._chooser_table.set_col_spacing(0, style.DEFAULT_PADDING)
self._scales = []
self._scales.append(
self._create_color_scale(_('Red'), self._RED, 0))
self._scales.append(
self._create_color_scale(_('Green'), self._GREEN, 1))
self._scales.append(
self._create_color_scale(_('Blue'), self._BLUE, 2))
self._picker_hbox.add(self._chooser_table)
self._picker_hbox.show_all()
self._build_swatches()
def _create_color_scale(self, text, color, row):
label = gtk.Label(text)
label.props.xalign = 1.0
scale = gtk.HScale()
scale.set_size_request(style.zoom(250), -1)
scale.set_draw_value(False)
scale.set_range(0, 1.0)
scale.set_increments(0.1, 0.2)
if color == self._RED:
scale.set_value(self._color.red / 65535.0)
elif color == self._GREEN:
scale.set_value(self._color.green / 65535.0)
elif color == self._BLUE:
scale.set_value(self._color.blue / 65535.0)
scale.connect('value-changed',
self.__scale_value_changed_cb,
color)
self._chooser_table.attach(label, 0, 1, row, row + 1)
self._chooser_table.attach(scale, 1, 2, row, row + 1)
return scale
def _build_swatches(self):
for child in self._swatch_tray.get_children():
child.destroy()
# Use a hardcoded list of colors for now.
colors = ['#ed2529', '#69bc47', '#3c54a3',
'#f57f25', '#0b6b3a', '#00a0c6',
'#f6eb1a', '#b93f94', '#5b4a9c',
'#000000', '#919496', '#ffffff']
# We want 3 rows of colors.
rows = 3
i = 0
self._swatch_tray.props.n_rows = rows
self._swatch_tray.props.n_columns = (len(colors) + rows - 1) / rows
for color in colors:
button = _ColorButton(has_palette=False,
color=gtk.gdk.color_parse(color),
accept_drag=False,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
button.set_relief(gtk.RELIEF_NONE)
self._swatch_tray.attach(button,
i % rows, i % rows + 1,
i / rows, i / rows + 1,
yoptions=0, xoptions=0)
button.connect('clicked', self.__swatch_button_clicked_cb)
i += 1
self._swatch_tray.show_all()
def __popup_cb(self, palette):
self._previous_color = self._color.copy()
def __popdown_cb(self, palette):
self.emit('color-set')
def __scale_value_changed_cb(self, widget, color):
new_color = self._color.copy()
if color == self._RED:
new_color.red = int(65535 * widget.get_value())
elif color == self._GREEN:
new_color.green = int(65535 * widget.get_value())
elif color == self._BLUE:
new_color.blue = int(65535 * widget.get_value())
self.color = new_color
def do_key_press_event(self, event):
if event.keyval == gtk.keysyms.Escape:
self.props.color = self._previous_color
self.popdown(immediate=True)
return True
elif event.keyval == gtk.keysyms.Return:
self.popdown(immediate=True)
return True
return False
def __swatch_button_clicked_cb(self, button):
self.props.color = button.get_color()
def set_color(self, color):
assert isinstance(color, gtk.gdk.Color)
if self._color.red == color.red and \
self._color.green == color.green and \
self._color.blue == color.blue:
return
self._color = color.copy()
if self._scales:
self._scales[self._RED].set_value(self._color.red / 65535.0)
self._scales[self._GREEN].set_value(self._color.green / 65535.0)
self._scales[self._BLUE].set_value(self._color.blue / 65535.0)
self.notify('color')
def get_color(self):
return self._color
color = gobject.property(type=object, getter=get_color, setter=set_color)
def _add_accelerator(tool_button):
if not tool_button.props.accelerator or not tool_button.get_toplevel() or \
not tool_button.child:
return
# TODO: should we remove the accelerator from the prev top level?
accel_group = tool_button.get_toplevel().get_data('sugar-accel-group')
if not accel_group:
logging.warning('No gtk.AccelGroup in the top level window.')
return
keyval, mask = gtk.accelerator_parse(tool_button.props.accelerator)
# the accelerator needs to be set at the child, so the gtk.AccelLabel
# in the palette can pick it up.
tool_button.child.add_accelerator('clicked', accel_group, keyval, mask,
gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
def _hierarchy_changed_cb(tool_button, previous_toplevel):
_add_accelerator(tool_button)
def setup_accelerator(tool_button):
_add_accelerator(tool_button)
tool_button.connect('hierarchy-changed', _hierarchy_changed_cb)
class ColorToolButton(gtk.ToolItem):
# This not ideal. It would be better to subclass gtk.ToolButton, however
# the python bindings do not seem to be powerfull enough for that.
# (As we need to change a variable in the class structure.)
__gtype_name__ = 'SugarColorToolButton'
__gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
tuple())}
def __init__(self, icon_name='color-preview', **kwargs):
self._accelerator = None
self._tooltip = None
self._palette_invoker = ToolInvoker()
self._palette = None
gobject.GObject.__init__(self, **kwargs)
# The gtk.ToolButton has already added a normal button.
# Replace it with a ColorButton
color_button = _ColorButton(icon_name=icon_name, has_invoker=False)
self.add(color_button)
# The following is so that the behaviour on the toolbar is correct.
color_button.set_relief(gtk.RELIEF_NONE)
color_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
self._palette_invoker.attach_tool(self)
# This widget just proxies the following properties to the colorbutton
color_button.connect('notify::color', self.__notify_change)
color_button.connect('notify::icon-name', self.__notify_change)
color_button.connect('notify::icon-size', self.__notify_change)
color_button.connect('notify::title', self.__notify_change)
color_button.connect('color-set', self.__color_set_cb)
color_button.connect('can-activate-accel',
self.__button_can_activate_accel_cb)
def __button_can_activate_accel_cb(self, button, signal_id):
# Accept activation via accelerators regardless of this widget's state
return True
def set_accelerator(self, accelerator):
self._accelerator = accelerator
setup_accelerator(self)
def get_accelerator(self):
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
def create_palette(self):
self._palette = self.get_child().create_palette()
return self._palette
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = gobject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def set_color(self, color):
self.get_child().props.color = color
def get_color(self):
return self.get_child().props.color
color = gobject.property(type=object, getter=get_color, setter=set_color)
def set_icon_name(self, icon_name):
self.get_child().props.icon_name = icon_name
def get_icon_name(self):
return self.get_child().props.icon_name
icon_name = gobject.property(type=str,
getter=get_icon_name, setter=set_icon_name)
def set_icon_size(self, icon_size):
self.get_child().props.icon_size = icon_size
def get_icon_size(self):
return self.get_child().props.icon_size
icon_size = gobject.property(type=int,
getter=get_icon_size, setter=set_icon_size)
def set_title(self, title):
self.get_child().props.title = title
def get_title(self):
return self.get_child().props.title
title = gobject.property(type=str, getter=get_title, setter=set_title)
def do_expose_event(self, event):
child = self.get_child()
allocation = self.get_allocation()
if self._palette and self._palette.is_up():
invoker = self._palette.props.invoker
invoker.draw_rectangle(event, self._palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, 'toolbutton-prelight',
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.ToolButton.do_expose_event(self, event)
def __notify_change(self, widget, pspec):
self.notify(pspec.name)
def __color_set_cb(self, widget):
self.emit('color-set')
+170
View File
@@ -0,0 +1,170 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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.
"""
STABLE.
"""
import gobject
import gtk
class ComboBox(gtk.ComboBox):
__gtype_name__ = 'SugarComboBox'
def __init__(self):
gtk.ComboBox.__init__(self)
self._text_renderer = None
self._icon_renderer = None
self._model = gtk.ListStore(gobject.TYPE_PYOBJECT,
gobject.TYPE_STRING,
gtk.gdk.Pixbuf,
gobject.TYPE_BOOLEAN)
self.set_model(self._model)
self.set_row_separator_func(self._is_separator)
def get_value(self):
"""
Parameters
----------
None :
Returns:
--------
value :
"""
row = self.get_active_item()
if not row:
return None
return row[0]
value = gobject.property(
type=object, getter=get_value, setter=None)
def _get_real_name_from_theme(self, name, size):
icon_theme = gtk.icon_theme_get_default()
width, height = gtk.icon_size_lookup(size)
info = icon_theme.lookup_icon(name, max(width, height), 0)
if not info:
raise ValueError('Icon %r not found.' % name)
fname = info.get_filename()
del info
return fname
def append_item(self, action_id, text, icon_name=None, file_name=None):
"""
Parameters
----------
action_id :
text :
icon_name=None :
file_name=None :
Returns
-------
None
"""
if not self._icon_renderer and (icon_name or file_name):
self._icon_renderer = gtk.CellRendererPixbuf()
settings = self.get_settings()
w, h = gtk.icon_size_lookup_for_settings(
settings, gtk.ICON_SIZE_MENU)
self._icon_renderer.props.stock_size = max(w, h)
self.pack_start(self._icon_renderer, False)
self.add_attribute(self._icon_renderer, 'pixbuf', 2)
if not self._text_renderer and text:
self._text_renderer = gtk.CellRendererText()
self.pack_end(self._text_renderer, True)
self.add_attribute(self._text_renderer, 'text', 1)
if icon_name or file_name:
if text:
size = gtk.ICON_SIZE_MENU
else:
size = gtk.ICON_SIZE_LARGE_TOOLBAR
width, height = gtk.icon_size_lookup(size)
if icon_name:
file_name = self._get_real_name_from_theme(icon_name, size)
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
file_name, width, height)
else:
pixbuf = None
self._model.append([action_id, text, pixbuf, False])
def append_separator(self):
"""
Parameters
----------
None
Returns
-------
None
"""
self._model.append([0, None, None, True])
def get_active_item(self):
"""
Parameters
----------
None
Returns
-------
Active_item :
"""
index = self.get_active()
if index == -1:
index = 0
row = self._model.iter_nth_child(None, index)
if not row:
return None
return self._model[row]
def remove_all(self):
"""
Parameters
----------
None
Returns
-------
None
"""
self._model.clear()
def _is_separator(self, model, row):
return model[row][3]
+41
View File
@@ -0,0 +1,41 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import gtk
import hippo
class CanvasEntry(hippo.CanvasEntry):
def set_background(self, color_spec):
"""
Parameters
----------
color_spec :
Returns
-------
None
"""
color = gtk.gdk.color_parse(color_spec)
self.props.widget.modify_bg(gtk.STATE_INSENSITIVE, color)
self.props.widget.modify_base(gtk.STATE_INSENSITIVE, color)
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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 gtk
from sugar.graphics import style
from sugar.graphics.icon import _SVGLoader
ICON_ENTRY_PRIMARY = gtk.ENTRY_ICON_PRIMARY
ICON_ENTRY_SECONDARY = gtk.ENTRY_ICON_SECONDARY
class IconEntry(gtk.Entry):
def __init__(self):
gtk.Entry.__init__(self)
self._clear_icon = None
self._clear_shown = False
self.connect('key_press_event', self._keypress_event_cb)
def set_icon_from_name(self, position, name):
icon_theme = gtk.icon_theme_get_default()
icon_info = icon_theme.lookup_icon(name,
gtk.ICON_SIZE_SMALL_TOOLBAR,
0)
if icon_info.get_filename().endswith('.svg'):
loader = _SVGLoader()
entities = {'fill_color': style.COLOR_TOOLBAR_GREY.get_svg(),
'stroke_color': style.COLOR_TOOLBAR_GREY.get_svg()}
handle = loader.load(icon_info.get_filename(), entities, None)
pixbuf = handle.get_pixbuf()
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
del icon_info
self.set_icon(position, pixbuf)
def set_icon(self, position, pixbuf):
if type(pixbuf) is not gtk.gdk.Pixbuf:
raise ValueError('Argument must be a pixbuf, not %r.' % pixbuf)
self.set_icon_from_pixbuf(position, pixbuf)
def remove_icon(self, position):
self.set_icon_from_pixbuf(position, None)
def add_clear_button(self):
if self.props.text != "":
self.show_clear_button()
else:
self.hide_clear_button()
self.connect('icon-press', self._icon_pressed_cb)
self.connect('changed', self._changed_cb)
def show_clear_button(self):
if not self._clear_shown:
self.set_icon_from_name(ICON_ENTRY_SECONDARY,
'dialog-cancel')
self._clear_shown = True
def hide_clear_button(self):
if self._clear_shown:
self.remove_icon(ICON_ENTRY_SECONDARY)
self._clear_shown = False
def _keypress_event_cb(self, widget, event):
keyval = gtk.gdk.keyval_name(event.keyval)
if keyval == 'Escape':
self.props.text = ''
return True
return False
def _icon_pressed_cb(self, entru, icon_pos, button):
if icon_pos == ICON_ENTRY_SECONDARY:
self.set_text('')
self.hide_clear_button()
def _changed_cb(self, icon_entry):
if not self.props.text:
self.hide_clear_button()
else:
self.show_clear_button()
+95
View File
@@ -0,0 +1,95 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# 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.
"""
STABLE.
"""
import logging
import gobject
import pango
import gtk
from sugar.graphics.icon import Icon
class MenuItem(gtk.ImageMenuItem):
def __init__(self, text_label=None, icon_name=None, text_maxlen=60,
xo_color=None, file_name=None):
gobject.GObject.__init__(self)
self._accelerator = None
label = gtk.AccelLabel(text_label)
label.set_alignment(0.0, 0.5)
label.set_accel_widget(self)
if text_maxlen > 0:
label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
label.set_max_width_chars(text_maxlen)
self.add(label)
label.show()
if icon_name is not None:
icon = Icon(icon_name=icon_name,
icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
if xo_color is not None:
icon.props.xo_color = xo_color
self.set_image(icon)
icon.show()
elif file_name is not None:
icon = Icon(file=file_name, icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
if xo_color is not None:
icon.props.xo_color = xo_color
self.set_image(icon)
icon.show()
self.connect('can-activate-accel', self.__can_activate_accel_cb)
self.connect('hierarchy-changed', self.__hierarchy_changed_cb)
def __hierarchy_changed_cb(self, widget, previous_toplevel):
self._add_accelerator()
def __can_activate_accel_cb(self, widget, signal_id):
# Accept activation via accelerators regardless of this widget's state
return True
def _add_accelerator(self):
if self._accelerator is None or self.get_toplevel() is None:
return
# TODO: should we remove the accelerator from the prev top level?
accel_group = self.get_toplevel().get_data('sugar-accel-group')
if not accel_group:
logging.warning('No gtk.AccelGroup in the top level window.')
return
keyval, mask = gtk.accelerator_parse(self._accelerator)
self.add_accelerator('activate', accel_group, keyval, mask,
gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
def set_accelerator(self, accelerator):
self._accelerator = accelerator
self._add_accelerator()
def get_accelerator(self):
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
+151
View File
@@ -0,0 +1,151 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com)
#
# 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.
"""Notebook class
This class create a gtk.Notebook() widget supporting
a close button in every tab when the 'can-close-tabs' gproperty
is enabled (True)
STABLE.
"""
import gtk
import gobject
class Notebook(gtk.Notebook):
__gtype_name__ = 'SugarNotebook'
__gproperties__ = {
'can-close-tabs': (bool, None, None, False,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
}
def __init__(self, **kwargs):
# Initialise the Widget
# Side effects:
# Set the 'can-close-tabs' property using **kwargs
# Set True the scrollable notebook property
gobject.GObject.__init__(self, **kwargs)
self._can_close_tabs = None
self.set_scrollable(True)
self.show()
def do_set_property(self, pspec, value):
"""
Set notebook property
Parameters
----------
pspec :
property for which the value will be set
Returns
-------
None
Raises
------
AssertionError
"""
if pspec.name == 'can-close-tabs':
self._can_close_tabs = value
else:
raise AssertionError
def _add_icon_to_button(self, button):
icon_box = gtk.HBox()
image = gtk.Image()
image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
gtk.Button.set_relief(button, gtk.RELIEF_NONE)
settings = gtk.Widget.get_settings(button)
w, h = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
gtk.Widget.set_size_request(button, w + 4, h + 4)
image.show()
icon_box.pack_start(image, True, False, 0)
button.add(icon_box)
icon_box.show()
def _create_custom_tab(self, text, child):
event_box = gtk.EventBox()
tab_box = gtk.HBox(False, 2)
tab_label = gtk.Label(text)
tab_button = gtk.Button()
tab_button.connect('clicked', self._close_page, child)
# Add a picture on a button
self._add_icon_to_button(tab_button)
event_box.show()
tab_button.show()
tab_label.show()
tab_box.pack_start(tab_label, True)
tab_box.pack_start(tab_button, True)
tab_box.show_all()
event_box.add(tab_box)
return event_box
def add_page(self, text_label, widget):
"""
Adds a page to the notebook.
Parameters
----------
text_label :
widget :
Returns
-------
Boolean
Returns TRUE if the page is successfully added to th notebook.
"""
# Add a new page to the notebook
if self._can_close_tabs:
eventbox = self._create_custom_tab(text_label, widget)
self.append_page(widget, eventbox)
else:
self.append_page(widget, gtk.Label(text_label))
pages = self.get_n_pages()
# Set the new page
self.set_current_page(pages - 1)
self.show_all()
return True
def _close_page(self, button, child):
# Remove a page from the notebook
page = self.page_num(child)
if page != -1:
self.remove_page(page)
+132
View File
@@ -0,0 +1,132 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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.
"""
STABLE.
"""
import logging
import gobject
import gtk
import dbus
from sugar.datastore import datastore
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
J_DBUS_PATH = '/org/laptop/Journal'
class ObjectChooser(object):
def __init__(self, title=None, parent=None, flags=None, buttons=None,
what_filter=None):
# For backwards compatibility:
# - We ignore title, flags and buttons.
# - 'parent' can be a xid or a gtk.Window
if title is not None or flags is not None or buttons is not None:
logging.warning('Invocation of ObjectChooser() has deprecated '
'parameters.')
if parent is None:
parent_xid = 0
elif hasattr(parent, 'window') and hasattr(parent.window, 'xid'):
parent_xid = parent.window.xid
else:
parent_xid = parent
self._parent_xid = parent_xid
self._main_loop = None
self._object_id = None
self._bus = None
self._chooser_id = None
self._response_code = gtk.RESPONSE_NONE
self._what_filter = what_filter
def run(self):
self._object_id = None
self._main_loop = gobject.MainLoop()
self._bus = dbus.SessionBus(mainloop=self._main_loop)
self._bus.add_signal_receiver(
self.__name_owner_changed_cb,
signal_name='NameOwnerChanged',
dbus_interface='org.freedesktop.DBus',
arg0=J_DBUS_SERVICE)
obj = self._bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
journal = dbus.Interface(obj, J_DBUS_INTERFACE)
journal.connect_to_signal('ObjectChooserResponse',
self.__chooser_response_cb)
journal.connect_to_signal('ObjectChooserCancelled',
self.__chooser_cancelled_cb)
if self._what_filter is None:
what_filter = ''
else:
what_filter = self._what_filter
self._chooser_id = journal.ChooseObject(self._parent_xid, what_filter)
gtk.gdk.threads_leave()
try:
self._main_loop.run()
finally:
gtk.gdk.threads_enter()
self._main_loop = None
return self._response_code
def get_selected_object(self):
if self._object_id is None:
return None
else:
return datastore.get(self._object_id)
def destroy(self):
self._cleanup()
def _cleanup(self):
if self._main_loop is not None:
self._main_loop.quit()
self._main_loop = None
self._bus = None
def __chooser_response_cb(self, chooser_id, object_id):
if chooser_id != self._chooser_id:
return
logging.debug('ObjectChooser.__chooser_response_cb: %r', object_id)
self._response_code = gtk.RESPONSE_ACCEPT
self._object_id = object_id
self._cleanup()
def __chooser_cancelled_cb(self, chooser_id):
if chooser_id != self._chooser_id:
return
logging.debug('ObjectChooser.__chooser_cancelled_cb: %r', chooser_id)
self._response_code = gtk.RESPONSE_CANCEL
self._cleanup()
def __name_owner_changed_cb(self, name, old, new):
logging.debug('ObjectChooser.__name_owner_changed_cb')
# Journal service disappeared from the bus
self._response_code = gtk.RESPONSE_CANCEL
self._cleanup()
+445
View File
@@ -0,0 +1,445 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
# Copyright (C) 2008, One Laptop Per Child
# Copyright (C) 2009, Tomeu Vizoso
#
# 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.
"""
STABLE.
"""
import gtk
import gobject
import pango
from sugar.graphics import palettegroup
from sugar.graphics import animator
from sugar.graphics import style
from sugar.graphics.icon import Icon
from sugar.graphics.palettewindow import PaletteWindow
from sugar import _sugarext
# DEPRECATED
# Import these for backwards compatibility
from sugar.graphics.palettewindow import MouseSpeedDetector, Invoker, \
WidgetInvoker, CanvasInvoker, ToolInvoker, CellRendererInvoker
class Palette(PaletteWindow):
PRIMARY = 0
SECONDARY = 1
__gtype_name__ = 'SugarPalette'
def __init__(self, label=None, accel_path=None, menu_after_content=False,
text_maxlen=60, **kwargs):
# DEPRECATED: label is passed with the primary-text property,
# accel_path is set via the invoker property, and menu_after_content
# is not used
self._primary_text = None
self._secondary_text = None
self._icon = None
self._icon_visible = True
self._palette_state = self.PRIMARY
palette_box = gtk.VBox()
primary_box = gtk.HBox()
palette_box.pack_start(primary_box, expand=False)
primary_box.show()
self._icon_box = gtk.HBox()
self._icon_box.set_size_request(style.GRID_CELL_SIZE, -1)
primary_box.pack_start(self._icon_box, expand=False)
labels_box = gtk.VBox()
self._label_alignment = gtk.Alignment(xalign=0, yalign=0.5,
xscale=1, yscale=0.33)
self._label_alignment.set_padding(0, 0, style.DEFAULT_SPACING,
style.DEFAULT_SPACING)
self._label_alignment.add(labels_box)
self._label_alignment.show()
primary_box.pack_start(self._label_alignment, expand=True)
labels_box.show()
self._label = gtk.AccelLabel('')
self._label.set_alignment(0, 0.5)
if text_maxlen > 0:
self._label.set_max_width_chars(text_maxlen)
self._label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
labels_box.pack_start(self._label, expand=True)
self._secondary_label = gtk.Label()
self._secondary_label.set_alignment(0, 0.5)
if text_maxlen > 0:
self._secondary_label.set_max_width_chars(text_maxlen)
self._secondary_label.set_ellipsize(pango.ELLIPSIZE_END)
labels_box.pack_start(self._secondary_label, expand=True)
self._secondary_box = gtk.VBox()
palette_box.pack_start(self._secondary_box)
self._separator = gtk.HSeparator()
self._secondary_box.pack_start(self._separator)
self._menu_content_separator = gtk.HSeparator()
self._secondary_anim = animator.Animator(2.0, 10)
self._secondary_anim.add(_SecondaryAnimation(self))
# we init after initializing all of our containers
PaletteWindow.__init__(self, **kwargs)
primary_box.set_size_request(-1, style.GRID_CELL_SIZE
- 2 * self.get_border_width())
self._full_request = [0, 0]
self._menu_box = None
self._content = None
# we set these for backward compatibility
if label is not None:
self.props.primary_text = label
self._add_menu()
self._secondary_box.pack_start(self._menu_content_separator)
self._add_content()
self.action_bar = PaletteActionBar()
self._secondary_box.pack_start(self.action_bar)
self.action_bar.show()
self.add(palette_box)
palette_box.show()
# The menu is not shown here until an item is added
self.menu = _Menu(self)
self.menu.connect('item-inserted', self.__menu_item_inserted_cb)
self.connect('realize', self.__realize_cb)
self.connect('show', self.__show_cb)
self.connect('hide', self.__hide_cb)
self.connect('notify::invoker', self.__notify_invoker_cb)
self.connect('destroy', self.__destroy_cb)
def _invoker_right_click_cb(self, invoker):
self.popup(immediate=True, state=self.SECONDARY)
def do_style_set(self, previous_style):
# Prevent a warning from pygtk
if previous_style is not None:
gtk.Window.do_style_set(self, previous_style)
self.set_border_width(self.get_style().xthickness)
def __menu_item_inserted_cb(self, menu):
self._update_separators()
def __destroy_cb(self, palette):
self._secondary_anim.stop()
self.popdown(immediate=True)
# 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.disconnect_by_func(self.__menu_item_inserted_cb)
self.menu = None
def __show_cb(self, widget):
self.menu.set_active(True)
def __hide_cb(self, widget):
self.menu.set_active(False)
self.menu.cancel()
self._secondary_anim.stop()
def __notify_invoker_cb(self, palette, pspec):
invoker = self.props.invoker
if invoker is not None and hasattr(invoker.props, 'widget'):
self._update_accel_widget()
self._invoker.connect('notify::widget',
self.__invoker_widget_changed_cb)
def __invoker_widget_changed_cb(self, invoker, spec):
self._update_accel_widget()
def get_full_size_request(self):
return self._full_request
def popup(self, immediate=False, state=None):
if self._invoker is not None:
self._update_full_request()
PaletteWindow.popup(self, immediate)
if state is None:
state = self.PRIMARY
self.set_palette_state(state)
if state == self.PRIMARY:
self._secondary_anim.start()
else:
self._secondary_anim.stop()
def popdown(self, immediate=False):
if immediate:
self._secondary_anim.stop()
self._popdown_submenus()
# to suppress glitches while later re-opening
self.set_palette_state(self.PRIMARY)
PaletteWindow.popdown(self, immediate)
def _popdown_submenus(self):
# TODO explicit hiding of subitems
# should be removed after fixing #1301
if self.menu is not None:
for menu_item in self.menu.get_children():
if menu_item.props.submenu is not None:
menu_item.props.submenu.popdown()
def on_enter(self, event):
PaletteWindow.on_enter(self, event)
self._secondary_anim.start()
def _add_menu(self):
self._menu_box = gtk.VBox()
self._secondary_box.pack_start(self._menu_box)
self._menu_box.show()
def _add_content(self):
# The content is not shown until a widget is added
self._content = gtk.VBox()
self._content.set_border_width(style.DEFAULT_SPACING)
self._secondary_box.pack_start(self._content)
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
if label is not None:
self._label.set_markup('<b>%s</b>' % label)
self._label.show()
def get_primary_text(self):
return self._primary_text
primary_text = gobject.property(type=str,
getter=get_primary_text,
setter=set_primary_text)
def set_secondary_text(self, label):
if label is not None:
label = label.split('\n', 1)[0]
self._secondary_text = label
if label is None:
self._secondary_label.hide()
else:
self._secondary_label.set_text(label)
self._secondary_label.show()
def get_secondary_text(self):
return self._secondary_text
secondary_text = gobject.property(type=str, getter=get_secondary_text,
setter=set_secondary_text)
def _show_icon(self):
self._label_alignment.set_padding(0, 0, 0, style.DEFAULT_SPACING)
self._icon_box.show()
def _hide_icon(self):
self._icon_box.hide()
self._label_alignment.set_padding(0, 0, style.DEFAULT_SPACING,
style.DEFAULT_SPACING)
def set_icon(self, icon):
if icon is None:
self._icon = None
self._hide_icon()
else:
if self._icon:
self._icon_box.remove(self._icon_box.get_children()[0])
event_box = gtk.EventBox()
event_box.connect('button-release-event',
self.__icon_button_release_event_cb)
self._icon_box.pack_start(event_box)
event_box.show()
self._icon = icon
self._icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
event_box.add(self._icon)
self._icon.show()
self._show_icon()
def get_icon(self):
return self._icon
icon = gobject.property(type=object, getter=get_icon, setter=set_icon)
def __icon_button_release_event_cb(self, icon, event):
self.emit('activate')
def set_icon_visible(self, visible):
self._icon_visible = visible
if visible and self._icon is not None:
self._show_icon()
else:
self._hide_icon()
def get_icon_visible(self):
return self._icon_visilbe
icon_visible = gobject.property(type=bool,
default=True,
getter=get_icon_visible,
setter=set_icon_visible)
def set_content(self, widget):
if len(self._content.get_children()) > 0:
self._content.remove(self._content.get_children()[0])
if widget is not None:
self._content.add(widget)
self._content.show()
else:
self._content.hide()
self._update_accept_focus()
self._update_separators()
def do_size_request(self, requisition):
PaletteWindow.do_size_request(self, requisition)
# gtk.AccelLabel request doesn't include the accelerator.
label_width = self._label_alignment.size_request()[0] + \
self._label.get_accel_width() + \
2 * self.get_border_width()
requisition.width = max(requisition.width,
label_width,
self._full_request[0])
def _update_separators(self):
visible = len(self.menu.get_children()) > 0 or \
len(self._content.get_children()) > 0
self._separator.props.visible = visible
visible = len(self.menu.get_children()) > 0 and \
len(self._content.get_children()) > 0
self._menu_content_separator.props.visible = visible
def _update_accept_focus(self):
accept_focus = len(self._content.get_children())
if self.window:
self.window.set_accept_focus(accept_focus)
def __realize_cb(self, widget):
self._update_accept_focus()
def _update_full_request(self):
if self._palette_state == self.PRIMARY:
self.menu.embed(self._menu_box)
self._secondary_box.show()
self._full_request = self.size_request()
if self._palette_state == self.PRIMARY:
self.menu.unembed()
self._secondary_box.hide()
def _set_palette_state(self, state):
if self._palette_state == state:
return
if state == self.PRIMARY:
self.menu.unembed()
self._secondary_box.hide()
elif state == self.SECONDARY:
self.menu.embed(self._menu_box)
self._secondary_box.show()
self.update_position()
self._palette_state = state
class PaletteActionBar(gtk.HButtonBox):
def add_action(self, label, icon_name=None):
button = gtk.Button(label)
if icon_name:
icon = Icon(icon_name)
button.set_image(icon)
icon.show()
self.pack_start(button)
button.show()
class _Menu(_sugarext.Menu):
__gtype_name__ = 'SugarPaletteMenu'
__gsignals__ = {
'item-inserted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self, palette):
_sugarext.Menu.__init__(self)
self._palette = palette
def do_insert(self, item, position):
_sugarext.Menu.do_insert(self, item, position)
self.emit('item-inserted')
self.show()
def attach(self, child, left_attach, right_attach,
top_attach, bottom_attach):
_sugarext.Menu.attach(self, child, left_attach, right_attach,
top_attach, bottom_attach)
self.emit('item-inserted')
self.show()
def do_expose_event(self, event):
# Ignore the Menu expose, just do the MenuShell expose to prevent any
# border from being drawn here. A border is drawn by the palette object
# around everything.
gtk.MenuShell.do_expose_event(self, event)
def do_grab_notify(self, was_grabbed):
# Ignore grab_notify as the menu would close otherwise
pass
def do_deactivate(self):
self._palette.hide()
class _SecondaryAnimation(animator.Animation):
def __init__(self, palette):
animator.Animation.__init__(self, 0.0, 1.0)
self._palette = palette
def next_frame(self, current):
if current == 1.0:
self._palette.set_palette_state(Palette.SECONDARY)
+106
View File
@@ -0,0 +1,106 @@
# Copyright (C) 2007 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.
"""
STABLE.
"""
import gobject
_groups = {}
def get_group(group_id):
if group_id in _groups:
group = _groups[group_id]
else:
group = Group()
_groups[group_id] = group
return group
def popdown_all():
for group in _groups.values():
group.popdown()
class Group(gobject.GObject):
__gsignals__ = {
'popup': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'popdown': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
gobject.GObject.__init__(self)
self._up = False
self._palettes = []
self._sig_ids = {}
def is_up(self):
return self._up
def get_state(self):
for palette in self._palettes:
if palette.is_up():
return palette.palette_state
return None
def add(self, palette):
self._palettes.append(palette)
self._sig_ids[palette] = []
sid = palette.connect('popup', self._palette_popup_cb)
self._sig_ids[palette].append(sid)
sid = palette.connect('popdown', self._palette_popdown_cb)
self._sig_ids[palette].append(sid)
def remove(self, palette):
sig_ids = self._sig_ids[palette]
for sid in sig_ids:
palette.disconnect(sid)
self._palettes.remove(palette)
del self._sig_ids[palette]
def popdown(self):
for palette in self._palettes:
if palette.is_up():
palette.popdown(immediate=True)
def _palette_popup_cb(self, palette):
for i in self._palettes:
if i != palette:
i.popdown(immediate=True)
if not self._up:
self.emit('popup')
self._up = True
def _palette_popdown_cb(self, palette):
down = True
for palette in self._palettes:
if palette.is_up():
down = False
if down:
self._up = False
self.emit('popdown')
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import gtk
class Panel(gtk.VBox):
__gtype_name__ = 'SugarPanel'
def __init__(self):
gtk.VBox.__init__(self)
+104
View File
@@ -0,0 +1,104 @@
# Copyright (C) 2009, Aleksey Lim
#
# 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 gtk
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.palette import Palette
class RadioMenuButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, **kwargs)
self.selected_button = None
if self.props.palette:
self.__palette_cb(None, None)
self.connect('notify::palette', self.__palette_cb)
def __palette_cb(self, widget, pspec):
if not isinstance(self.props.palette, RadioPalette):
return
self.props.palette.update_button()
def do_clicked(self):
if self.palette is None:
return
if self.palette.is_up() and \
self.palette.palette_state == Palette.SECONDARY:
self.palette.popdown(immediate=True)
else:
self.palette.popup(immediate=True, state=Palette.SECONDARY)
class RadioToolsButton(RadioMenuButton):
def __init__(self, **kwargs):
RadioMenuButton.__init__(self, **kwargs)
def do_clicked(self):
if not self.selected_button:
return
self.selected_button.emit('clicked')
class RadioPalette(Palette):
def __init__(self, **kwargs):
Palette.__init__(self, **kwargs)
self.button_box = gtk.HBox()
self.button_box.show()
self.set_content(self.button_box)
def append(self, button, label):
children = self.button_box.get_children()
if button.palette is not None:
raise RuntimeError("Palette's button should not have sub-palettes")
button.show()
button.connect('clicked', self.__clicked_cb)
self.button_box.pack_start(button, fill=False)
button.palette_label = label
if not children:
self.__clicked_cb(button)
def update_button(self):
for i in self.button_box.get_children():
self.__clicked_cb(i)
def __clicked_cb(self, button):
if not button.get_active():
return
self.set_primary_text(button.palette_label)
self.popdown(immediate=True)
if self.invoker is not None:
parent = self.invoker.parent
else:
parent = None
if not isinstance(parent, RadioMenuButton):
return
parent.props.label = button.palette_label
parent.set_icon(button.props.icon_name)
parent.selected_button = button
+182
View File
@@ -0,0 +1,182 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007-2008, One Laptop Per Child
#
# 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.
"""
STABLE.
"""
import gtk
import gobject
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
from sugar.graphics import toolbutton
class RadioToolButton(gtk.RadioToolButton):
"""
An implementation of a "push" button.
"""
__gtype_name__ = 'SugarRadioToolButton'
def __init__(self, **kwargs):
self._accelerator = None
self._tooltip = None
self._xo_color = None
self._palette_invoker = ToolInvoker()
gobject.GObject.__init__(self, **kwargs)
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.
Parameters
----------
tooltip:
Returns
-------
None
"""
if self.palette is None or self._tooltip is None:
self.palette = Palette(tooltip)
elif self.palette is not None:
self.palette.set_primary_text(tooltip)
self._tooltip = tooltip
# Set label, shows up when toolbar overflows
gtk.RadioToolButton.set_label(self, tooltip)
def get_tooltip(self):
return self._tooltip
tooltip = gobject.property(type=str, setter=set_tooltip,
getter=get_tooltip)
def set_accelerator(self, accelerator):
"""
Sets the accelerator.
Parameters
----------
accelerator:
Returns
-------
None
"""
self._accelerator = accelerator
toolbutton.setup_accelerator(self)
def get_accelerator(self):
"""
Returns the accelerator for the button.
Parameters
----------
None
Returns
------
accelerator:
"""
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
def set_named_icon(self, named_icon):
icon = Icon(icon_name=named_icon,
xo_color=self._xo_color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self.set_icon_widget(icon)
icon.show()
def get_named_icon(self):
if self.props.icon_widget is not None:
return self.props.icon_widget.props.icon_name
else:
return None
named_icon = gobject.property(type=str, setter=set_named_icon,
getter=get_named_icon)
def set_xo_color(self, xo_color):
if self._xo_color != xo_color:
self._xo_color = xo_color
if self.props.icon_widget is not None:
self.props.icon_widget.props.xo_color = xo_color
def get_xo_color(self):
return self._xo_color
xo_color = gobject.property(type=object, setter=set_xo_color,
getter=get_xo_color)
def create_palette(self):
return None
def get_palette(self):
return self._palette_invoker.palette
def set_palette(self, palette):
self._palette_invoker.palette = palette
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = gobject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def do_expose_event(self, event):
child = self.get_child()
allocation = self.get_allocation()
if self.palette and self.palette.is_up():
invoker = self.palette.props.invoker
invoker.draw_rectangle(event, self.palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, 'toolbutton-prelight',
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.RadioToolButton.do_expose_event(self, event)
+71
View File
@@ -0,0 +1,71 @@
# Copyright (C) 2006-2007 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.
"""
STABLE.
"""
import math
import hippo
from sugar.graphics import style
class CanvasRoundBox(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarRoundBox'
_BORDER_DEFAULT = style.LINE_WIDTH
def __init__(self, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs)
# TODO: we should calculate radius depending on the height of the box.
self._radius = style.zoom(10)
self.props.orientation = hippo.ORIENTATION_HORIZONTAL
self.props.border = self._BORDER_DEFAULT
self.props.border_left = self._radius
self.props.border_right = self._radius
self.props.border_color = style.COLOR_BLACK.get_int()
def do_paint_background(self, cr, damaged_box):
[width, height] = self.get_allocation()
x = self._BORDER_DEFAULT / 2
y = self._BORDER_DEFAULT / 2
width -= self._BORDER_DEFAULT
height -= self._BORDER_DEFAULT
cr.move_to(x + self._radius, y)
cr.arc(x + width - self._radius, y + self._radius,
self._radius, math.pi * 1.5, math.pi * 2)
cr.arc(x + width - self._radius, x + height - self._radius,
self._radius, 0, math.pi * 0.5)
cr.arc(x + self._radius, y + height - self._radius,
self._radius, math.pi * 0.5, math.pi)
cr.arc(x + self._radius, y + self._radius, self._radius,
math.pi, math.pi * 1.5)
hippo.cairo_set_source_rgba32(cr, self.props.background_color)
cr.fill_preserve()
# TODO: we should be more consistent here with the border properties.
if self.props.border_color:
hippo.cairo_set_source_rgba32(cr, self.props.border_color)
cr.set_line_width(self.props.border_top)
cr.stroke()
+147
View File
@@ -0,0 +1,147 @@
# Copyright (C) 2007, 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.
"""
All the constants are expressed in pixels. They are defined for the XO screen
and are usually adapted to different resolution by applying a zoom factor.
STABLE.
"""
import os
import logging
import gtk
import pango
import gconf
FOCUS_LINE_WIDTH = 2
_TAB_CURVATURE = 1
def _compute_zoom_factor():
try:
scaling = int(os.environ.get('SUGAR_SCALING', '100'))
return scaling / 100.0
except ValueError:
logging.error('Invalid SUGAR_SCALING.')
return 1.0
class Font(object):
def __init__(self, desc):
self._desc = desc
def __str__(self):
return self._desc
def get_pango_desc(self):
return pango.FontDescription(self._desc)
class Color(object):
def __init__(self, color, alpha=1.0):
self._r, self._g, self._b = self._html_to_rgb(color)
self._a = alpha
def get_rgba(self):
return (self._r, self._g, self._b, self._a)
def get_int(self):
return int(self._a * 255) + (int(self._b * 255) << 8) + \
(int(self._g * 255) << 16) + (int(self._r * 255) << 24)
def get_gdk_color(self):
return gtk.gdk.Color(int(self._r * 65535), int(self._g * 65535),
int(self._b * 65535))
def get_html(self):
return '#%02x%02x%02x' % (self._r * 255, self._g * 255, self._b * 255)
def _html_to_rgb(self, html_color):
""" #RRGGBB -> (r, g, b) tuple (in float format) """
html_color = html_color.strip()
if html_color[0] == '#':
html_color = html_color[1:]
if len(html_color) != 6:
raise ValueError('input #%s is not in #RRGGBB format' % html_color)
r, g, b = html_color[:2], html_color[2:4], html_color[4:]
r, g, b = [int(n, 16) for n in (r, g, b)]
r, g, b = (r / 255.0, g / 255.0, b / 255.0)
return (r, g, b)
def get_svg(self):
if self._a == 0.0:
return 'none'
else:
return self.get_html()
def zoom(units):
return int(ZOOM_FACTOR * units)
ZOOM_FACTOR = _compute_zoom_factor()
DEFAULT_SPACING = zoom(15)
DEFAULT_PADDING = zoom(6)
GRID_CELL_SIZE = zoom(75)
LINE_WIDTH = zoom(2)
STANDARD_ICON_SIZE = zoom(55)
SMALL_ICON_SIZE = zoom(55 * 0.5)
MEDIUM_ICON_SIZE = zoom(55 * 1.5)
LARGE_ICON_SIZE = zoom(55 * 2.0)
XLARGE_ICON_SIZE = zoom(55 * 2.75)
client = gconf.client_get_default()
FONT_SIZE = client.get_float('/desktop/sugar/font/default_size')
FONT_FACE = client.get_string('/desktop/sugar/font/default_face')
FONT_NORMAL = Font('%s %f' % (FONT_FACE, FONT_SIZE))
FONT_BOLD = Font('%s bold %f' % (FONT_FACE, FONT_SIZE))
FONT_NORMAL_H = zoom(24)
FONT_BOLD_H = zoom(24)
TOOLBOX_SEPARATOR_HEIGHT = zoom(9)
TOOLBOX_HORIZONTAL_PADDING = zoom(75)
TOOLBOX_TAB_VBORDER = int((zoom(36) - FONT_NORMAL_H - FOCUS_LINE_WIDTH) / 2)
TOOLBOX_TAB_HBORDER = zoom(15) - FOCUS_LINE_WIDTH - _TAB_CURVATURE
TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2)
COLOR_BLACK = Color('#000000')
COLOR_WHITE = Color('#FFFFFF')
COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
COLOR_PANEL_GREY = Color('#C0C0C0')
COLOR_SELECTION_GREY = Color('#A6A6A6')
COLOR_TOOLBAR_GREY = Color('#282828')
COLOR_BUTTON_GREY = Color('#808080')
COLOR_INACTIVE_FILL = Color('#9D9FA1')
COLOR_INACTIVE_STROKE = Color('#757575')
COLOR_TEXT_FIELD_GREY = Color('#E5E5E5')
COLOR_HIGHLIGHT = Color('#E7E7E7')
PALETTE_CURSOR_DISTANCE = zoom(10)
TOOLBAR_ARROW_SIZE = zoom(24)
+91
View File
@@ -0,0 +1,91 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import gobject
import gtk
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
class ToggleToolButton(gtk.ToggleToolButton):
__gtype_name__ = 'SugarToggleToolButton'
def __init__(self, named_icon=None):
gtk.ToggleToolButton.__init__(self)
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)
icon.show()
def create_palette(self):
return None
def get_palette(self):
return self._palette_invoker.palette
def set_palette(self, palette):
self._palette_invoker.palette = palette
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = gobject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
allocation = self.get_allocation()
child = self.get_child()
if self.palette and self.palette.is_up():
invoker = self.palette.props.invoker
invoker.draw_rectangle(event, self.palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, 'toolbutton-prelight',
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.ToggleToolButton.do_expose_event(self, event)
palette = property(get_palette, set_palette)
+332
View File
@@ -0,0 +1,332 @@
# Copyright (C) 2009, Aleksey Lim
#
# 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 gtk
import gobject
from sugar.graphics import style
from sugar.graphics.palette import PaletteWindow, ToolInvoker
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics import palettegroup
class ToolbarButton(ToolButton):
def __init__(self, page=None, **kwargs):
ToolButton.__init__(self, **kwargs)
self.page_widget = None
self.set_page(page)
self.connect('clicked',
lambda widget: self.set_expanded(not self.is_expanded()))
self.connect('hierarchy-changed', self.__hierarchy_changed_cb)
def __hierarchy_changed_cb(self, tool_button, previous_toplevel):
if hasattr(self.parent, 'owner'):
if self.page_widget and previous_toplevel is None:
self._unparent()
self.parent.owner.pack_start(self.page_widget)
self.set_expanded(False)
def get_toolbar_box(self):
if not hasattr(self.parent, 'owner'):
return None
return self.parent.owner
toolbar_box = property(get_toolbar_box)
def get_page(self):
if self.page_widget is None:
return None
return _get_embedded_page(self.page_widget)
def set_page(self, page):
if page is None:
self.page_widget = None
return
self.page_widget, alignment_ = _embed_page(_Box, page)
self.page_widget.set_size_request(-1, style.GRID_CELL_SIZE)
page.show()
if self.props.palette is None:
self.props.palette = _ToolbarPalette(invoker=ToolInvoker(self))
self._move_page_to_palette()
page = gobject.property(type=object, getter=get_page, setter=set_page)
def is_in_palette(self):
return self.page is not None and \
self.page_widget.parent == self.props.palette
def is_expanded(self):
return self.page is not None and \
not self.is_in_palette()
def popdown(self):
if self.props.palette is not None:
self.props.palette.popdown(immediate=True)
def set_expanded(self, expanded):
self.popdown()
if self.page is None or self.is_expanded() == expanded:
return
if not expanded:
self._move_page_to_palette()
return
box = self.toolbar_box
if box.expanded_button is not None:
if box.expanded_button.window is not None:
# need to redraw it to erase arrow
box.expanded_button.window.invalidate_rect(None, True)
box.expanded_button.set_expanded(False)
box.expanded_button = self
self._unparent()
self.modify_bg(gtk.STATE_NORMAL, box.background)
_setup_page(self.page_widget, box.background, box.props.padding)
box.pack_start(self.page_widget)
def _move_page_to_palette(self):
if self.is_in_palette():
return
self._unparent()
if isinstance(self.props.palette, _ToolbarPalette):
self.props.palette.add(self.page_widget)
def _unparent(self):
if self.page_widget.parent is None:
return
self.page_widget.parent.remove(self.page_widget)
def do_expose_event(self, event):
if not self.is_expanded() or self.props.palette is not None and \
self.props.palette.is_up():
ToolButton.do_expose_event(self, event)
_paint_arrow(self, event, gtk.ARROW_DOWN)
return
alloc = self.allocation
self.get_style().paint_box(event.window,
gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
'palette-invoker', alloc.x, 0,
alloc.width, alloc.height + style.FOCUS_LINE_WIDTH)
if self.child.state != gtk.STATE_PRELIGHT:
self.get_style().paint_box(event.window,
gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
alloc.x + style.FOCUS_LINE_WIDTH, style.FOCUS_LINE_WIDTH,
alloc.width - style.FOCUS_LINE_WIDTH * 2, alloc.height)
gtk.ToolButton.do_expose_event(self, event)
_paint_arrow(self, event, gtk.ARROW_UP)
class ToolbarBox(gtk.VBox):
def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING):
gtk.VBox.__init__(self)
self._expanded_button_index = -1
self.background = None
self._toolbar = gtk.Toolbar()
self._toolbar.owner = self
self._toolbar.connect('remove', self.__remove_cb)
self._toolbar_widget, self._toolbar_alignment = \
_embed_page(gtk.EventBox, self._toolbar)
self.pack_start(self._toolbar_widget)
self.props.padding = padding
self.modify_bg(gtk.STATE_NORMAL,
style.COLOR_TOOLBAR_GREY.get_gdk_color())
def get_toolbar(self):
return self._toolbar
toolbar = property(get_toolbar)
def get_expanded_button(self):
if self._expanded_button_index == -1:
return None
return self.toolbar.get_nth_item(self._expanded_button_index)
def set_expanded_button(self, button):
if not button in self.toolbar:
self._expanded_button_index = -1
return
self._expanded_button_index = self.toolbar.get_item_index(button)
expanded_button = property(get_expanded_button, set_expanded_button)
def get_padding(self):
return self._toolbar_alignment.props.left_padding
def set_padding(self, pad):
self._toolbar_alignment.set_padding(0, 0, pad, pad)
padding = gobject.property(type=object,
getter=get_padding, setter=set_padding)
def modify_bg(self, state, color):
if state == gtk.STATE_NORMAL:
self.background = color
self._toolbar_widget.modify_bg(state, color)
self.toolbar.modify_bg(state, color)
def __remove_cb(self, sender, button):
if not isinstance(button, ToolbarButton):
return
button.popdown()
if button == self.expanded_button:
self.remove(button.page_widget)
self._expanded_button_index = -1
class _ToolbarPalette(PaletteWindow):
def __init__(self, **kwargs):
PaletteWindow.__init__(self, **kwargs)
self.set_border_width(0)
self._has_focus = False
group = palettegroup.get_group('default')
group.connect('popdown', self.__group_popdown_cb)
self.set_group_id('toolbarbox')
def get_expanded_button(self):
return self.invoker.parent
expanded_button = property(get_expanded_button)
def on_invoker_enter(self):
PaletteWindow.on_invoker_enter(self)
self._set_focus(True)
def on_invoker_leave(self):
PaletteWindow.on_invoker_leave(self)
self._set_focus(False)
def on_enter(self, event):
PaletteWindow.on_enter(self, event)
self._set_focus(True)
def on_leave(self, event):
PaletteWindow.on_enter(self, event)
self._set_focus(False)
def _set_focus(self, new_focus):
self._has_focus = new_focus
if not self._has_focus:
group = palettegroup.get_group('default')
if not group.is_up():
self.popdown()
def do_size_request(self, requisition):
gtk.Window.do_size_request(self, requisition)
requisition.width = max(requisition.width,
gtk.gdk.screen_width())
def popup(self, immediate=False):
button = self.expanded_button
if button.is_expanded():
return
box = button.toolbar_box
_setup_page(button.page_widget, style.COLOR_BLACK.get_gdk_color(),
box.props.padding)
PaletteWindow.popup(self, immediate)
def __group_popdown_cb(self, group):
if not self._has_focus:
self.popdown(immediate=True)
class _Box(gtk.EventBox):
def __init__(self):
gtk.EventBox.__init__(self)
self.connect('expose-event', self.do_expose_event)
self.set_app_paintable(True)
def do_expose_event(self, widget, event):
if self.parent.expanded_button is None:
return
alloc = self.parent.expanded_button.allocation
self.get_style().paint_box(event.window,
gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
'palette-invoker', -style.FOCUS_LINE_WIDTH, 0,
self.allocation.width + style.FOCUS_LINE_WIDTH * 2,
self.allocation.height + style.FOCUS_LINE_WIDTH)
self.get_style().paint_box(event.window,
gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
alloc.x + style.FOCUS_LINE_WIDTH, 0,
alloc.width - style.FOCUS_LINE_WIDTH * 2,
style.FOCUS_LINE_WIDTH)
def _setup_page(page_widget, color, hpad):
vpad = style.FOCUS_LINE_WIDTH
page_widget.child.set_padding(vpad, vpad, hpad, hpad)
page = _get_embedded_page(page_widget)
page.modify_bg(gtk.STATE_NORMAL, color)
if isinstance(page, gtk.Container):
for i in page.get_children():
i.modify_bg(gtk.STATE_INSENSITIVE, color)
page_widget.modify_bg(gtk.STATE_NORMAL, color)
page_widget.modify_bg(gtk.STATE_PRELIGHT, color)
def _embed_page(box_class, page):
page.show()
alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
alignment.add(page)
alignment.show()
page_widget = box_class()
page_widget.modify_bg(gtk.STATE_ACTIVE,
style.COLOR_BUTTON_GREY.get_gdk_color())
page_widget.add(alignment)
page_widget.show()
return (page_widget, alignment)
def _get_embedded_page(page_widget):
return page_widget.child.child
def _paint_arrow(widget, event, arrow_type):
alloc = widget.allocation
x = alloc.x + alloc.width / 2 - style.TOOLBAR_ARROW_SIZE / 2
y = alloc.y + alloc.height - int(style.TOOLBAR_ARROW_SIZE * .85)
widget.get_style().paint_arrow(event.window,
gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, widget,
None, arrow_type, True,
x, y, style.TOOLBAR_ARROW_SIZE, style.TOOLBAR_ARROW_SIZE)
+96
View File
@@ -0,0 +1,96 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import gtk
import gobject
from sugar.graphics import style
class Toolbox(gtk.VBox):
__gtype_name__ = 'SugarToolbox'
__gsignals__ = {
'current-toolbar-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([int])),
}
def __init__(self):
gtk.VBox.__init__(self)
self._notebook = gtk.Notebook()
self._notebook.set_tab_pos(gtk.POS_BOTTOM)
self._notebook.set_show_border(False)
self._notebook.set_show_tabs(False)
self._notebook.props.tab_vborder = style.TOOLBOX_TAB_VBORDER
self._notebook.props.tab_hborder = style.TOOLBOX_TAB_HBORDER
self.pack_start(self._notebook)
self._notebook.show()
self._separator = gtk.HSeparator()
self._separator.modify_bg(gtk.STATE_NORMAL,
style.COLOR_PANEL_GREY.get_gdk_color())
self._separator.set_size_request(1, style.TOOLBOX_SEPARATOR_HEIGHT)
self.pack_start(self._separator, False)
self._notebook.connect('notify::page', self._notify_page_cb)
def _notify_page_cb(self, notebook, pspec):
self.emit('current-toolbar-changed', notebook.props.page)
def add_toolbar(self, name, toolbar):
label = gtk.Label(name)
width, height_ = label.size_request()
label.set_size_request(max(width, style.TOOLBOX_TAB_LABEL_WIDTH), -1)
label.set_alignment(0.0, 0.5)
event_box = gtk.EventBox()
alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
alignment.set_padding(0, 0, style.TOOLBOX_HORIZONTAL_PADDING,
style.TOOLBOX_HORIZONTAL_PADDING)
alignment.add(toolbar)
event_box.add(alignment)
alignment.show()
event_box.show()
self._notebook.append_page(event_box, label)
if self._notebook.get_n_pages() > 1:
self._notebook.set_show_tabs(True)
self._separator.show()
def remove_toolbar(self, index):
self._notebook.remove_page(index)
if self._notebook.get_n_pages() < 2:
self._notebook.set_show_tabs(False)
self._separator.hide()
def set_current_toolbar(self, index):
self._notebook.set_current_page(index)
def get_current_toolbar(self):
return self._notebook.get_current_page()
current_toolbar = property(get_current_toolbar, set_current_toolbar)
+162
View File
@@ -0,0 +1,162 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2008, One Laptop Per Child
#
# 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.
"""
STABLE.
"""
import logging
import gtk
import gobject
from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, ToolInvoker
def _add_accelerator(tool_button):
if not tool_button.props.accelerator or not tool_button.get_toplevel() or \
not tool_button.child:
return
# TODO: should we remove the accelerator from the prev top level?
accel_group = tool_button.get_toplevel().get_data('sugar-accel-group')
if not accel_group:
logging.warning('No gtk.AccelGroup in the top level window.')
return
keyval, mask = gtk.accelerator_parse(tool_button.props.accelerator)
# the accelerator needs to be set at the child, so the gtk.AccelLabel
# in the palette can pick it up.
tool_button.child.add_accelerator('clicked', accel_group, keyval, mask,
gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
def _hierarchy_changed_cb(tool_button, previous_toplevel):
_add_accelerator(tool_button)
def setup_accelerator(tool_button):
_add_accelerator(tool_button)
tool_button.connect('hierarchy-changed', _hierarchy_changed_cb)
class ToolButton(gtk.ToolButton):
__gtype_name__ = 'SugarToolButton'
def __init__(self, icon_name=None, **kwargs):
self._accelerator = None
self._tooltip = None
self._palette_invoker = ToolInvoker()
gobject.GObject.__init__(self, **kwargs)
self._palette_invoker.attach_tool(self)
if icon_name:
self.set_icon(icon_name)
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.
"""
if self.palette is None or self._tooltip is None:
self.palette = Palette(tooltip)
elif self.palette is not None:
self.palette.set_primary_text(tooltip)
self._tooltip = tooltip
# Set label, shows up when toolbar overflows
gtk.ToolButton.set_label(self, tooltip)
def get_tooltip(self):
return self._tooltip
tooltip = gobject.property(type=str, setter=set_tooltip,
getter=get_tooltip)
def set_accelerator(self, accelerator):
self._accelerator = accelerator
setup_accelerator(self)
def get_accelerator(self):
return self._accelerator
accelerator = gobject.property(type=str, setter=set_accelerator,
getter=get_accelerator)
def set_icon(self, icon_name):
icon = Icon(icon_name=icon_name)
self.set_icon_widget(icon)
icon.show()
def create_palette(self):
return None
def get_palette(self):
return self._palette_invoker.palette
def set_palette(self, palette):
self._palette_invoker.palette = palette
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = gobject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def do_expose_event(self, event):
child = self.get_child()
allocation = self.get_allocation()
if self.palette and self.palette.is_up():
invoker = self.palette.props.invoker
invoker.draw_rectangle(event, self.palette)
elif child.state == gtk.STATE_PRELIGHT:
child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area,
child, 'toolbutton-prelight',
allocation.x, allocation.y,
allocation.width, allocation.height)
gtk.ToolButton.do_expose_event(self, event)
def do_clicked(self):
if self.palette:
self.palette.popdown(True)
+64
View File
@@ -0,0 +1,64 @@
# Copyright (C) 2007, 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.
"""
STABLE.
"""
import gtk
import gobject
from sugar.graphics.combobox import ComboBox
from sugar.graphics import style
class ToolComboBox(gtk.ToolItem):
__gproperties__ = {
'label-text': (str, None, None, None, gobject.PARAM_WRITABLE),
}
def __init__(self, combo=None, **kwargs):
self.label = None
self._label_text = ''
gobject.GObject.__init__(self, **kwargs)
self.set_border_width(style.DEFAULT_PADDING)
hbox = gtk.HBox(False, style.DEFAULT_SPACING)
self.label = gtk.Label(self._label_text)
hbox.pack_start(self.label, False)
self.label.show()
if combo:
self.combo = combo
else:
self.combo = ComboBox()
hbox.pack_start(self.combo)
self.combo.show()
self.add(hbox)
hbox.show()
def do_set_property(self, pspec, value):
if pspec.name == 'label-text':
self._label_text = value
if self.label:
self.label.set_text(self._label_text)
+468
View File
@@ -0,0 +1,468 @@
# Copyright (C) 2007, One Laptop Per Child
#
# 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.
"""
STABLE.
"""
import gobject
import gtk
from sugar.graphics import style
from sugar.graphics.palette import ToolInvoker
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.icon import Icon
_PREVIOUS_PAGE = 0
_NEXT_PAGE = 1
class _TrayViewport(gtk.Viewport):
__gproperties__ = {
'scrollable': (bool, None, None, False, gobject.PARAM_READABLE),
'can-scroll-prev': (bool, None, None, False, gobject.PARAM_READABLE),
'can-scroll-next': (bool, None, None, False, gobject.PARAM_READABLE),
}
def __init__(self, orientation):
self.orientation = orientation
self._scrollable = False
self._can_scroll_next = False
self._can_scroll_prev = False
gobject.GObject.__init__(self)
self.set_shadow_type(gtk.SHADOW_NONE)
self.traybar = gtk.Toolbar()
self.traybar.set_orientation(orientation)
self.traybar.set_show_arrow(False)
self.add(self.traybar)
self.traybar.show()
self.connect('size_allocate', self._size_allocate_cb)
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
else:
adj = self.get_vadjustment()
adj.connect('changed', self._adjustment_changed_cb)
adj.connect('value-changed', self._adjustment_changed_cb)
def scroll(self, direction):
if direction == _PREVIOUS_PAGE:
self._scroll_previous()
elif direction == _NEXT_PAGE:
self._scroll_next()
def scroll_to_item(self, item):
"""This function scrolls the viewport so that item will be visible."""
assert item in self.traybar.get_children()
# Get the allocation, and make sure that it is visible
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
start = item.allocation.x
stop = item.allocation.x + item.allocation.width
else:
adj = self.get_vadjustment()
start = item.allocation.y
stop = item.allocation.y + item.allocation.height
if start < adj.value:
adj.value = start
elif stop > adj.value + adj.page_size:
adj.value = stop - adj.page_size
def _scroll_next(self):
allocation = self.get_allocation()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value + allocation.width
adj.value = min(new_value, adj.upper - allocation.width)
else:
adj = self.get_vadjustment()
new_value = adj.value + allocation.height
adj.value = min(new_value, adj.upper - allocation.height)
def _scroll_previous(self):
allocation = self.get_allocation()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
adj = self.get_hadjustment()
new_value = adj.value - allocation.width
adj.value = max(adj.lower, new_value)
else:
adj = self.get_vadjustment()
new_value = adj.value - allocation.height
adj.value = max(adj.lower, new_value)
def do_size_request(self, requisition):
child_requisition = self.get_child().size_request()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
requisition[0] = 0
requisition[1] = child_requisition[1]
else:
requisition[0] = child_requisition[0]
requisition[1] = 0
def do_get_property(self, pspec):
if pspec.name == 'scrollable':
return self._scrollable
elif pspec.name == 'can-scroll-next':
return self._can_scroll_next
elif pspec.name == 'can-scroll-prev':
return self._can_scroll_prev
def _size_allocate_cb(self, viewport, allocation):
bar_requisition = self.traybar.get_child_requisition()
if self.orientation == gtk.ORIENTATION_HORIZONTAL:
scrollable = bar_requisition[0] > allocation.width
else:
scrollable = bar_requisition[1] > allocation.height
if scrollable != self._scrollable:
self._scrollable = scrollable
self.notify('scrollable')
def _adjustment_changed_cb(self, adjustment):
if adjustment.value <= adjustment.lower:
can_scroll_prev = False
else:
can_scroll_prev = True
if adjustment.value + adjustment.page_size >= adjustment.upper:
can_scroll_next = False
else:
can_scroll_next = True
if can_scroll_prev != self._can_scroll_prev:
self._can_scroll_prev = can_scroll_prev
self.notify('can-scroll-prev')
if can_scroll_next != self._can_scroll_next:
self._can_scroll_next = can_scroll_next
self.notify('can-scroll-next')
class _TrayScrollButton(ToolButton):
def __init__(self, icon_name, scroll_direction):
ToolButton.__init__(self)
self._viewport = None
self._scroll_direction = scroll_direction
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
self.icon = Icon(icon_name=icon_name,
icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
# The alignment is a hack to work around gtk.ToolButton code
# that sets the icon_size when the icon_widget is a gtk.Image
alignment = gtk.Alignment(0.5, 0.5)
alignment.add(self.icon)
self.set_icon_widget(alignment)
alignment.show_all()
self.connect('clicked', self._clicked_cb)
def set_viewport(self, viewport):
self._viewport = viewport
self._viewport.connect('notify::scrollable',
self._viewport_scrollable_changed_cb)
if self._scroll_direction == _PREVIOUS_PAGE:
self._viewport.connect('notify::can-scroll-prev',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_prev)
else:
self._viewport.connect('notify::can-scroll-next',
self._viewport_can_scroll_dir_changed_cb)
self.set_sensitive(self._viewport.props.can_scroll_next)
def _viewport_scrollable_changed_cb(self, viewport, pspec):
self.props.visible = self._viewport.props.scrollable
def _viewport_can_scroll_dir_changed_cb(self, viewport, pspec):
if self._scroll_direction == _PREVIOUS_PAGE:
sensitive = self._viewport.props.can_scroll_prev
else:
sensitive = self._viewport.props.can_scroll_next
self.set_sensitive(sensitive)
def _clicked_cb(self, button):
self._viewport.scroll(self._scroll_direction)
viewport = property(fset=set_viewport)
ALIGN_TO_START = 0
ALIGN_TO_END = 1
class HTray(gtk.HBox):
__gtype_name__ = 'SugarHTray'
__gproperties__ = {
'align': (int, None, None, 0, 1, ALIGN_TO_START,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
'drag-active': (bool, None, None, False, gobject.PARAM_READWRITE),
}
def __init__(self, **kwargs):
self._drag_active = False
self.align = ALIGN_TO_START
gobject.GObject.__init__(self, **kwargs)
scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
self.pack_start(scroll_left, False)
self._viewport = _TrayViewport(gtk.ORIENTATION_HORIZONTAL)
self.pack_start(self._viewport)
self._viewport.show()
scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
self.pack_start(scroll_right, False)
scroll_left.viewport = self._viewport
scroll_right.viewport = self._viewport
if self.align == ALIGN_TO_END:
spacer = gtk.SeparatorToolItem()
spacer.set_size_request(0, 0)
spacer.props.draw = False
spacer.set_expand(True)
self._viewport.traybar.insert(spacer, 0)
spacer.show()
def do_set_property(self, pspec, value):
if pspec.name == 'align':
self.align = value
elif pspec.name == 'drag-active':
self._set_drag_active(value)
else:
raise AssertionError
def do_get_property(self, pspec):
if pspec.name == 'align':
return self.align
elif pspec.name == 'drag-active':
return self._drag_active
else:
raise AssertionError
def _set_drag_active(self, active):
if self._drag_active != active:
self._drag_active = active
if self._drag_active:
self._viewport.traybar.modify_bg(gtk.STATE_NORMAL,
style.COLOR_BLACK.get_gdk_color())
else:
self._viewport.traybar.modify_bg(gtk.STATE_NORMAL, None)
def get_children(self):
children = self._viewport.traybar.get_children()[:]
if self.align == ALIGN_TO_END:
children = children[1:]
return children
def add_item(self, item, index=-1):
if self.align == ALIGN_TO_END and index > -1:
index += 1
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
index = self._viewport.traybar.get_item_index(item)
if self.align == ALIGN_TO_END:
index -= 1
return index
def scroll_to_item(self, item):
self._viewport.scroll_to_item(item)
class VTray(gtk.VBox):
__gtype_name__ = 'SugarVTray'
__gproperties__ = {
'align': (int, None, None, 0, 1, ALIGN_TO_START,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
'drag-active': (bool, None, None, False, gobject.PARAM_READWRITE),
}
def __init__(self, **kwargs):
self._drag_active = False
self.align = ALIGN_TO_START
gobject.GObject.__init__(self, **kwargs)
scroll_up = _TrayScrollButton('go-up', _PREVIOUS_PAGE)
self.pack_start(scroll_up, False)
self._viewport = _TrayViewport(gtk.ORIENTATION_VERTICAL)
self.pack_start(self._viewport)
self._viewport.show()
scroll_down = _TrayScrollButton('go-down', _NEXT_PAGE)
self.pack_start(scroll_down, False)
scroll_up.viewport = self._viewport
scroll_down.viewport = self._viewport
if self.align == ALIGN_TO_END:
spacer = gtk.SeparatorToolItem()
spacer.set_size_request(0, 0)
spacer.props.draw = False
spacer.set_expand(True)
self._viewport.traybar.insert(spacer, 0)
spacer.show()
def do_set_property(self, pspec, value):
if pspec.name == 'align':
self.align = value
elif pspec.name == 'drag-active':
self._set_drag_active(value)
else:
raise AssertionError
def do_get_property(self, pspec):
if pspec.name == 'align':
return self.align
elif pspec.name == 'drag-active':
return self._drag_active
else:
raise AssertionError
def _set_drag_active(self, active):
if self._drag_active != active:
self._drag_active = active
if self._drag_active:
self._viewport.traybar.modify_bg(gtk.STATE_NORMAL,
style.COLOR_BLACK.get_gdk_color())
else:
self._viewport.traybar.modify_bg(gtk.STATE_NORMAL, None)
def get_children(self):
children = self._viewport.traybar.get_children()[:]
if self.align == ALIGN_TO_END:
children = children[1:]
return children
def add_item(self, item, index=-1):
if self.align == ALIGN_TO_END and index > -1:
index += 1
self._viewport.traybar.insert(item, index)
def remove_item(self, item):
self._viewport.traybar.remove(item)
def get_item_index(self, item):
index = self._viewport.traybar.get_item_index(item)
if self.align == ALIGN_TO_END:
index -= 1
return index
def scroll_to_item(self, item):
self._viewport.scroll_to_item(item)
class TrayButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, **kwargs)
class _IconWidget(gtk.EventBox):
__gtype_name__ = 'SugarTrayIconWidget'
def __init__(self, icon_name=None, xo_color=None):
gtk.EventBox.__init__(self)
self.set_app_paintable(True)
self._icon = Icon(icon_name=icon_name, xo_color=xo_color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self.add(self._icon)
self._icon.show()
def do_expose_event(self, event):
palette = self.parent.palette
if palette and palette.is_up():
invoker = palette.props.invoker
invoker.draw_rectangle(event, palette)
gtk.EventBox.do_expose_event(self, event)
def get_icon(self):
return self._icon
class TrayIcon(gtk.ToolItem):
__gtype_name__ = 'SugarTrayIcon'
def __init__(self, icon_name=None, xo_color=None):
gtk.ToolItem.__init__(self)
self._icon_widget = _IconWidget(icon_name, xo_color)
self.add(self._icon_widget)
self._icon_widget.show()
self._palette_invoker = ToolInvoker(self)
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
def get_palette(self):
return self._palette_invoker.palette
def set_palette(self, palette):
self._palette_invoker.palette = palette
palette = gobject.property(
type=object, setter=set_palette, getter=get_palette)
def get_palette_invoker(self):
return self._palette_invoker
def set_palette_invoker(self, palette_invoker):
self._palette_invoker.detach()
self._palette_invoker = palette_invoker
palette_invoker = gobject.property(
type=object, setter=set_palette_invoker, getter=get_palette_invoker)
def get_icon(self):
return self._icon_widget.get_icon()
icon = property(get_icon, None)
+297
View File
@@ -0,0 +1,297 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2009, Aleksey Lim, Sayamindu Dasgupta
#
# 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.
"""
STABLE.
"""
import gobject
import gtk
import warnings
from sugar.graphics.icon import Icon
from sugar.graphics import palettegroup
_UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT = 2
class UnfullscreenButton(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_decorated(False)
self.set_resizable(False)
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.set_border_width(0)
self.props.accept_focus = False
#Setup estimate of width, height
w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)
self._width = w
self._height = h
self.connect('size-request', self._size_request_cb)
screen = self.get_screen()
screen.connect('size-changed', self._screen_size_changed_cb)
self._button = gtk.Button()
self._button.set_relief(gtk.RELIEF_NONE)
self._icon = Icon(icon_name='view-return',
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
self._icon.show()
self._button.add(self._icon)
self._button.show()
self.add(self._button)
def connect_button_press(self, cb):
self._button.connect('button-press-event', cb)
def _reposition(self):
x = gtk.gdk.screen_width() - self._width
self.move(x, 0)
def _size_request_cb(self, widget, req):
self._width = req.width
self._height = req.height
self._reposition()
def _screen_size_changed_cb(self, screen):
self._reposition()
class Window(gtk.Window):
def __init__(self, **args):
self._enable_fullscreen_mode = True
gtk.Window.__init__(self, **args)
self.set_decorated(False)
self.maximize()
self.connect('realize', self.__window_realize_cb)
self.connect('key-press-event', self.__key_press_cb)
self._toolbar_box = None
self._alerts = []
self._canvas = None
self.tray = None
self.__vbox = gtk.VBox()
self.__hbox = gtk.HBox()
self.__vbox.pack_start(self.__hbox)
self.__hbox.show()
self.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK
| gtk.gdk.POINTER_MOTION_MASK)
self.connect('motion-notify-event', self.__motion_notify_cb)
self.add(self.__vbox)
self.__vbox.show()
self._is_fullscreen = False
self._unfullscreen_button = UnfullscreenButton()
self._unfullscreen_button.set_transient_for(self)
self._unfullscreen_button.connect_button_press(
self.__unfullscreen_button_pressed)
self._unfullscreen_button_timeout_id = None
def reveal(self):
""" Make window active
In contrast with present(), brings window to the top
even after invoking on response on non-gtk events.
See #1423.
"""
if self.window is None:
self.show()
return
timestamp = gtk.get_current_event_time()
if not timestamp:
timestamp = gtk.gdk.x11_get_server_time(self.window)
self.window.focus(timestamp)
def fullscreen(self):
palettegroup.popdown_all()
if self._toolbar_box is not None:
self._toolbar_box.hide()
if self.tray is not None:
self.tray.hide()
self._is_fullscreen = True
if self.props.enable_fullscreen_mode:
self._unfullscreen_button.show()
if self._unfullscreen_button_timeout_id is not None:
gobject.source_remove(self._unfullscreen_button_timeout_id)
self._unfullscreen_button_timeout_id = None
self._unfullscreen_button_timeout_id = \
gobject.timeout_add_seconds( \
_UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \
self.__unfullscreen_button_timeout_cb)
def unfullscreen(self):
if self._toolbar_box is not None:
self._toolbar_box.show()
if self.tray is not None:
self.tray.show()
self._is_fullscreen = False
if self.props.enable_fullscreen_mode:
self._unfullscreen_button.hide()
if self._unfullscreen_button_timeout_id:
gobject.source_remove(self._unfullscreen_button_timeout_id)
self._unfullscreen_button_timeout_id = None
def set_canvas(self, canvas):
if self._canvas:
self.__hbox.remove(self._canvas)
if canvas:
self.__hbox.pack_start(canvas)
self._canvas = canvas
self.__vbox.set_focus_child(self._canvas)
def get_canvas(self):
return self._canvas
canvas = property(get_canvas, set_canvas)
def get_toolbar_box(self):
return self._toolbar_box
def set_toolbar_box(self, toolbar_box):
if self._toolbar_box:
self.__vbox.remove(self._toolbar_box)
if toolbar_box:
self.__vbox.pack_start(toolbar_box, False)
self.__vbox.reorder_child(toolbar_box, 0)
self._toolbar_box = toolbar_box
toolbar_box = property(get_toolbar_box, set_toolbar_box)
def set_tray(self, tray, position):
if self.tray:
box = self.tray.get_parent()
box.remove(self.tray)
if position == gtk.POS_LEFT:
self.__hbox.pack_start(tray, False)
elif position == gtk.POS_RIGHT:
self.__hbox.pack_end(tray, False)
elif position == gtk.POS_BOTTOM:
self.__vbox.pack_end(tray, False)
self.tray = tray
def add_alert(self, alert):
self._alerts.append(alert)
if len(self._alerts) == 1:
self.__vbox.pack_start(alert, False)
if self._toolbar_box is not None:
self.__vbox.reorder_child(alert, 1)
else:
self.__vbox.reorder_child(alert, 0)
def remove_alert(self, alert):
if alert in self._alerts:
self._alerts.remove(alert)
# if the alert is the visible one on top of the queue
if alert.get_parent() is not None:
self.__vbox.remove(alert)
if len(self._alerts) >= 1:
self.__vbox.pack_start(self._alerts[0], False)
if self._toolbar_box is not None:
self.__vbox.reorder_child(self._alerts[0], 1)
else:
self.__vbox.reorder_child(self._alert[0], 0)
def __window_realize_cb(self, window):
group = gtk.Window()
group.realize()
window.window.set_group(group.window)
def __key_press_cb(self, widget, event):
key = gtk.gdk.keyval_name(event.keyval)
if event.state & gtk.gdk.MOD1_MASK:
if self.tray is not None and key == 'space':
self.tray.props.visible = not self.tray.props.visible
return True
elif key == 'Escape' and self._is_fullscreen and \
self.props.enable_fullscreen_mode:
self.unfullscreen()
return True
return False
def __unfullscreen_button_pressed(self, widget, event):
self.unfullscreen()
def __motion_notify_cb(self, widget, event):
if self._is_fullscreen and self.props.enable_fullscreen_mode:
if not self._unfullscreen_button.props.visible:
self._unfullscreen_button.show()
else:
# Reset the timer
if self._unfullscreen_button_timeout_id is not None:
gobject.source_remove(self._unfullscreen_button_timeout_id)
self._unfullscreen_button_timeout_id = None
self._unfullscreen_button_timeout_id = \
gobject.timeout_add_seconds( \
_UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \
self.__unfullscreen_button_timeout_cb)
return False
def __unfullscreen_button_timeout_cb(self):
self._unfullscreen_button.hide()
self._unfullscreen_button_timeout_id = None
return False
def set_enable_fullscreen_mode(self, enable_fullscreen_mode):
self._enable_fullscreen_mode = enable_fullscreen_mode
def get_enable_fullscreen_mode(self):
return self._enable_fullscreen_mode
enable_fullscreen_mode = gobject.property(type=object,
setter=set_enable_fullscreen_mode, getter=get_enable_fullscreen_mode)
# DEPRECATED
def set_toolbox(self, toolbar_box):
warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
self.set_toolbar_box(toolbar_box)
def get_toolbox(self):
warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
return self._toolbar_box
toolbox = property(get_toolbox, set_toolbox)
+282
View File
@@ -0,0 +1,282 @@
# Copyright (C) 2006-2007 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.
"""
STABLE.
"""
import random
import logging
import gconf
colors = [
['#B20008', '#FF2B34'], \
['#FF2B34', '#B20008'], \
['#E6000A', '#FF2B34'], \
['#FF2B34', '#E6000A'], \
['#FFADCE', '#FF2B34'], \
['#9A5200', '#FF2B34'], \
['#FF2B34', '#9A5200'], \
['#FF8F00', '#FF2B34'], \
['#FF2B34', '#FF8F00'], \
['#FFC169', '#FF2B34'], \
['#807500', '#FF2B34'], \
['#FF2B34', '#807500'], \
['#BE9E00', '#FF2B34'], \
['#FF2B34', '#BE9E00'], \
['#F8E800', '#FF2B34'], \
['#008009', '#FF2B34'], \
['#FF2B34', '#008009'], \
['#00B20D', '#FF2B34'], \
['#FF2B34', '#00B20D'], \
['#8BFF7A', '#FF2B34'], \
['#00588C', '#FF2B34'], \
['#FF2B34', '#00588C'], \
['#005FE4', '#FF2B34'], \
['#FF2B34', '#005FE4'], \
['#BCCDFF', '#FF2B34'], \
['#5E008C', '#FF2B34'], \
['#FF2B34', '#5E008C'], \
['#7F00BF', '#FF2B34'], \
['#FF2B34', '#7F00BF'], \
['#D1A3FF', '#FF2B34'], \
['#9A5200', '#FF8F00'], \
['#FF8F00', '#9A5200'], \
['#C97E00', '#FF8F00'], \
['#FF8F00', '#C97E00'], \
['#FFC169', '#FF8F00'], \
['#807500', '#FF8F00'], \
['#FF8F00', '#807500'], \
['#BE9E00', '#FF8F00'], \
['#FF8F00', '#BE9E00'], \
['#F8E800', '#FF8F00'], \
['#008009', '#FF8F00'], \
['#FF8F00', '#008009'], \
['#00B20D', '#FF8F00'], \
['#FF8F00', '#00B20D'], \
['#8BFF7A', '#FF8F00'], \
['#00588C', '#FF8F00'], \
['#FF8F00', '#00588C'], \
['#005FE4', '#FF8F00'], \
['#FF8F00', '#005FE4'], \
['#BCCDFF', '#FF8F00'], \
['#5E008C', '#FF8F00'], \
['#FF8F00', '#5E008C'], \
['#A700FF', '#FF8F00'], \
['#FF8F00', '#A700FF'], \
['#D1A3FF', '#FF8F00'], \
['#B20008', '#FF8F00'], \
['#FF8F00', '#B20008'], \
['#FF2B34', '#FF8F00'], \
['#FF8F00', '#FF2B34'], \
['#FFADCE', '#FF8F00'], \
['#807500', '#F8E800'], \
['#F8E800', '#807500'], \
['#BE9E00', '#F8E800'], \
['#F8E800', '#BE9E00'], \
['#FFFA00', '#EDDE00'], \
['#008009', '#F8E800'], \
['#F8E800', '#008009'], \
['#00EA11', '#F8E800'], \
['#F8E800', '#00EA11'], \
['#8BFF7A', '#F8E800'], \
['#00588C', '#F8E800'], \
['#F8E800', '#00588C'], \
['#00A0FF', '#F8E800'], \
['#F8E800', '#00A0FF'], \
['#BCCEFF', '#F8E800'], \
['#5E008C', '#F8E800'], \
['#F8E800', '#5E008C'], \
['#AC32FF', '#F8E800'], \
['#F8E800', '#AC32FF'], \
['#D1A3FF', '#F8E800'], \
['#B20008', '#F8E800'], \
['#F8E800', '#B20008'], \
['#FF2B34', '#F8E800'], \
['#F8E800', '#FF2B34'], \
['#FFADCE', '#F8E800'], \
['#9A5200', '#F8E800'], \
['#F8E800', '#9A5200'], \
['#FF8F00', '#F8E800'], \
['#F8E800', '#FF8F00'], \
['#FFC169', '#F8E800'], \
['#008009', '#00EA11'], \
['#00EA11', '#008009'], \
['#00B20D', '#00EA11'], \
['#00EA11', '#00B20D'], \
['#8BFF7A', '#00EA11'], \
['#00588C', '#00EA11'], \
['#00EA11', '#00588C'], \
['#005FE4', '#00EA11'], \
['#00EA11', '#005FE4'], \
['#BCCDFF', '#00EA11'], \
['#5E008C', '#00EA11'], \
['#00EA11', '#5E008C'], \
['#7F00BF', '#00EA11'], \
['#00EA11', '#7F00BF'], \
['#D1A3FF', '#00EA11'], \
['#B20008', '#00EA11'], \
['#00EA11', '#B20008'], \
['#FF2B34', '#00EA11'], \
['#00EA11', '#FF2B34'], \
['#FFADCE', '#00EA11'], \
['#9A5200', '#00EA11'], \
['#00EA11', '#9A5200'], \
['#FF8F00', '#00EA11'], \
['#00EA11', '#FF8F00'], \
['#FFC169', '#00EA11'], \
['#807500', '#00EA11'], \
['#00EA11', '#807500'], \
['#BE9E00', '#00EA11'], \
['#00EA11', '#BE9E00'], \
['#F8E800', '#00EA11'], \
['#00588C', '#00A0FF'], \
['#00A0FF', '#00588C'], \
['#005FE4', '#00A0FF'], \
['#00A0FF', '#005FE4'], \
['#BCCDFF', '#00A0FF'], \
['#5E008C', '#00A0FF'], \
['#00A0FF', '#5E008C'], \
['#9900E6', '#00A0FF'], \
['#00A0FF', '#9900E6'], \
['#D1A3FF', '#00A0FF'], \
['#B20008', '#00A0FF'], \
['#00A0FF', '#B20008'], \
['#FF2B34', '#00A0FF'], \
['#00A0FF', '#FF2B34'], \
['#FFADCE', '#00A0FF'], \
['#9A5200', '#00A0FF'], \
['#00A0FF', '#9A5200'], \
['#FF8F00', '#00A0FF'], \
['#00A0FF', '#FF8F00'], \
['#FFC169', '#00A0FF'], \
['#807500', '#00A0FF'], \
['#00A0FF', '#807500'], \
['#BE9E00', '#00A0FF'], \
['#00A0FF', '#BE9E00'], \
['#F8E800', '#00A0FF'], \
['#008009', '#00A0FF'], \
['#00A0FF', '#008009'], \
['#00B20D', '#00A0FF'], \
['#00A0FF', '#00B20D'], \
['#8BFF7A', '#00A0FF'], \
['#5E008C', '#AC32FF'], \
['#AC32FF', '#5E008C'], \
['#7F00BF', '#AC32FF'], \
['#AC32FF', '#7F00BF'], \
['#D1A3FF', '#AC32FF'], \
['#B20008', '#AC32FF'], \
['#AC32FF', '#B20008'], \
['#FF2B34', '#AC32FF'], \
['#AC32FF', '#FF2B34'], \
['#FFADCE', '#AC32FF'], \
['#9A5200', '#AC32FF'], \
['#AC32FF', '#9A5200'], \
['#FF8F00', '#AC32FF'], \
['#AC32FF', '#FF8F00'], \
['#FFC169', '#AC32FF'], \
['#807500', '#AC32FF'], \
['#AC32FF', '#807500'], \
['#BE9E00', '#AC32FF'], \
['#AC32FF', '#BE9E00'], \
['#F8E800', '#AC32FF'], \
['#008009', '#AC32FF'], \
['#AC32FF', '#008009'], \
['#00B20D', '#AC32FF'], \
['#AC32FF', '#00B20D'], \
['#8BFF7A', '#AC32FF'], \
['#00588C', '#AC32FF'], \
['#AC32FF', '#00588C'], \
['#005FE4', '#AC32FF'], \
['#AC32FF', '#005FE4'], \
['#BCCDFF', '#AC32FF'], \
]
def _parse_string(color_string):
if not isinstance(color_string, (str, unicode)):
logging.error('Invalid color string: %r', color_string)
return None
if color_string == 'white':
return ['#ffffff', '#414141']
elif color_string == 'insensitive':
return ['#ffffff', '#e2e2e2']
splitted = color_string.split(',')
if len(splitted) == 2:
return [splitted[0], splitted[1]]
else:
return None
def is_valid(color_string):
return (_parse_string(color_string) != None)
class XoColor:
def __init__(self, color_string=None):
if color_string == None:
randomize = True
elif not is_valid(color_string):
logging.debug('Color string is not valid: %s, '
'fallback to default', color_string)
client = gconf.client_get_default()
color_string = client.get_string('/desktop/sugar/user/color')
randomize = False
else:
randomize = False
if randomize:
n = int(random.random() * (len(colors) - 1))
[self.stroke, self.fill] = colors[n]
else:
[self.stroke, self.fill] = _parse_string(color_string)
def __cmp__(self, other):
if isinstance(other, XoColor):
if self.stroke == other.stroke and self.fill == other.fill:
return 0
return -1
def get_stroke_color(self):
return self.stroke
def get_fill_color(self):
return self.fill
def to_string(self):
return '%s,%s' % (self.stroke, self.fill)
if __name__ == '__main__':
import sys
import re
f = open(sys.argv[1], 'r')
print 'colors = ['
for line in f.readlines():
match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line)
print "['#%s', '#%s'], \\" % (match.group(2), match.group(1))
print ']'
f.close()
+396
View File
@@ -0,0 +1,396 @@
/* app.c
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <string.h>
#include <sys/wait.h>
#include "gsm-app.h"
enum {
EXITED,
REGISTERED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
enum {
PROP_0,
PROP_DESKTOP_FILE,
PROP_CLIENT_ID,
LAST_PROP
};
static void set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec);
static void get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec);
static void dispose (GObject *object);
static const char *get_basename (GsmApp *app);
static pid_t launch (GsmApp *app, GError **err);
G_DEFINE_TYPE (GsmApp, gsm_app, G_TYPE_OBJECT)
static void
gsm_app_init (GsmApp *app)
{
app->pid = -1;
}
static void
gsm_app_class_init (GsmAppClass *app_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (app_class);
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->dispose = dispose;
app_class->get_basename = get_basename;
app_class->launch = launch;
g_object_class_install_property (object_class,
PROP_DESKTOP_FILE,
g_param_spec_string ("desktop-file",
"Desktop file",
"Freedesktop .desktop file",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_CLIENT_ID,
g_param_spec_string ("client-id",
"Client ID",
"Session management client ID",
NULL,
G_PARAM_READWRITE));
signals[EXITED] =
g_signal_new ("exited",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmAppClass, exited),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[REGISTERED] =
g_signal_new ("registered",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmAppClass, registered),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GsmApp *app = GSM_APP (object);
const char *desktop_file;
char *phase;
GError *error = NULL;
switch (prop_id)
{
case PROP_DESKTOP_FILE:
if (app->desktop_file)
egg_desktop_file_free (app->desktop_file);
desktop_file = g_value_get_string (value);
if (!desktop_file)
{
app->desktop_file = NULL;
break;
}
app->desktop_file = egg_desktop_file_new (desktop_file, &error);
if (!app->desktop_file)
{
g_warning ("Could not parse desktop file %s: %s",
desktop_file, error->message);
g_error_free (error);
break;
}
phase = egg_desktop_file_get_string (app->desktop_file,
"X-GNOME-Autostart-Phase", NULL);
if (phase)
{
if (!strcmp (phase, "Initialization"))
app->phase = GSM_SESSION_PHASE_INITIALIZATION;
else if (!strcmp (phase, "WindowManager"))
app->phase = GSM_SESSION_PHASE_WINDOW_MANAGER;
else if (!strcmp (phase, "Panel"))
app->phase = GSM_SESSION_PHASE_PANEL;
else if (!strcmp (phase, "Desktop"))
app->phase = GSM_SESSION_PHASE_DESKTOP;
else
app->phase = GSM_SESSION_PHASE_APPLICATION;
g_free (phase);
}
else
app->phase = GSM_SESSION_PHASE_APPLICATION;
break;
case PROP_CLIENT_ID:
g_free (app->client_id);
app->client_id = g_value_dup_string (value);
break;
default:
break;
}
}
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GsmApp *app = GSM_APP (object);
switch (prop_id)
{
case PROP_DESKTOP_FILE:
if (app->desktop_file)
g_value_set_string (value, egg_desktop_file_get_source (app->desktop_file));
else
g_value_set_string (value, NULL);
break;
case PROP_CLIENT_ID:
g_value_set_string (value, app->client_id);
break;
default:
break;
}
}
static void
dispose(GObject *object)
{
GsmApp *app = GSM_APP (object);
if (app->desktop_file)
{
egg_desktop_file_free (app->desktop_file);
app->desktop_file = NULL;
}
if (app->startup_id)
{
g_free (app->startup_id);
app->startup_id = NULL;
}
if (app->client_id)
{
g_free (app->client_id);
app->client_id = NULL;
}
}
/**
* gsm_app_get_basename:
* @app: a %GsmApp
*
* Returns an identifying name for @app, e.g. the basename of the path to
* @app's desktop file (if any).
*
* Return value: an identifying name for @app, or %NULL.
**/
const char *
gsm_app_get_basename (GsmApp *app)
{
return GSM_APP_GET_CLASS (app)->get_basename (app);
}
static const char *
get_basename (GsmApp *app)
{
const char *location, *slash;
if (!app->desktop_file)
return NULL;
location = egg_desktop_file_get_source (app->desktop_file);
slash = strrchr (location, '/');
if (slash)
return slash + 1;
else
return location;
}
/**
* gsm_app_get_phase:
* @app: a %GsmApp
*
* Returns @app's startup phase.
*
* Return value: @app's startup phase
**/
GsmSessionPhase
gsm_app_get_phase (GsmApp *app)
{
g_return_val_if_fail (GSM_IS_APP (app), GSM_SESSION_PHASE_APPLICATION);
return app->phase;
}
/**
* gsm_app_is_disabled:
* @app: a %GsmApp
*
* Tests if @app is disabled
*
* Return value: whether or not @app is disabled
**/
gboolean
gsm_app_is_disabled (GsmApp *app)
{
g_return_val_if_fail (GSM_IS_APP (app), FALSE);
if (GSM_APP_GET_CLASS (app)->is_disabled)
return GSM_APP_GET_CLASS (app)->is_disabled (app);
else
return FALSE;
}
gboolean
gsm_app_provides (GsmApp *app, const char *service)
{
char **provides;
gsize len, i;
g_return_val_if_fail (GSM_IS_APP (app), FALSE);
if (!app->desktop_file)
return FALSE;
provides = egg_desktop_file_get_string_list (app->desktop_file,
"X-GNOME-Provides",
&len, NULL);
if (!provides)
return FALSE;
for (i = 0; i < len; i++)
{
if (!strcmp (provides[i], service))
{
g_strfreev (provides);
return TRUE;
}
}
g_strfreev (provides);
return FALSE;
}
static void
app_exited (GPid pid, gint status, gpointer data)
{
if (WIFEXITED (status))
g_signal_emit (GSM_APP (data), signals[EXITED], 0);
}
static pid_t
launch (GsmApp *app,
GError **err)
{
char *env[2] = { NULL, NULL };
gboolean success;
g_return_val_if_fail (app->desktop_file != NULL, (pid_t)-1);
if (egg_desktop_file_get_boolean (app->desktop_file,
"X-GNOME-Autostart-Notify", NULL) ||
egg_desktop_file_get_boolean (app->desktop_file,
"AutostartNotify", NULL))
env[0] = g_strdup_printf ("DESKTOP_AUTOSTART_ID=%s", app->client_id);
#if 0
g_debug ("launching %s with client_id %s\n",
gsm_app_get_basename (app), app->client_id);
#endif
success =
egg_desktop_file_launch (app->desktop_file, NULL, err,
EGG_DESKTOP_FILE_LAUNCH_PUTENV, env,
EGG_DESKTOP_FILE_LAUNCH_FLAGS, G_SPAWN_DO_NOT_REAP_CHILD,
EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, &app->pid,
EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID, &app->startup_id,
NULL);
g_free (env[0]);
if (success)
{
/* In case the app belongs to Initialization phase, we monitor
* if it exits to emit proper "exited" signal to session. */
if (app->phase == GSM_SESSION_PHASE_INITIALIZATION)
g_child_watch_add ((GPid) app->pid, app_exited, app);
return app->pid;
}
else
return (pid_t) -1;
}
/**
* gsm_app_launch:
* @app: a %GsmApp
* @err: an error pointer
*
* Launches @app
*
* Return value: the pid of the new process, or -1 on error
**/
pid_t
gsm_app_launch (GsmApp *app, GError **err)
{
return GSM_APP_GET_CLASS (app)->launch (app, err);
}
/**
* gsm_app_registered:
* @app: a %GsmApp
*
* Emits "registered" signal in @app
**/
void
gsm_app_registered (GsmApp *app)
{
g_return_if_fail (GSM_IS_APP (app));
g_signal_emit (app, signals[REGISTERED], 0);
}
+70
View File
@@ -0,0 +1,70 @@
/* gsmapp.h
* Copyright (C) 2006 Novell, Inc.
*
*/
#ifndef __GSM_APP_H__
#define __GSM_APP_H__
#include <glib-object.h>
#include <sys/types.h>
#include "eggdesktopfile.h"
#include "gsm-session.h"
G_BEGIN_DECLS
#define GSM_TYPE_APP (gsm_app_get_type ())
#define GSM_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_APP, GsmApp))
#define GSM_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_APP, GsmAppClass))
#define GSM_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_APP))
#define GSM_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_APP))
#define GSM_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_APP, GsmAppClass))
typedef struct _GsmApp GsmApp;
typedef struct _GsmAppClass GsmAppClass;
typedef struct _GsmAppPrivate GsmAppPrivate;
struct _GsmApp
{
GObject parent;
EggDesktopFile *desktop_file;
GsmSessionPhase phase;
pid_t pid;
char *startup_id, *client_id;
};
struct _GsmAppClass
{
GObjectClass parent_class;
/* signals */
void (*exited) (GsmApp *app, int status);
void (*registered) (GsmApp *app);
/* virtual methods */
const char *(*get_basename) (GsmApp *app);
gboolean (*is_disabled) (GsmApp *app);
pid_t (*launch) (GsmApp *app, GError **err);
void (*set_client) (GsmApp *app, GsmClient *client);
};
GType gsm_app_get_type (void) G_GNUC_CONST;
const char *gsm_app_get_basename (GsmApp *app);
GsmSessionPhase gsm_app_get_phase (GsmApp *app);
gboolean gsm_app_provides (GsmApp *app,
const char *service);
gboolean gsm_app_is_disabled (GsmApp *app);
pid_t gsm_app_launch (GsmApp *app,
GError **err);
void gsm_app_set_client (GsmApp *app,
GsmClient *client);
void gsm_app_registered (GsmApp *app);
G_END_DECLS
#endif /* __GSM_APP_H__ */
+828
View File
@@ -0,0 +1,828 @@
/* client-xsmp.c
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "gsm-client-xsmp.h"
#include "gsm-session.h"
/* FIXME */
#define GsmDesktopFile "_Gsm_DesktopFile"
static gboolean client_iochannel_watch (GIOChannel *channel,
GIOCondition condition,
gpointer data);
static gboolean client_protocol_timeout (gpointer data);
static void set_description (GsmClientXSMP *xsmp);
static const char *xsmp_get_client_id (GsmClient *client);
static pid_t xsmp_get_pid (GsmClient *client);
static char *xsmp_get_desktop_file (GsmClient *client);
static char *xsmp_get_restart_command (GsmClient *client);
static char *xsmp_get_discard_command (GsmClient *client);
static gboolean xsmp_get_autorestart (GsmClient *client);
static void xsmp_finalize (GObject *object);
static void xsmp_restart (GsmClient *client,
GError **error);
static void xsmp_save_yourself (GsmClient *client,
gboolean save_state);
static void xsmp_save_yourself_phase2 (GsmClient *client);
static void xsmp_interact (GsmClient *client);
static void xsmp_shutdown_cancelled (GsmClient *client);
static void xsmp_die (GsmClient *client);
G_DEFINE_TYPE (GsmClientXSMP, gsm_client_xsmp, GSM_TYPE_CLIENT)
static void
gsm_client_xsmp_init (GsmClientXSMP *xsmp)
{
;
}
static void
gsm_client_xsmp_class_init (GsmClientXSMPClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GsmClientClass *client_class = GSM_CLIENT_CLASS (klass);
object_class->finalize = xsmp_finalize;
client_class->get_client_id = xsmp_get_client_id;
client_class->get_pid = xsmp_get_pid;
client_class->get_desktop_file = xsmp_get_desktop_file;
client_class->get_restart_command = xsmp_get_restart_command;
client_class->get_discard_command = xsmp_get_discard_command;
client_class->get_autorestart = xsmp_get_autorestart;
client_class->restart = xsmp_restart;
client_class->save_yourself = xsmp_save_yourself;
client_class->save_yourself_phase2 = xsmp_save_yourself_phase2;
client_class->interact = xsmp_interact;
client_class->shutdown_cancelled = xsmp_shutdown_cancelled;
client_class->die = xsmp_die;
}
GsmClientXSMP *
gsm_client_xsmp_new (IceConn ice_conn)
{
GsmClientXSMP *xsmp;
GIOChannel *channel;
int fd;
xsmp = g_object_new (GSM_TYPE_CLIENT_XSMP, NULL);
xsmp->props = g_ptr_array_new ();
xsmp->ice_conn = ice_conn;
xsmp->current_save_yourself = -1;
xsmp->next_save_yourself = -1;
fd = IceConnectionNumber (ice_conn);
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
channel = g_io_channel_unix_new (fd);
xsmp->watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
client_iochannel_watch, xsmp);
g_io_channel_unref (channel);
xsmp->protocol_timeout = g_timeout_add_seconds (5, client_protocol_timeout, xsmp);
set_description (xsmp);
g_debug ("New client '%s'", xsmp->description);
return xsmp;
}
static void
xsmp_finalize (GObject *object)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) object;
g_debug ("xsmp_finalize (%s)", xsmp->description);
if (xsmp->watch_id)
g_source_remove (xsmp->watch_id);
if (xsmp->conn)
SmsCleanUp (xsmp->conn);
else
IceCloseConnection (xsmp->ice_conn);
if (xsmp->protocol_timeout)
g_source_remove (xsmp->protocol_timeout);
G_OBJECT_CLASS (gsm_client_xsmp_parent_class)->finalize (object);
}
static gboolean
client_iochannel_watch (GIOChannel *channel,
GIOCondition condition,
gpointer data)
{
GsmClient *client = data;
GsmClientXSMP *xsmp = data;
switch (IceProcessMessages (xsmp->ice_conn, NULL, NULL))
{
case IceProcessMessagesSuccess:
return TRUE;
case IceProcessMessagesIOError:
g_debug ("IceProcessMessagesIOError on '%s'", xsmp->description);
gsm_client_disconnected (client);
return FALSE;
case IceProcessMessagesConnectionClosed:
g_debug ("IceProcessMessagesConnectionClosed on '%s'",
xsmp->description);
return FALSE;
default:
g_assert_not_reached ();
}
}
/* Called if too much time passes between the initial connection and
* the XSMP protocol setup.
*/
static gboolean
client_protocol_timeout (gpointer data)
{
GsmClient *client = data;
GsmClientXSMP *xsmp = data;
g_debug ("client_protocol_timeout for client '%s' in ICE status %d",
xsmp->description, IceConnectionStatus (xsmp->ice_conn));
gsm_client_disconnected (client);
return FALSE;
}
static Status
register_client_callback (SmsConn conn,
SmPointer manager_data,
char *previous_id)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
char *id;
g_debug ("Client '%s' received RegisterClient(%s)",
xsmp->description,
previous_id ? previous_id : "NULL");
id = gsm_session_register_client (global_session, client, previous_id);
if (id == NULL)
{
g_debug (" rejected: invalid previous_id");
free (previous_id);
return FALSE;
}
xsmp->id = id;
set_description (xsmp);
g_debug ("Sending RegisterClientReply to '%s'", xsmp->description);
SmsRegisterClientReply (conn, xsmp->id);
if (!previous_id)
{
/* Send the initial SaveYourself. */
g_debug ("Sending initial SaveYourself");
SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False);
xsmp->current_save_yourself = SmSaveLocal;
free (previous_id);
}
return TRUE;
}
static void
do_save_yourself (GsmClientXSMP *xsmp, int save_type)
{
if (xsmp->next_save_yourself != -1)
{
/* Either we're currently doing a shutdown and there's a checkpoint
* queued after it, or vice versa. Either way, the new SaveYourself
* is redundant.
*/
g_debug (" skipping redundant SaveYourself for '%s'",
xsmp->description);
}
else if (xsmp->current_save_yourself != -1)
{
g_debug (" queuing new SaveYourself for '%s'",
xsmp->description);
xsmp->next_save_yourself = save_type;
}
else
{
xsmp->current_save_yourself = save_type;
switch (save_type)
{
case SmSaveLocal:
/* Save state */
SmsSaveYourself (xsmp->conn, SmSaveLocal, FALSE,
SmInteractStyleNone, FALSE);
break;
default:
/* Logout */
SmsSaveYourself (xsmp->conn, save_type, TRUE,
SmInteractStyleAny, FALSE);
break;
}
}
}
static void
save_yourself_request_callback (SmsConn conn,
SmPointer manager_data,
int save_type,
Bool shutdown,
int interact_style,
Bool fast,
Bool global)
{
GsmClientXSMP *xsmp = manager_data;
g_debug ("Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)",
xsmp->description,
save_type == SmSaveLocal ? "SmSaveLocal" :
save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
shutdown ? "Shutdown" : "!Shutdown",
interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
"SmInteractStyleNone", fast ? "Fast" : "!Fast",
global ? "Global" : "!Global");
/* Examining the g_debug above, you can see that there are a total
* of 72 different combinations of options that this could have been
* called with. However, most of them are stupid.
*
* If @shutdown and @global are both TRUE, that means the caller is
* requesting that a logout message be sent to all clients, so we do
* that. We use @fast to decide whether or not to show a
* confirmation dialog. (This isn't really what @fast is for, but
* the old gnome-session and ksmserver both interpret it that way,
* so we do too.) We ignore @save_type because we pick the correct
* save_type ourselves later based on user prefs, dialog choices,
* etc, and we ignore @interact_style, because clients have not used
* it correctly consistently enough to make it worth honoring.
*
* If @shutdown is TRUE and @global is FALSE, the caller is
* confused, so we ignore the request.
*
* If @shutdown is FALSE and @save_type is SmSaveGlobal or
* SmSaveBoth, then the client wants us to ask some or all open
* applications to save open files to disk, but NOT quit. This is
* silly and so we ignore the request.
*
* If @shutdown is FALSE and @save_type is SmSaveLocal, then the
* client wants us to ask some or all open applications to update
* their current saved state, but not log out. At the moment, the
* code only supports this for the !global case (ie, a client
* requesting that it be allowed to update *its own* saved state,
* but not having everyone else update their saved state).
*/
if (shutdown && global)
{
g_debug (" initiating shutdown");
/* gsm_session_initiate_shutdown (global_session,
!fast,
GSM_SESSION_LOGOUT_TYPE_LOGOUT);
*/
}
else if (!shutdown && !global)
{
g_debug (" initiating checkpoint");
do_save_yourself (xsmp, SmSaveLocal);
}
else
g_debug (" ignoring");
}
static void
xsmp_restart (GsmClient *client, GError **error)
{
char *restart_cmd = gsm_client_get_restart_command (client);
g_spawn_command_line_async (restart_cmd, error);
g_free (restart_cmd);
}
static void
xsmp_save_yourself (GsmClient *client, gboolean save_state)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
g_debug ("xsmp_save_yourself ('%s', %s)", xsmp->description,
save_state ? "True" : "False");
do_save_yourself (xsmp, save_state ? SmSaveBoth : SmSaveGlobal);
}
static void
save_yourself_phase2_request_callback (SmsConn conn,
SmPointer manager_data)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
g_debug ("Client '%s' received SaveYourselfPhase2Request",
xsmp->description);
if (xsmp->current_save_yourself == SmSaveLocal)
{
/* WTF? Anyway, if it's checkpointing, it doesn't have to wait
* for anyone else.
*/
SmsSaveYourselfPhase2 (xsmp->conn);
}
else
gsm_client_request_phase2 (client);
}
static void
xsmp_save_yourself_phase2 (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
g_debug ("xsmp_save_yourself_phase2 ('%s')", xsmp->description);
SmsSaveYourselfPhase2 (xsmp->conn);
}
static void
interact_request_callback (SmsConn conn,
SmPointer manager_data,
int dialog_type)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
g_debug ("Client '%s' received InteractRequest(%s)", xsmp->description,
dialog_type == SmInteractStyleAny ? "Any" : "Errors");
gsm_client_request_interaction (client);
}
static void
xsmp_interact (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
g_debug ("xsmp_interact ('%s')", xsmp->description);
SmsInteract (xsmp->conn);
}
static void
interact_done_callback (SmsConn conn,
SmPointer manager_data,
Bool cancel_shutdown)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
g_debug ("Client '%s' received InteractDone(cancel_shutdown = %s)",
xsmp->description, cancel_shutdown ? "True" : "False");
gsm_client_interaction_done (client, cancel_shutdown);
}
static void
xsmp_shutdown_cancelled (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
g_debug ("xsmp_shutdown_cancelled ('%s')", xsmp->description);
SmsShutdownCancelled (xsmp->conn);
}
static void
xsmp_die (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
g_debug ("xsmp_die ('%s')", xsmp->description);
SmsDie (xsmp->conn);
}
static void
save_yourself_done_callback (SmsConn conn,
SmPointer manager_data,
Bool success)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
g_debug ("Client '%s' received SaveYourselfDone(success = %s)",
xsmp->description, success ? "True" : "False");
if (xsmp->current_save_yourself == SmSaveLocal)
{
xsmp->current_save_yourself = -1;
SmsSaveComplete (xsmp->conn);
gsm_client_saved_state (client);
}
else
{
xsmp->current_save_yourself = -1;
gsm_client_save_yourself_done (client);
}
if (xsmp->next_save_yourself)
{
int save_type = xsmp->next_save_yourself;
xsmp->next_save_yourself = -1;
do_save_yourself (xsmp, save_type);
}
}
static void
close_connection_callback (SmsConn conn,
SmPointer manager_data,
int count,
char **reason_msgs)
{
GsmClient *client = manager_data;
GsmClientXSMP *xsmp = manager_data;
int i;
g_debug ("Client '%s' received CloseConnection", xsmp->description);
for (i = 0; i < count; i++)
g_debug (" close reason: '%s'", reason_msgs[i]);
SmFreeReasons (count, reason_msgs);
gsm_client_disconnected (client);
}
static void
debug_print_property (SmProp *prop)
{
GString *tmp;
int i;
switch (prop->type[0])
{
case 'C': /* CARD8 */
g_debug (" %s = %d", prop->name, *(unsigned char *)prop->vals[0].value);
break;
case 'A': /* ARRAY8 */
g_debug (" %s = '%s'", prop->name, (char *)prop->vals[0].value);
break;
case 'L': /* LISTofARRAY8 */
tmp = g_string_new (NULL);
for (i = 0; i < prop->num_vals; i++)
{
g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length,
(char *)prop->vals[i].value);
}
g_debug (" %s = %s", prop->name, tmp->str);
g_string_free (tmp, TRUE);
break;
default:
g_debug (" %s = ??? (%s)", prop->name, prop->type);
break;
}
}
static SmProp *
find_property (GsmClientXSMP *client, const char *name, int *index)
{
SmProp *prop;
int i;
for (i = 0; i < client->props->len; i++)
{
prop = client->props->pdata[i];
if (!strcmp (prop->name, name))
{
if (index)
*index = i;
return prop;
}
}
return NULL;
}
static void
delete_property (GsmClientXSMP *client, const char *name)
{
int index;
SmProp *prop;
prop = find_property (client, name, &index);
if (!prop)
return;
#if 0
/* This is wrong anyway; we can't unconditionally run the current
* discard command; if this client corresponds to a GsmAppResumed,
* and the current discard command is identical to the app's
* discard_command, then we don't run the discard command now,
* because that would delete a saved state we may want to resume
* again later.
*/
if (!strcmp (name, SmDiscardCommand))
gsm_client_run_discard (client);
#endif
g_ptr_array_remove_index_fast (client->props, index);
SmFreeProperty (prop);
}
static void
set_properties_callback (SmsConn conn,
SmPointer manager_data,
int num_props,
SmProp **props)
{
GsmClientXSMP *client = manager_data;
int i;
g_debug ("Set properties from client '%s'", client->description);
for (i = 0; i < num_props; i++)
{
delete_property (client, props[i]->name);
g_ptr_array_add (client->props, props[i]);
debug_print_property (props[i]);
if (!strcmp (props[i]->name, SmProgram))
set_description (client);
}
free (props);
}
static void
delete_properties_callback (SmsConn conn,
SmPointer manager_data,
int num_props,
char **prop_names)
{
GsmClientXSMP *client = manager_data;
int i;
g_debug ("Delete properties from '%s'", client->description);
for (i = 0; i < num_props; i++)
{
delete_property (client, prop_names[i]);
g_debug (" %s", prop_names[i]);
}
free (prop_names);
}
static void
get_properties_callback (SmsConn conn,
SmPointer manager_data)
{
GsmClientXSMP *client = manager_data;
g_debug ("Get properties request from '%s'", client->description);
SmsReturnProperties (conn, client->props->len,
(SmProp **)client->props->pdata);
}
static const char *
xsmp_get_client_id (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
return xsmp->id;
}
static pid_t
xsmp_get_pid (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
SmProp *prop = find_property (xsmp, SmProcessID, NULL);
char buf[32];
if (!prop || strcmp (prop->type, SmARRAY8) != 0)
return (pid_t)-1;
/* prop->vals[0].value might not be '\0'-terminated... */
g_strlcpy (buf, prop->vals[0].value, MIN (prop->vals[0].length, sizeof (buf)));
return (pid_t)strtoul (buf, NULL, 10);
}
static char *
xsmp_get_desktop_file (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
SmProp *prop = find_property (xsmp, GsmDesktopFile, NULL);
if (!prop || strcmp (prop->type, SmARRAY8) != 0)
return NULL;
return g_strndup (prop->vals[0].value, prop->vals[0].length);
}
static char *
prop_to_command (SmProp *prop)
{
GString *str;
int i, j;
gboolean need_quotes;
str = g_string_new (NULL);
for (i = 0; i < prop->num_vals; i++)
{
char *val = prop->vals[i].value;
need_quotes = FALSE;
for (j = 0; j < prop->vals[i].length; j++)
{
if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j]))
{
need_quotes = TRUE;
break;
}
}
if (i > 0)
g_string_append_c (str, ' ');
if (!need_quotes)
{
g_string_append_printf (str, "%.*s", prop->vals[i].length,
(char *)prop->vals[i].value);
}
else
{
g_string_append_c (str, '\'');
while (val < (char *)prop->vals[i].value + prop->vals[i].length)
{
if (*val == '\'')
g_string_append (str, "'\''");
else
g_string_append_c (str, *val);
val++;
}
g_string_append_c (str, '\'');
}
}
return g_string_free (str, FALSE);
}
static char *
xsmp_get_restart_command (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
SmProp *prop = find_property (xsmp, SmRestartCommand, NULL);
if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
return NULL;
return prop_to_command (prop);
}
static char *
xsmp_get_discard_command (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
SmProp *prop = find_property (xsmp, SmDiscardCommand, NULL);
if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
return NULL;
return prop_to_command (prop);
}
static gboolean
xsmp_get_autorestart (GsmClient *client)
{
GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
SmProp *prop = find_property (xsmp, SmRestartStyleHint, NULL);
if (!prop || strcmp (prop->type, SmCARD8) != 0)
return FALSE;
return ((unsigned char *)prop->vals[0].value)[0] == SmRestartImmediately;
}
static void
set_description (GsmClientXSMP *client)
{
SmProp *prop = find_property (client, SmProgram, NULL);
g_free (client->description);
if (prop)
{
client->description = g_strdup_printf ("%p [%.*s %s]", client,
prop->vals[0].length,
(char *)prop->vals[0].value,
client->id);
}
else if (client->id)
client->description = g_strdup_printf ("%p [%s]", client, client->id);
else
client->description = g_strdup_printf ("%p", client);
}
void
gsm_client_xsmp_connect (GsmClientXSMP *client, SmsConn conn,
unsigned long *mask_ret, SmsCallbacks *callbacks_ret)
{
client->conn = conn;
if (client->protocol_timeout)
{
g_source_remove (client->protocol_timeout);
client->protocol_timeout = 0;
}
g_debug ("Initializing client %s", client->description);
*mask_ret = 0;
*mask_ret |= SmsRegisterClientProcMask;
callbacks_ret->register_client.callback = register_client_callback;
callbacks_ret->register_client.manager_data = client;
*mask_ret |= SmsInteractRequestProcMask;
callbacks_ret->interact_request.callback = interact_request_callback;
callbacks_ret->interact_request.manager_data = client;
*mask_ret |= SmsInteractDoneProcMask;
callbacks_ret->interact_done.callback = interact_done_callback;
callbacks_ret->interact_done.manager_data = client;
*mask_ret |= SmsSaveYourselfRequestProcMask;
callbacks_ret->save_yourself_request.callback = save_yourself_request_callback;
callbacks_ret->save_yourself_request.manager_data = client;
*mask_ret |= SmsSaveYourselfP2RequestProcMask;
callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback;
callbacks_ret->save_yourself_phase2_request.manager_data = client;
*mask_ret |= SmsSaveYourselfDoneProcMask;
callbacks_ret->save_yourself_done.callback = save_yourself_done_callback;
callbacks_ret->save_yourself_done.manager_data = client;
*mask_ret |= SmsCloseConnectionProcMask;
callbacks_ret->close_connection.callback = close_connection_callback;
callbacks_ret->close_connection.manager_data = client;
*mask_ret |= SmsSetPropertiesProcMask;
callbacks_ret->set_properties.callback = set_properties_callback;
callbacks_ret->set_properties.manager_data = client;
*mask_ret |= SmsDeletePropertiesProcMask;
callbacks_ret->delete_properties.callback = delete_properties_callback;
callbacks_ret->delete_properties.manager_data = client;
*mask_ret |= SmsGetPropertiesProcMask;
callbacks_ret->get_properties.callback = get_properties_callback;
callbacks_ret->get_properties.manager_data = client;
}
+70
View File
@@ -0,0 +1,70 @@
/* client-xsmp.h
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef __GSM_CLIENT_XSMP_H__
#define __GSM_CLIENT_XSMP_H__
#include "gsm-client.h"
#include <X11/SM/SMlib.h>
G_BEGIN_DECLS
#define GSM_TYPE_CLIENT_XSMP (gsm_client_xsmp_get_type ())
#define GSM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMP))
#define GSM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
#define GSM_IS_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT_XSMP))
#define GSM_IS_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT_XSMP))
#define GSM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
typedef struct _GsmClientXSMP GsmClientXSMP;
typedef struct _GsmClientXSMPClass GsmClientXSMPClass;
struct _GsmClientXSMP
{
GsmClient parent;
SmsConn conn;
IceConn ice_conn;
guint watch_id, protocol_timeout;
int current_save_yourself, next_save_yourself;
char *id, *description;
GPtrArray *props;
};
struct _GsmClientXSMPClass
{
GsmClientClass parent_class;
};
GType gsm_client_xsmp_get_type (void) G_GNUC_CONST;
GsmClientXSMP *gsm_client_xsmp_new (IceConn ice_conn);
void gsm_client_xsmp_connect (GsmClientXSMP *client,
SmsConn conn,
unsigned long *mask_ret,
SmsCallbacks *callbacks_ret);
G_END_DECLS
#endif /* __GSM_CLIENT_XSMP_H__ */
+251
View File
@@ -0,0 +1,251 @@
/* client.c
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gsm-client.h"
enum {
SAVED_STATE,
REQUEST_PHASE2,
REQUEST_INTERACTION,
INTERACTION_DONE,
SAVE_YOURSELF_DONE,
DISCONNECTED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GsmClient, gsm_client, G_TYPE_OBJECT)
static void
gsm_client_init (GsmClient *client)
{
;
}
static void
gsm_client_class_init (GsmClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
signals[SAVED_STATE] =
g_signal_new ("saved_state",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, saved_state),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[REQUEST_PHASE2] =
g_signal_new ("request_phase2",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, request_phase2),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[REQUEST_INTERACTION] =
g_signal_new ("request_interaction",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, request_interaction),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[INTERACTION_DONE] =
g_signal_new ("interaction_done",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, interaction_done),
NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE,
1, G_TYPE_BOOLEAN);
signals[SAVE_YOURSELF_DONE] =
g_signal_new ("save_yourself_done",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, save_yourself_done),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals[DISCONNECTED] =
g_signal_new ("disconnected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmClientClass, disconnected),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
const char *
gsm_client_get_client_id (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
return GSM_CLIENT_GET_CLASS (client)->get_client_id (client);
}
pid_t
gsm_client_get_pid (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), -1);
return GSM_CLIENT_GET_CLASS (client)->get_pid (client);
}
char *
gsm_client_get_desktop_file (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
return GSM_CLIENT_GET_CLASS (client)->get_desktop_file (client);
}
char *
gsm_client_get_restart_command (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
return GSM_CLIENT_GET_CLASS (client)->get_restart_command (client);
}
char *
gsm_client_get_discard_command (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
return GSM_CLIENT_GET_CLASS (client)->get_discard_command (client);
}
gboolean
gsm_client_get_autorestart (GsmClient *client)
{
g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
return GSM_CLIENT_GET_CLASS (client)->get_autorestart (client);
}
void
gsm_client_save_state (GsmClient *client)
{
g_return_if_fail (GSM_IS_CLIENT (client));
}
void
gsm_client_restart (GsmClient *client, GError **error)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->restart (client, error);
}
void
gsm_client_save_yourself (GsmClient *client,
gboolean save_state)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->save_yourself (client, save_state);
}
void
gsm_client_save_yourself_phase2 (GsmClient *client)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->save_yourself_phase2 (client);
}
void
gsm_client_interact (GsmClient *client)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->interact (client);
}
void
gsm_client_shutdown_cancelled (GsmClient *client)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->shutdown_cancelled (client);
}
void
gsm_client_die (GsmClient *client)
{
g_return_if_fail (GSM_IS_CLIENT (client));
GSM_CLIENT_GET_CLASS (client)->die (client);
}
void
gsm_client_saved_state (GsmClient *client)
{
g_signal_emit (client, signals[SAVED_STATE], 0);
}
void
gsm_client_request_phase2 (GsmClient *client)
{
g_signal_emit (client, signals[REQUEST_PHASE2], 0);
}
void
gsm_client_request_interaction (GsmClient *client)
{
g_signal_emit (client, signals[REQUEST_INTERACTION], 0);
}
void
gsm_client_interaction_done (GsmClient *client, gboolean cancel_shutdown)
{
g_signal_emit (client, signals[INTERACTION_DONE], 0, cancel_shutdown);
}
void
gsm_client_save_yourself_done (GsmClient *client)
{
g_signal_emit (client, signals[SAVE_YOURSELF_DONE], 0);
}
void
gsm_client_disconnected (GsmClient *client)
{
g_signal_emit (client, signals[DISCONNECTED], 0);
}
+111
View File
@@ -0,0 +1,111 @@
/* client.h
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef __GSM_CLIENT_H__
#define __GSM_CLIENT_H__
#include <glib-object.h>
#include <sys/types.h>
G_BEGIN_DECLS
#define GSM_TYPE_CLIENT (gsm_client_get_type ())
#define GSM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT, GsmClient))
#define GSM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT, GsmClientClass))
#define GSM_IS_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT))
#define GSM_IS_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT))
#define GSM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT, GsmClientClass))
typedef struct _GsmClient GsmClient;
typedef struct _GsmClientClass GsmClientClass;
struct _GsmClient
{
GObject parent;
};
struct _GsmClientClass
{
GObjectClass parent_class;
/* signals */
void (*saved_state) (GsmClient *client);
void (*request_phase2) (GsmClient *client);
void (*request_interaction) (GsmClient *client);
void (*interaction_done) (GsmClient *client,
gboolean cancel_shutdown);
void (*save_yourself_done) (GsmClient *client);
void (*disconnected) (GsmClient *client);
/* virtual methods */
const char * (*get_client_id) (GsmClient *client);
pid_t (*get_pid) (GsmClient *client);
char * (*get_desktop_file) (GsmClient *client);
char * (*get_restart_command) (GsmClient *client);
char * (*get_discard_command) (GsmClient *client);
gboolean (*get_autorestart) (GsmClient *client);
void (*restart) (GsmClient *client,
GError **error);
void (*save_yourself) (GsmClient *client,
gboolean save_state);
void (*save_yourself_phase2) (GsmClient *client);
void (*interact) (GsmClient *client);
void (*shutdown_cancelled) (GsmClient *client);
void (*die) (GsmClient *client);
};
GType gsm_client_get_type (void) G_GNUC_CONST;
const char *gsm_client_get_client_id (GsmClient *client);
pid_t gsm_client_get_pid (GsmClient *client);
char *gsm_client_get_desktop_file (GsmClient *client);
char *gsm_client_get_restart_command (GsmClient *client);
char *gsm_client_get_discard_command (GsmClient *client);
gboolean gsm_client_get_autorestart (GsmClient *client);
void gsm_client_save_state (GsmClient *client);
void gsm_client_restart (GsmClient *client,
GError **error);
void gsm_client_save_yourself (GsmClient *client,
gboolean save_state);
void gsm_client_save_yourself_phase2 (GsmClient *client);
void gsm_client_interact (GsmClient *client);
void gsm_client_shutdown_cancelled (GsmClient *client);
void gsm_client_die (GsmClient *client);
/* protected */
void gsm_client_saved_state (GsmClient *client);
void gsm_client_request_phase2 (GsmClient *client);
void gsm_client_request_interaction (GsmClient *client);
void gsm_client_interaction_done (GsmClient *client,
gboolean cancel_shutdown);
void gsm_client_save_yourself_done (GsmClient *client);
void gsm_client_disconnected (GsmClient *client);
G_END_DECLS
#endif /* __GSM_CLIENT_H__ */
+509
View File
@@ -0,0 +1,509 @@
/* session.c
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <string.h>
#include "gsm-session.h"
#include "gsm-app.h"
#include "gsm-xsmp.h"
GsmSession *global_session;
static void initiate_shutdown (GsmSession *session);
static void session_shutdown (GsmSession *session);
static void client_saved_state (GsmClient *client,
gpointer data);
static void client_request_phase2 (GsmClient *client,
gpointer data);
static void client_request_interaction (GsmClient *client,
gpointer data);
static void client_interaction_done (GsmClient *client,
gboolean cancel_shutdown,
gpointer data);
static void client_save_yourself_done (GsmClient *client,
gpointer data);
static void client_disconnected (GsmClient *client,
gpointer data);
struct _GsmSession {
GObject parent;
char *name;
/* Current status */
GsmSessionPhase phase;
guint timeout;
GSList *pending_apps;
/* SM clients */
GSList *clients;
/* When shutdown starts, all clients are put into shutdown_clients.
* If they request phase2, they are moved from shutdown_clients to
* phase2_clients. If they request interaction, they are appended
* to interact_clients (the first client in interact_clients is
* the one currently interacting). If they report that they're done,
* they're removed from shutdown_clients/phase2_clients.
*
* Once shutdown_clients is empty, phase2 starts. Once phase2_clients
* is empty, shutdown is complete.
*/
GSList *shutdown_clients;
GSList *interact_clients;
GSList *phase2_clients;
/* List of clients which were disconnected due to disabled condition
* and shouldn't be automatically restarted */
GSList *condition_clients;
};
struct _GsmSessionClass
{
GObjectClass parent_class;
void (* shutdown_completed) (GsmSession *client);
};
enum {
SHUTDOWN_COMPLETED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GsmSession, gsm_session, G_TYPE_OBJECT)
#define GSM_SESSION_PHASE_TIMEOUT 10 /* seconds */
void
gsm_session_init (GsmSession *session)
{
session->name = NULL;
session->clients = NULL;
session->condition_clients = NULL;
}
static void
gsm_session_class_init (GsmSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
signals[SHUTDOWN_COMPLETED] =
g_signal_new ("shutdown_completed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsmSessionClass, shutdown_completed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
/**
* gsm_session_set_name:
* @session: session instance
* @name: name of the session
*
* Sets the name of a running session.
**/
void
gsm_session_set_name (GsmSession *session, const char *name)
{
if (session->name)
g_free (session->name);
session->name = g_strdup (name);
}
static void start_phase (GsmSession *session);
static void
end_phase (GsmSession *session)
{
g_slist_free (session->pending_apps);
session->pending_apps = NULL;
g_debug ("ending phase %d\n", session->phase);
session->phase++;
if (session->phase < GSM_SESSION_PHASE_RUNNING)
start_phase (session);
}
static void
app_registered (GsmApp *app, gpointer data)
{
GsmSession *session = data;
session->pending_apps = g_slist_remove (session->pending_apps, app);
g_signal_handlers_disconnect_by_func (app, app_registered, session);
if (!session->pending_apps)
{
if (session->timeout > 0)
{
g_source_remove (session->timeout);
session->timeout = 0;
}
end_phase (session);
}
}
static gboolean
phase_timeout (gpointer data)
{
GsmSession *session = data;
GSList *a;
session->timeout = 0;
for (a = session->pending_apps; a; a = a->next)
{
g_warning ("Application '%s' failed to register before timeout",
gsm_app_get_basename (a->data));
g_signal_handlers_disconnect_by_func (a->data, app_registered, session);
/* FIXME: what if the app was filling in a required slot? */
}
end_phase (session);
return FALSE;
}
static void
start_phase (GsmSession *session)
{
g_debug ("starting phase %d\n", session->phase);
g_slist_free (session->pending_apps);
session->pending_apps = NULL;
if (session->pending_apps)
{
if (session->phase < GSM_SESSION_PHASE_APPLICATION)
{
session->timeout = g_timeout_add_seconds (GSM_SESSION_PHASE_TIMEOUT,
phase_timeout, session);
}
}
else
end_phase (session);
}
void
gsm_session_start (GsmSession *session)
{
session->phase = GSM_SESSION_PHASE_INITIALIZATION;
start_phase (session);
}
GsmSessionPhase
gsm_session_get_phase (GsmSession *session)
{
return session->phase;
}
char *
gsm_session_register_client (GsmSession *session,
GsmClient *client,
const char *id)
{
GSList *a;
char *client_id = NULL;
/* If we're shutting down, we don't accept any new session
clients. */
if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
return FALSE;
if (id == NULL)
client_id = gsm_xsmp_generate_client_id ();
else
{
for (a = session->clients; a; a = a->next)
{
GsmClient *client = GSM_CLIENT (a->data);
/* We can't have two clients with the same id. */
if (!strcmp (id, gsm_client_get_client_id (client)))
{
return NULL;
}
}
client_id = g_strdup (id);
}
g_debug ("Adding new client %s to session", id);
g_signal_connect (client, "saved_state",
G_CALLBACK (client_saved_state), session);
g_signal_connect (client, "request_phase2",
G_CALLBACK (client_request_phase2), session);
g_signal_connect (client, "request_interaction",
G_CALLBACK (client_request_interaction), session);
g_signal_connect (client, "interaction_done",
G_CALLBACK (client_interaction_done), session);
g_signal_connect (client, "save_yourself_done",
G_CALLBACK (client_save_yourself_done), session);
g_signal_connect (client, "disconnected",
G_CALLBACK (client_disconnected), session);
session->clients = g_slist_prepend (session->clients, client);
/* If it's a brand new client id, we just accept the client*/
if (id == NULL)
return client_id;
/* If we're starting up the session, try to match the new client
* with one pending apps for the current phase. If not, try to match
* with any of the autostarted apps. */
if (session->phase < GSM_SESSION_PHASE_APPLICATION)
a = session->pending_apps;
for (; a; a = a->next)
{
GsmApp *app = GSM_APP (a->data);
if (!strcmp (client_id, app->client_id))
{
gsm_app_registered (app);
return client_id;
}
}
g_free (client_id);
return NULL;
}
static void
client_saved_state (GsmClient *client, gpointer data)
{
/* FIXME */
}
void
gsm_session_initiate_shutdown (GsmSession *session)
{
if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
{
/* Already shutting down, nothing more to do */
return;
}
initiate_shutdown (session);
}
static void
session_shutdown_phase2 (GsmSession *session)
{
GSList *cl;
for (cl = session->phase2_clients; cl; cl = cl->next)
gsm_client_save_yourself_phase2 (cl->data);
}
static void
session_cancel_shutdown (GsmSession *session)
{
GSList *cl;
session->phase = GSM_SESSION_PHASE_RUNNING;
g_slist_free (session->shutdown_clients);
session->shutdown_clients = NULL;
g_slist_free (session->interact_clients);
session->interact_clients = NULL;
g_slist_free (session->phase2_clients);
session->phase2_clients = NULL;
for (cl = session->clients; cl; cl = cl->next)
gsm_client_shutdown_cancelled (cl->data);
}
void
gsm_session_cancel_shutdown (GsmSession *session)
{
if (session == NULL || session->phase != GSM_SESSION_PHASE_SHUTDOWN)
{
g_warning ("Session is not in shutdown mode");
return;
}
session_cancel_shutdown (session);
}
static void
initiate_shutdown (GsmSession *session)
{
GSList *cl;
session->phase = GSM_SESSION_PHASE_SHUTDOWN;
if (session->clients == NULL)
session_shutdown (session);
for (cl = session->clients; cl; cl = cl->next)
{
GsmClient *client = GSM_CLIENT (cl->data);
session->shutdown_clients =
g_slist_prepend (session->shutdown_clients, client);
gsm_client_save_yourself (client, FALSE);
}
}
static void
session_shutdown (GsmSession *session)
{
GSList *cl;
/* FIXME: do this in reverse phase order */
for (cl = session->clients; cl; cl = cl->next)
gsm_client_die (cl->data);
g_signal_emit (session, signals[SHUTDOWN_COMPLETED], 0);
}
static void
client_request_phase2 (GsmClient *client, gpointer data)
{
GsmSession *session = data;
/* Move the client from shutdown_clients to phase2_clients */
session->shutdown_clients =
g_slist_remove (session->shutdown_clients, client);
session->phase2_clients =
g_slist_prepend (session->phase2_clients, client);
}
static void
client_request_interaction (GsmClient *client, gpointer data)
{
GsmSession *session = data;
session->interact_clients =
g_slist_append (session->interact_clients, client);
if (!session->interact_clients->next)
gsm_client_interact (client);
}
static void
client_interaction_done (GsmClient *client, gboolean cancel_shutdown,
gpointer data)
{
GsmSession *session = data;
g_return_if_fail (session->interact_clients &&
(GsmClient *)session->interact_clients->data == client);
if (cancel_shutdown)
{
session_cancel_shutdown (session);
return;
}
/* Remove this client from interact_clients, and if there's another
* client waiting to interact, let it interact now.
*/
session->interact_clients =
g_slist_remove (session->interact_clients, client);
if (session->interact_clients)
gsm_client_interact (session->interact_clients->data);
}
static void
client_save_yourself_done (GsmClient *client, gpointer data)
{
GsmSession *session = data;
session->shutdown_clients =
g_slist_remove (session->shutdown_clients, client);
session->interact_clients =
g_slist_remove (session->interact_clients, client);
session->phase2_clients =
g_slist_remove (session->phase2_clients, client);
if (session->phase == GSM_SESSION_PHASE_SHUTDOWN &&
!session->shutdown_clients)
{
if (session->phase2_clients)
session_shutdown_phase2 (session);
else
session_shutdown (session);
}
}
static void
client_disconnected (GsmClient *client, gpointer data)
{
GsmSession *session = data;
gboolean is_condition_client = FALSE;
session->clients =
g_slist_remove (session->clients, client);
session->shutdown_clients =
g_slist_remove (session->shutdown_clients, client);
session->interact_clients =
g_slist_remove (session->interact_clients, client);
session->phase2_clients =
g_slist_remove (session->phase2_clients, client);
if (g_slist_find (session->condition_clients, client))
{
session->condition_clients =
g_slist_remove (session->condition_clients, client);
is_condition_client = TRUE;
}
if (session->phase != GSM_SESSION_PHASE_SHUTDOWN &&
gsm_client_get_autorestart (client) &&
!is_condition_client)
{
GError *error = NULL;
gsm_client_restart (client, &error);
if (error)
{
g_warning ("Error on restarting session client: %s", error->message);
g_clear_error (&error);
}
}
g_object_unref (client);
}
GsmSession *
gsm_session_create_global (void)
{
global_session = GSM_SESSION(g_object_new (GSM_TYPE_SESSION, NULL));
return global_session;
}
+97
View File
@@ -0,0 +1,97 @@
/* session.h
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef __GSM_SESSION_H__
#define __GSM_SESSION_H__
#include <glib.h>
#include "gsm-client.h"
G_BEGIN_DECLS
#define GSM_TYPE_SESSION (gsm_session_get_type ())
#define GSM_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SESSION, GsmSession))
#define GSM_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SESSION, GsmSessionClass))
#define GSM_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SESSION))
#define GSM_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SESSION))
#define GSM_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SESSION, GsmSessionClass))
typedef struct _GsmSession GsmSession;
typedef struct _GsmSessionClass GsmSessionClass;
extern GsmSession *global_session;
typedef enum {
/* gsm's own startup/initialization phase */
GSM_SESSION_PHASE_STARTUP,
/* xrandr setup, gnome-settings-daemon, etc */
GSM_SESSION_PHASE_INITIALIZATION,
/* window/compositing managers */
GSM_SESSION_PHASE_WINDOW_MANAGER,
/* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */
GSM_SESSION_PHASE_PANEL,
/* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */
GSM_SESSION_PHASE_DESKTOP,
/* everything else */
GSM_SESSION_PHASE_APPLICATION,
/* done launching */
GSM_SESSION_PHASE_RUNNING,
/* shutting down */
GSM_SESSION_PHASE_SHUTDOWN
} GsmSessionPhase;
typedef enum {
GSM_SESSION_LOGOUT_TYPE_LOGOUT,
GSM_SESSION_LOGOUT_TYPE_SHUTDOWN
} GsmSessionLogoutType;
typedef enum {
GSM_SESSION_LOGOUT_MODE_NORMAL,
GSM_SESSION_LOGOUT_MODE_NO_CONFIRMATION,
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);
void gsm_session_start (GsmSession *session);
GsmSessionPhase gsm_session_get_phase (GsmSession *session);
void gsm_session_initiate_shutdown (GsmSession *session);
void gsm_session_cancel_shutdown (GsmSession *session);
char *gsm_session_register_client (GsmSession *session,
GsmClient *client,
const char *previous_id);
GsmSession *gsm_session_create_global (void);
G_END_DECLS
#endif /* __GSM_SESSION_H__ */
+535
View File
@@ -0,0 +1,535 @@
/* xsmp.c
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "gsm-client-xsmp.h"
#include "gsm-xsmp.h"
#include <X11/ICE/ICElib.h>
#include <X11/ICE/ICEutil.h>
#include <X11/ICE/ICEconn.h>
#include <X11/SM/SMlib.h>
#ifdef HAVE_X11_XTRANS_XTRANS_H
/* Get the proto for _IceTransNoListen */
#define ICE_t
#define TRANS_SERVER
#include <X11/Xtrans/Xtrans.h>
#undef ICE_t
#undef TRANS_SERVER
#endif /* HAVE_X11_XTRANS_XTRANS_H */
static IceListenObj *xsmp_sockets;
static int num_xsmp_sockets, num_local_xsmp_sockets;
static gboolean update_iceauthority (gboolean adding);
static gboolean accept_ice_connection (GIOChannel *source,
GIOCondition condition,
gpointer data);
static Status accept_xsmp_connection (SmsConn conn,
SmPointer manager_data,
unsigned long *mask_ret,
SmsCallbacks *callbacks_ret,
char **failure_reason_ret);
static void ice_error_handler (IceConn conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence_num,
int error_class,
int severity,
IcePointer values);
static void ice_io_error_handler (IceConn conn);
static void sms_error_handler (SmsConn sms_conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence_num,
int error_class,
int severity,
IcePointer values);
/**
* gsm_xsmp_init:
*
* Initializes XSMP. Notably, it creates the XSMP listening socket and
* sets the SESSION_MANAGER environment variable to point to it.
**/
char *
gsm_xsmp_init (void)
{
char error[256];
mode_t saved_umask;
char *network_id_list;
int i;
/* Set up sane error handlers */
IceSetErrorHandler (ice_error_handler);
IceSetIOErrorHandler (ice_io_error_handler);
SmsSetErrorHandler (sms_error_handler);
/* Initialize libSM; we pass NULL for hostBasedAuthProc to disable
* host-based authentication.
*/
if (!SmsInitialize (PACKAGE, VERSION, accept_xsmp_connection,
NULL, NULL, sizeof (error), error))
g_error("Could not initialize libSM: %s", error);
#ifdef HAVE_X11_XTRANS_XTRANS_H
/* By default, IceListenForConnections will open one socket for each
* transport type known to X. We don't want connections from remote
* hosts, so for security reasons it would be best if ICE didn't
* even open any non-local sockets. So we use an internal ICElib
* method to disable them here. Unfortunately, there is no way to
* ask X what transport types it knows about, so we're forced to
* guess.
*/
_IceTransNoListen ("tcp");
#endif
/* Create the XSMP socket. Older versions of IceListenForConnections
* have a bug which causes the umask to be set to 0 on certain types
* of failures. Probably not an issue on any modern systems, but
* we'll play it safe.
*/
saved_umask = umask (0);
umask (saved_umask);
if (!IceListenForConnections (&num_xsmp_sockets, &xsmp_sockets,
sizeof (error), error))
g_error ("Could not create ICE listening socket: %s", error);
umask (saved_umask);
/* Find the local sockets in the returned socket list and move them
* to the start of the list.
*/
for (i = num_local_xsmp_sockets = 0; i < num_xsmp_sockets; i++)
{
char *id = IceGetListenConnectionString (xsmp_sockets[i]);
if (!strncmp (id, "local/", sizeof ("local/") - 1) ||
!strncmp (id, "unix/", sizeof ("unix/") - 1))
{
if (i > num_local_xsmp_sockets)
{
IceListenObj tmp = xsmp_sockets[i];
xsmp_sockets[i] = xsmp_sockets[num_local_xsmp_sockets];
xsmp_sockets[num_local_xsmp_sockets] = tmp;
}
num_local_xsmp_sockets++;
}
free (id);
}
if (num_local_xsmp_sockets == 0)
g_error ("IceListenForConnections did not return a local listener!");
#ifdef HAVE_X11_XTRANS_XTRANS_H
if (num_local_xsmp_sockets != num_xsmp_sockets)
{
/* Xtrans was apparently compiled with support for some
* non-local transport besides TCP (which we disabled above); we
* won't create IO watches on those extra sockets, so
* connections to them will never be noticed, but they're still
* there, which is inelegant.
*
* If the g_warning below is triggering for you and you want to
* stop it, the fix is to add additional _IceTransNoListen()
* calls above.
*/
network_id_list =
IceComposeNetworkIdList (num_xsmp_sockets - num_local_xsmp_sockets,
xsmp_sockets + num_local_xsmp_sockets);
g_warning ("IceListenForConnections returned %d non-local listeners: %s",
num_xsmp_sockets - num_local_xsmp_sockets, network_id_list);
free (network_id_list);
}
#endif
/* Update .ICEauthority with new auth entries for our socket */
if (!update_iceauthority (TRUE))
{
/* FIXME: is this really fatal? Hm... */
g_error ("Could not update ICEauthority file %s",
IceAuthFileName ());
}
network_id_list = IceComposeNetworkIdList (num_local_xsmp_sockets,
xsmp_sockets);
return network_id_list;
}
/**
* gsm_xsmp_run:
*
* Sets the XSMP server to start accepting connections.
**/
void
gsm_xsmp_run (void)
{
GIOChannel *channel;
int i;
for (i = 0; i < num_local_xsmp_sockets; i++)
{
channel = g_io_channel_unix_new (IceGetListenConnectionNumber (xsmp_sockets[i]));
g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
accept_ice_connection, xsmp_sockets[i]);
g_io_channel_unref (channel);
}
}
/**
* gsm_xsmp_shutdown:
*
* Shuts down the XSMP server and closes the ICE listening socket
**/
void
gsm_xsmp_shutdown (void)
{
update_iceauthority (FALSE);
IceFreeListenObjs (num_xsmp_sockets, xsmp_sockets);
xsmp_sockets = NULL;
}
/**
* gsm_xsmp_generate_client_id:
*
* Generates a new XSMP client ID.
*
* Return value: an XSMP client ID.
**/
char *
gsm_xsmp_generate_client_id (void)
{
static int sequence = -1;
static guint rand1 = 0, rand2 = 0;
static pid_t pid = 0;
struct timeval tv;
/* The XSMP spec defines the ID as:
*
* Version: "1"
* Address type and address:
* "1" + an IPv4 address as 8 hex digits
* "2" + a DECNET address as 12 hex digits
* "6" + an IPv6 address as 32 hex digits
* Time stamp: milliseconds since UNIX epoch as 13 decimal digits
* Process-ID type and process-ID:
* "1" + POSIX PID as 10 decimal digits
* Sequence number as 4 decimal digits
*
* XSMP client IDs are supposed to be globally unique: if
* SmsGenerateClientID() is unable to determine a network
* address for the machine, it gives up and returns %NULL.
* GNOME and KDE have traditionally used a fourth address
* format in this case:
* "0" + 16 random hex digits
*
* We don't even bother trying SmsGenerateClientID(), since the
* user's IP address is probably "192.168.1.*" anyway, so a random
* number is actually more likely to be globally unique.
*/
if (!rand1)
{
rand1 = g_random_int ();
rand2 = g_random_int ();
pid = getpid ();
}
sequence = (sequence + 1) % 10000;
gettimeofday (&tv, NULL);
return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d",
rand1, rand2,
(unsigned long) tv.tv_sec,
(unsigned) tv.tv_usec,
(unsigned long) pid,
sequence);
}
/* This is called (by glib via xsmp->ice_connection_watch) when a
* connection is first received on the ICE listening socket. (We
* expect that the client will then initiate XSMP on the connection;
* if it does not, GsmClientXSMP will eventually time out and close
* the connection.)
*
* FIXME: it would probably make more sense to not create a
* GsmClientXSMP object until accept_xsmp_connection, below (and to do
* the timing-out here in xsmp.c).
*/
static gboolean
accept_ice_connection (GIOChannel *source,
GIOCondition condition,
gpointer data)
{
IceListenObj listener = data;
IceConn ice_conn;
IceAcceptStatus status;
GsmClientXSMP *client;
g_debug ("accept_ice_connection()");
ice_conn = IceAcceptConnection (listener, &status);
if (status != IceAcceptSuccess)
{
g_debug ("IceAcceptConnection returned %d", status);
return TRUE;
}
client = gsm_client_xsmp_new (ice_conn);
ice_conn->context = client;
return TRUE;
}
/* This is called (by libSM) when XSMP is initiated on an ICE
* connection that was already accepted by accept_ice_connection.
*/
static Status
accept_xsmp_connection (SmsConn sms_conn, SmPointer manager_data,
unsigned long *mask_ret, SmsCallbacks *callbacks_ret,
char **failure_reason_ret)
{
IceConn ice_conn;
GsmClientXSMP *client;
/* FIXME: what about during shutdown but before gsm_xsmp_shutdown? */
if (!xsmp_sockets)
{
g_debug ("In shutdown, rejecting new client");
*failure_reason_ret =
strdup (_("Refusing new client connection because the session is currently being shut down\n"));
return FALSE;
}
ice_conn = SmsGetIceConnection (sms_conn);
client = ice_conn->context;
g_return_val_if_fail (client != NULL, TRUE);
gsm_client_xsmp_connect (client, sms_conn, mask_ret, callbacks_ret);
return TRUE;
}
/* ICEauthority stuff */
/* Various magic numbers stolen from iceauth.c */
#define GSM_ICE_AUTH_RETRIES 10
#define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */
#define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */
#define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1"
#define GSM_ICE_MAGIC_COOKIE_LEN 16
static IceAuthFileEntry *
auth_entry_new (const char *protocol, const char *network_id)
{
IceAuthFileEntry *file_entry;
IceAuthDataEntry data_entry;
file_entry = malloc (sizeof (IceAuthFileEntry));
file_entry->protocol_name = strdup (protocol);
file_entry->protocol_data = NULL;
file_entry->protocol_data_length = 0;
file_entry->network_id = strdup (network_id);
file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME);
file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN);
file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN;
/* Also create an in-memory copy, which is what the server will
* actually use for checking client auth.
*/
data_entry.protocol_name = file_entry->protocol_name;
data_entry.network_id = file_entry->network_id;
data_entry.auth_name = file_entry->auth_name;
data_entry.auth_data = file_entry->auth_data;
data_entry.auth_data_length = file_entry->auth_data_length;
IceSetPaAuthData (1, &data_entry);
return file_entry;
}
static gboolean
update_iceauthority (gboolean adding)
{
char *filename = IceAuthFileName ();
char **our_network_ids;
FILE *fp;
IceAuthFileEntry *auth_entry;
GSList *entries, *e;
int i;
gboolean ok = FALSE;
if (IceLockAuthFile (filename, GSM_ICE_AUTH_RETRIES, GSM_ICE_AUTH_INTERVAL,
GSM_ICE_AUTH_LOCK_TIMEOUT) != IceAuthLockSuccess)
return FALSE;
our_network_ids = g_malloc (num_local_xsmp_sockets * sizeof (char *));
for (i = 0; i < num_local_xsmp_sockets; i++)
our_network_ids[i] = IceGetListenConnectionString (xsmp_sockets[i]);
entries = NULL;
fp = fopen (filename, "r+");
if (fp)
{
while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL)
{
/* Skip/delete entries with no network ID (invalid), or with
* our network ID; if we're starting up, an entry with our
* ID must be a stale entry left behind by an old process,
* and if we're shutting down, it won't be valid in the
* future, so either way we want to remove it from the list.
*/
if (!auth_entry->network_id)
{
IceFreeAuthFileEntry (auth_entry);
continue;
}
for (i = 0; i < num_local_xsmp_sockets; i++)
{
if (!strcmp (auth_entry->network_id, our_network_ids[i]))
{
IceFreeAuthFileEntry (auth_entry);
break;
}
}
if (i != num_local_xsmp_sockets)
continue;
entries = g_slist_prepend (entries, auth_entry);
}
rewind (fp);
}
else
{
int fd;
if (g_file_test (filename, G_FILE_TEST_EXISTS))
{
g_warning ("Unable to read ICE authority file: %s", filename);
goto cleanup;
}
fd = open (filename, O_CREAT | O_WRONLY, 0600);
fp = fdopen (fd, "w");
if (!fp)
{
g_warning ("Unable to write to ICE authority file: %s", filename);
if (fd != -1)
close (fd);
goto cleanup;
}
}
if (adding)
{
for (i = 0; i < num_local_xsmp_sockets; i++)
{
entries = g_slist_append (entries,
auth_entry_new ("ICE", our_network_ids[i]));
entries = g_slist_prepend (entries,
auth_entry_new ("XSMP", our_network_ids[i]));
}
}
for (e = entries; e; e = e->next)
{
IceAuthFileEntry *auth_entry = e->data;
IceWriteAuthFileEntry (fp, auth_entry);
IceFreeAuthFileEntry (auth_entry);
}
g_slist_free (entries);
fclose (fp);
ok = TRUE;
cleanup:
IceUnlockAuthFile (filename);
for (i = 0; i < num_local_xsmp_sockets; i++)
free (our_network_ids[i]);
g_free (our_network_ids);
return ok;
}
/* Error handlers */
static void
ice_error_handler (IceConn conn, Bool swap, int offending_minor_opcode,
unsigned long offending_sequence, int error_class,
int severity, IcePointer values)
{
g_debug ("ice_error_handler (%p, %s, %d, %lx, %d, %d)",
conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
offending_sequence, error_class, severity);
if (severity == IceCanContinue)
return;
/* FIXME: the ICElib docs are completely vague about what we're
* supposed to do in this case. Need to verify that calling
* IceCloseConnection() here is guaranteed to cause neither
* free-memory-reads nor leaks.
*/
IceCloseConnection (conn);
}
static void
ice_io_error_handler (IceConn conn)
{
g_debug ("ice_io_error_handler (%p)", conn);
/* We don't need to do anything here; the next call to
* IceProcessMessages() for this connection will receive
* IceProcessMessagesIOError and we can handle the error there.
*/
}
static void
sms_error_handler (SmsConn conn, Bool swap, int offending_minor_opcode,
unsigned long offending_sequence_num, int error_class,
int severity, IcePointer values)
{
g_debug ("sms_error_handler (%p, %s, %d, %lx, %d, %d)",
conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
offending_sequence_num, error_class, severity);
/* We don't need to do anything here; if the connection needs to be
* closed, libSM will do that itself.
*/
}
+29
View File
@@ -0,0 +1,29 @@
/* xsmp.h
* Copyright (C) 2007 Novell, Inc.
*
* 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
* Lesser 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., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#ifndef __GSM_XSMP_H__
#define __GSM_XSMP_H__
char *gsm_xsmp_init (void);
void gsm_xsmp_run (void);
void gsm_xsmp_shutdown (void);
char *gsm_xsmp_generate_client_id (void);
#endif /* __GSM_XSMP_H__ */
+308
View File
@@ -0,0 +1,308 @@
# Copyright (C) 2006-2007 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.
"""
STABLE.
"""
import os
import threading
import urllib
import fcntl
import tempfile
import gobject
import SimpleHTTPServer
import SocketServer
__authinfos = {}
def _add_authinfo(authinfo):
__authinfos[threading.currentThread()] = authinfo
def get_authinfo():
return __authinfos.get(threading.currentThread())
def _del_authinfo():
del __authinfos[threading.currentThread()]
class GlibTCPServer(SocketServer.TCPServer):
"""GlibTCPServer
Integrate socket accept into glib mainloop.
"""
allow_reuse_address = True
request_queue_size = 20
def __init__(self, server_address, RequestHandlerClass):
SocketServer.TCPServer.__init__(self, server_address,
RequestHandlerClass)
self.socket.setblocking(0) # Set nonblocking
# Watch the listener socket for data
gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
def _handle_accept(self, source, condition):
"""Process incoming data on the server's socket by doing an accept()
via handle_request()."""
if not (condition & gobject.IO_IN):
return True
self.handle_request()
return True
def close_request(self, request):
"""Called to clean up an individual request."""
# let the request be closed by the request handler when its done
pass
def shutdown_request(self, request):
"""Called to shutdown and close an individual request."""
# like close_request, let the request be closed by the request handler
# when done
pass
class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""RequestHandler class that integrates with Glib mainloop. It writes
the specified file to the client in chunks, returning control to the
mainloop between chunks.
"""
CHUNK_SIZE = 4096
def __init__(self, request, client_address, server):
self._file = None
self._srcid = 0
SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
self, request, client_address, server)
def log_request(self, code='-', size='-'):
pass
def do_GET(self):
"""Serve a GET request."""
self._file = self.send_head()
if self._file:
self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT |
gobject.IO_ERR,
self._send_next_chunk)
else:
self._cleanup()
def _send_next_chunk(self, source, condition):
if condition & gobject.IO_ERR:
self._cleanup()
return False
if not (condition & gobject.IO_OUT):
self._cleanup()
return False
data = self._file.read(self.CHUNK_SIZE)
count = os.write(self.wfile.fileno(), data)
if count != len(data) or len(data) != self.CHUNK_SIZE:
self._cleanup()
return False
return True
def _cleanup(self):
if self._file:
self._file.close()
self._file = None
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
if not self.wfile.closed:
self.wfile.flush()
self.wfile.close()
self.rfile.close()
def finish(self):
"""Close the sockets when we're done, not before"""
pass
def send_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
** [dcbw] modified to send Content-disposition filename too
"""
path = self.translate_path(self.path)
if not path or not os.path.exists(path):
self.send_error(404, 'File not found')
return None
f = None
if os.path.isdir(path):
for index in 'index.html', 'index.htm':
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
else:
return self.list_directory(path)
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, 'File not found')
return None
self.send_response(200)
self.send_header('Content-type', ctype)
self.send_header('Content-Length', str(os.fstat(f.fileno())[6]))
self.send_header('Content-Disposition', 'attachment; filename="%s"' %
os.path.basename(path))
self.end_headers()
return f
class GlibURLDownloader(gobject.GObject):
"""Grabs a URL in chunks, returning to the mainloop after each chunk"""
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
'error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
}
CHUNK_SIZE = 4096
def __init__(self, url, destdir=None):
self._url = url
if not destdir:
destdir = tempfile.gettempdir()
self._destdir = destdir
self._srcid = 0
self._fname = None
self._outf = None
self._suggested_fname = None
self._info = None
self._written = 0
gobject.GObject.__init__(self)
def start(self, destfile=None, destfd=None):
self._info = urllib.urlopen(self._url)
self._outf = None
self._fname = None
if destfd and not destfile:
raise ValueError('Must provide destination file too when'
' specifying file descriptor')
if destfile:
self._suggested_fname = os.path.basename(destfile)
self._fname = os.path.abspath(os.path.expanduser(destfile))
if destfd:
# Use the user-supplied destination file descriptor
self._outf = destfd
else:
self._outf = os.open(self._fname, os.O_RDWR |
os.O_TRUNC | os.O_CREAT, 0644)
else:
fname = self._get_filename_from_headers(self._info.headers)
self._suggested_fname = fname
garbage_, path = urllib.splittype(self._url)
garbage_, path = urllib.splithost(path or "")
path, garbage_ = urllib.splitquery(path or "")
path, garbage_ = urllib.splitattr(path or "")
suffix = os.path.splitext(path)[1]
(self._outf, self._fname) = tempfile.mkstemp(suffix=suffix,
dir=self._destdir)
fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
self._srcid = gobject.io_add_watch(self._info.fp.fileno(),
gobject.IO_IN | gobject.IO_ERR,
self._read_next_chunk)
def cancel(self):
if self._srcid == 0:
raise RuntimeError('Download already canceled or stopped')
self.cleanup(remove=True)
def _get_filename_from_headers(self, headers):
if 'Content-Disposition' not in headers:
return None
ftag = 'filename='
data = headers['Content-Disposition']
fidx = data.find(ftag)
if fidx < 0:
return None
fname = data[fidx + len(ftag):]
if fname[0] == '"' or fname[0] == "'":
fname = fname[1:]
if fname[len(fname) - 1] == '"' or fname[len(fname) - 1] == "'":
fname = fname[:len(fname) - 1]
return fname
def _read_next_chunk(self, source, condition):
if condition & gobject.IO_ERR:
self.cleanup(remove=True)
self.emit('error', 'Error downloading file.')
return False
elif not (condition & gobject.IO_IN):
# shouldn't get here, but...
return True
try:
data = self._info.fp.read(self.CHUNK_SIZE)
count = os.write(self._outf, data)
self._written += len(data)
# error writing data to file?
if count < len(data):
self.cleanup(remove=True)
self.emit('error', 'Error writing to download file.')
return False
self.emit('progress', self._written)
# done?
if len(data) < self.CHUNK_SIZE:
self.cleanup()
self.emit('finished', self._fname, self._suggested_fname)
return False
except Exception, err:
self.cleanup(remove=True)
self.emit('error', 'Error downloading file: %r' % err)
return False
return True
def cleanup(self, remove=False):
if self._srcid > 0:
gobject.source_remove(self._srcid)
self._srcid = 0
del self._info
self._info = None
os.close(self._outf)
if remove:
os.remove(self._fname)
self._outf = None
+10
View File
@@ -0,0 +1,10 @@
sugardir = $(pythondir)/sugar3/presence
sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
connectionmanager.py \
sugartubeconn.py \
tubeconn.py \
presenceservice.py
+24
View File
@@ -0,0 +1,24 @@
# Copyright (C) 2006-2007, 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.
"""Client-code's interface to the PresenceService
Provides a simplified API for accessing the dbus service
which coordinates native network presence and sharing
information. This includes both "buddies" and "shared
activities".
"""
+722
View File
@@ -0,0 +1,722 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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.
"""UI interface to an activity in the presence service
STABLE.
"""
import logging
from functools import partial
import dbus
from dbus import PROPERTIES_IFACE
import gobject
from telepathy.client import Channel
from telepathy.interfaces import CHANNEL, \
CHANNEL_INTERFACE_GROUP, \
CHANNEL_TYPE_TUBES, \
CHANNEL_TYPE_TEXT, \
CONNECTION, \
PROPERTIES_INTERFACE
from telepathy.constants import CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, \
HANDLE_TYPE_ROOM, \
HANDLE_TYPE_CONTACT, \
PROPERTY_FLAG_WRITE
from sugar.presence.buddy import Buddy
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
_logger = logging.getLogger('sugar.presence.activity')
class Activity(gobject.GObject):
"""UI interface for an Activity in the presence service
Activities in the presence service represent your and other user's
shared activities.
Properties:
id
color
name
type
joined
"""
__gsignals__ = {
'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
}
__gproperties__ = {
'id': (str, None, None, None, gobject.PARAM_READABLE),
'name': (str, None, None, None, gobject.PARAM_READWRITE),
'tags': (str, None, None, None, gobject.PARAM_READWRITE),
'color': (str, None, None, None, gobject.PARAM_READWRITE),
'type': (str, None, None, None, gobject.PARAM_READABLE),
'private': (bool, None, None, True, gobject.PARAM_READWRITE),
'joined': (bool, None, None, False, gobject.PARAM_READABLE),
}
def __init__(self, account_path, connection, room_handle=None,
properties=None):
if room_handle is None and properties is None:
raise ValueError('Need to pass one of room_handle or properties')
if properties is None:
properties = {}
gobject.GObject.__init__(self)
self._account_path = account_path
self.telepathy_conn = connection
self.telepathy_text_chan = None
self.telepathy_tubes_chan = None
self.room_handle = room_handle
self._join_command = None
self._share_command = None
self._id = properties.get('id', None)
self._color = properties.get('color', None)
self._name = properties.get('name', None)
self._type = properties.get('type', None)
self._tags = properties.get('tags', None)
self._private = properties.get('private', True)
self._joined = properties.get('joined', False)
self._channel_self_handle = None
self._text_channel_group_flags = 0
self._buddies = {}
self._joined_buddies = {}
self._get_properties_call = None
if not self.room_handle is None:
self._start_tracking_properties()
def _start_tracking_properties(self):
bus = dbus.SessionBus()
self._get_properties_call = bus.call_async(
self.telepathy_conn.requested_bus_name,
self.telepathy_conn.object_path,
CONN_INTERFACE_ACTIVITY_PROPERTIES,
'GetProperties',
'u',
(self.room_handle,),
reply_handler=self.__got_properties_cb,
error_handler=self.__error_handler_cb,
utf8_strings=True)
# As only one Activity instance is needed per activity process,
# we can afford listening to ActivityPropertiesChanged like this.
self.telepathy_conn.connect_to_signal(
'ActivityPropertiesChanged',
self.__activity_properties_changed_cb,
dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
def __activity_properties_changed_cb(self, room_handle, properties):
_logger.debug('%r: Activity properties changed to %r', self,
properties)
self._update_properties(properties)
def __got_properties_cb(self, properties):
_logger.debug('__got_properties_cb %r', properties)
self._get_properties_call = None
self._update_properties(properties)
def __error_handler_cb(self, error):
_logger.debug('__error_handler_cb %r', error)
def _update_properties(self, new_props):
val = new_props.get('name', self._name)
if isinstance(val, str) and val != self._name:
self._name = val
self.notify('name')
val = new_props.get('tags', self._tags)
if isinstance(val, str) and val != self._tags:
self._tags = val
self.notify('tags')
val = new_props.get('color', self._color)
if isinstance(val, str) and val != self._color:
self._color = val
self.notify('color')
val = bool(new_props.get('private', self._private))
if val != self._private:
self._private = val
self.notify('private')
val = new_props.get('id', self._id)
if isinstance(val, str) and self._id is None:
self._id = val
self.notify('id')
val = new_props.get('type', self._type)
if isinstance(val, str) and self._type is None:
self._type = val
self.notify('type')
def object_path(self):
"""Get our dbus object path"""
return self._object_path
def do_get_property(self, pspec):
"""Retrieve a particular property from our property dictionary"""
if pspec.name == 'joined':
return self._joined
if self._get_properties_call is not None:
_logger.debug('%r: Blocking on GetProperties() because someone '
'wants property %s', self, pspec.name)
self._get_properties_call.block()
if pspec.name == 'id':
return self._id
elif pspec.name == 'name':
return self._name
elif pspec.name == 'color':
return self._color
elif pspec.name == 'type':
return self._type
elif pspec.name == 'tags':
return self._tags
elif pspec.name == 'private':
return self._private
def do_set_property(self, pspec, val):
"""Set a particular property in our property dictionary"""
# FIXME: need an asynchronous API to set these properties,
# particularly 'private'
if pspec.name == 'name':
self._name = val
elif pspec.name == 'color':
self._color = val
elif pspec.name == 'tags':
self._tags = val
elif pspec.name == 'private':
self._private = val
else:
raise ValueError('Unknown property %r', pspec.name)
self._publish_properties()
def set_private(self, val, reply_handler, error_handler):
_logger.debug('set_private %r', val)
self._activity.SetProperties({'private': bool(val)},
reply_handler=reply_handler,
error_handler=error_handler)
def get_joined_buddies(self):
"""Retrieve the set of Buddy objects attached to this activity
returns list of presence Buddy objects that we can successfully
create from the buddy object paths that PS has for this activity.
"""
return self._joined_buddies.values()
def get_buddy_by_handle(self, handle):
"""Retrieve the Buddy object given a telepathy handle.
buddy object paths are cached in self._handle_to_buddy_path,
so we can get the buddy without calling PS.
"""
object_path = self._handle_to_buddy_path.get(handle, None)
if object_path:
buddy = self._ps_new_object(object_path)
return buddy
return None
def invite(self, buddy, message, response_cb):
"""Invite the given buddy to join this activity.
The callback will be called with one parameter: None on success,
or an exception on failure.
"""
if not self._joined:
raise RuntimeError('Cannot invite a buddy to an activity that is'
'not shared.')
self.telepathy_text_chan.AddMembers([buddy.contact_handle], message,
dbus_interface=CHANNEL_INTERFACE_GROUP,
reply_handler=partial(self.__invite_cb, response_cb),
error_handler=partial(self.__invite_cb, response_cb))
def __invite_cb(self, response_cb, error=None):
response_cb(error)
def set_up_tubes(self, reply_handler, error_handler):
raise NotImplementedError()
def __joined_cb(self, join_command, error):
_logger.debug('%r: Join finished %r', self, error)
if error is not None:
self.emit('joined', error is None, str(error))
self.telepathy_text_chan = join_command.text_channel
self.telepathy_tubes_chan = join_command.tubes_channel
self._channel_self_handle = join_command.channel_self_handle
self._text_channel_group_flags = join_command.text_channel_group_flags
self._start_tracking_buddies()
self._start_tracking_channel()
def _start_tracking_buddies(self):
group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
group.GetAllMembers(reply_handler=self.__get_all_members_cb,
error_handler=self.__error_handler_cb)
group.connect_to_signal('MembersChanged',
self.__text_channel_members_changed_cb)
def _start_tracking_channel(self):
channel = self.telepathy_text_chan[CHANNEL]
channel.connect_to_signal('Closed', self.__text_channel_closed_cb)
def __get_all_members_cb(self, members, local_pending, remote_pending):
_logger.debug('__get_all_members_cb %r %r', members,
self._text_channel_group_flags)
if self._channel_self_handle in members:
members.remove(self._channel_self_handle)
if not members:
return
self._resolve_handles(members, reply_cb=self._add_initial_buddies)
def _resolve_handles(self, input_handles, reply_cb):
def get_handle_owners_cb(handles):
self.telepathy_conn.InspectHandles(HANDLE_TYPE_CONTACT, handles,
reply_handler=reply_cb,
error_handler=self.__error_handler_cb,
dbus_interface=CONNECTION)
if self._text_channel_group_flags & \
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
group.GetHandleOwners(input_handles,
reply_handler=get_handle_owners_cb,
error_handler=self.__error_handler_cb)
else:
get_handle_owners_cb(input_handles)
def _add_initial_buddies(self, contact_ids):
_logger.debug('__add_initial_buddies %r', contact_ids)
for contact_id in contact_ids:
self._buddies[contact_id] = self._get_buddy(contact_id)
self._joined_buddies[contact_id] = self._get_buddy(contact_id)
# Once we have the initial members, we can finish the join process
self._joined = True
self.emit('joined', True, None)
def __text_channel_members_changed_cb(self, message, added, removed,
local_pending, remote_pending,
actor, reason):
_logger.debug('__text_channel_members_changed_cb %r',
[added, message, added, removed, local_pending,
remote_pending, actor, reason])
if self._channel_self_handle in added:
added.remove(self._channel_self_handle)
if added:
self._resolve_handles(added, reply_cb=self._add_buddies)
if self._channel_self_handle in removed:
removed.remove(self._channel_self_handle)
if removed:
self._resolve_handles(removed, reply_cb=self._remove_buddies)
def _add_buddies(self, contact_ids):
for contact_id in contact_ids:
if contact_id not in self._buddies:
buddy = self._get_buddy(contact_id)
self.emit('buddy-joined', buddy)
self._buddies[contact_id] = buddy
if contact_id not in self._joined_buddies:
self._joined_buddies[contact_id] = buddy
def _remove_buddies(self, contact_ids):
for contact_id in contact_ids:
if contact_id in self._buddies:
buddy = self._get_buddy(contact_id)
self.emit('buddy-left', buddy)
del self._buddies[contact_id]
def _get_buddy(self, contact_id):
if contact_id in self._buddies:
return self._buddies[contact_id]
else:
return Buddy(self._account_path, contact_id)
def join(self):
"""Join this activity.
Emits 'joined' and otherwise does nothing if we're already joined.
"""
if self._join_command is not None:
return
if self._joined:
self.emit('joined', True, None)
return
_logger.debug('%r: joining', self)
self._join_command = _JoinCommand(self.telepathy_conn,
self.room_handle)
self._join_command.connect('finished', self.__joined_cb)
self._join_command.run()
def share(self, share_activity_cb, share_activity_error_cb):
if not self.room_handle is None:
raise ValueError('Already have a room handle')
self._share_command = _ShareCommand(self.telepathy_conn, self._id)
self._share_command.connect('finished',
partial(self.__shared_cb,
share_activity_cb,
share_activity_error_cb))
self._share_command.run()
def __shared_cb(self, share_activity_cb, share_activity_error_cb,
share_command, error):
_logger.debug('%r: Share finished %r', self, error)
if error is None:
self._joined = True
self.room_handle = share_command.room_handle
self.telepathy_text_chan = share_command.text_channel
self.telepathy_tubes_chan = share_command.tubes_channel
self._channel_self_handle = share_command.channel_self_handle
self._text_channel_group_flags = \
share_command.text_channel_group_flags
self._publish_properties()
self._start_tracking_properties()
self._start_tracking_buddies()
self._start_tracking_channel()
share_activity_cb(self)
else:
share_activity_error_cb(self, error)
def _publish_properties(self):
properties = {}
if self._color is not None:
properties['color'] = str(self._color)
if self._name is not None:
properties['name'] = str(self._name)
if self._type is not None:
properties['type'] = self._type
if self._tags is not None:
properties['tags'] = self._tags
properties['private'] = self._private
self.telepathy_conn.SetProperties(
self.room_handle,
properties,
dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
def __share_error_cb(self, share_activity_error_cb, error):
logging.debug('%r: Share failed because: %s', self, error)
share_activity_error_cb(self, error)
# GetChannels() wrapper
def get_channels(self):
"""Retrieve communications channel descriptions for the activity
Returns a tuple containing:
- the D-Bus well-known service name of the connection
(FIXME: this is redundant; in Telepathy it can be derived
from that of the connection)
- the D-Bus object path of the connection
- a list of D-Bus object paths representing the channels
associated with this activity
"""
bus_name = self.telepathy_conn.requested_bus_name
connection_path = self.telepathy_conn.object_path
channels = [self.telepathy_text_chan.object_path,
self.telepathy_tubes_chan.object_path]
_logger.debug('%r: bus name is %s, connection is %s, channels are %r',
self, bus_name, connection_path, channels)
return bus_name, connection_path, channels
# Leaving
def __text_channel_closed_cb(self):
self._joined = False
self.emit('joined', False, 'left activity')
def leave(self):
"""Leave this shared activity"""
_logger.debug('%r: leaving', self)
self.telepathy_text_chan.Close()
class _BaseCommand(gobject.GObject):
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([object])),
}
def __init__(self):
gobject.GObject.__init__(self)
self.text_channel = None
self.text_channel_group_flags = None
self.tubes_channel = None
self.room_handle = None
self.channel_self_handle = None
def run(self):
raise NotImplementedError()
class _ShareCommand(_BaseCommand):
def __init__(self, connection, activity_id):
_BaseCommand.__init__(self)
self._connection = connection
self._activity_id = activity_id
self._finished = False
self._join_command = None
def run(self):
self._connection.RequestHandles(
HANDLE_TYPE_ROOM,
[self._activity_id],
reply_handler=self.__got_handles_cb,
error_handler=self.__error_handler_cb,
dbus_interface=CONNECTION)
def __got_handles_cb(self, handles):
logging.debug('__got_handles_cb %r', handles)
self.room_handle = handles[0]
self._join_command = _JoinCommand(self._connection, self.room_handle)
self._join_command.connect('finished', self.__joined_cb)
self._join_command.run()
def __joined_cb(self, join_command, error):
_logger.debug('%r: Join finished %r', self, error)
if error is not None:
self._finished = True
self.emit('finished', error)
return
self.text_channel = join_command.text_channel
self.text_channel_group_flags = join_command.text_channel_group_flags
self.tubes_channel = join_command.tubes_channel
self.channel_self_handle = join_command.channel_self_handle
self._connection.AddActivity(
self._activity_id,
self.room_handle,
reply_handler=self.__added_activity_cb,
error_handler=self.__error_handler_cb,
dbus_interface=CONN_INTERFACE_BUDDY_INFO)
def __added_activity_cb(self):
self._finished = True
self.emit('finished', None)
def __error_handler_cb(self, error):
self._finished = True
self.emit('finished', error)
class _JoinCommand(_BaseCommand):
def __init__(self, connection, room_handle):
_BaseCommand.__init__(self)
self._connection = connection
self._finished = False
self.room_handle = room_handle
self._global_self_handle = None
def run(self):
if self._finished:
raise RuntimeError('This command has already finished')
self._connection.Get(CONNECTION, 'SelfHandle',
reply_handler=self.__get_self_handle_cb,
error_handler=self.__error_handler_cb,
dbus_interface=PROPERTIES_IFACE)
def __get_self_handle_cb(self, handle):
self._global_self_handle = handle
self._connection.RequestChannel(CHANNEL_TYPE_TEXT,
HANDLE_TYPE_ROOM, self.room_handle, True,
reply_handler=self.__create_text_channel_cb,
error_handler=self.__error_handler_cb,
dbus_interface=CONNECTION)
self._connection.RequestChannel(CHANNEL_TYPE_TUBES,
HANDLE_TYPE_ROOM, self.room_handle, True,
reply_handler=self.__create_tubes_channel_cb,
error_handler=self.__error_handler_cb,
dbus_interface=CONNECTION)
def __create_text_channel_cb(self, channel_path):
Channel(self._connection.requested_bus_name, channel_path,
ready_handler=self.__text_channel_ready_cb)
def __create_tubes_channel_cb(self, channel_path):
Channel(self._connection.requested_bus_name, channel_path,
ready_handler=self.__tubes_channel_ready_cb)
def __error_handler_cb(self, error):
self._finished = True
self.emit('finished', error)
def __tubes_channel_ready_cb(self, channel):
_logger.debug('%r: Tubes channel %r is ready', self, channel)
self.tubes_channel = channel
self._tubes_ready()
def __text_channel_ready_cb(self, channel):
_logger.debug('%r: Text channel %r is ready', self, channel)
self.text_channel = channel
self._tubes_ready()
def _tubes_ready(self):
if self.text_channel is None or \
self.tubes_channel is None:
return
_logger.debug('%r: finished setting up tubes', self)
self._add_self_to_channel()
def __text_channel_group_flags_changed_cb(self, added, removed):
_logger.debug('__text_channel_group_flags_changed_cb %r %r', added,
removed)
self.text_channel_group_flags |= added
self.text_channel_group_flags &= ~removed
def _add_self_to_channel(self):
# FIXME: cope with non-Group channels here if we want to support
# non-OLPC-compatible IMs
group = self.text_channel[CHANNEL_INTERFACE_GROUP]
def got_all_members(members, local_pending, remote_pending):
_logger.debug('got_all_members members %r local_pending %r '
'remote_pending %r', members, local_pending,
remote_pending)
if self.text_channel_group_flags & \
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
self_handle = self.channel_self_handle
else:
self_handle = self._global_self_handle
if self_handle in local_pending:
_logger.debug('%r: We are in local pending - entering', self)
group.AddMembers([self_handle], '',
reply_handler=lambda: None,
error_handler=lambda e: self._join_failed_cb(e,
'got_all_members AddMembers'))
if members:
self.__text_channel_members_changed_cb('', members, (),
(), (), 0, 0)
def got_group_flags(flags):
self.text_channel_group_flags = flags
# by the time we hook this, we need to know the group flags
group.connect_to_signal('MembersChanged',
self.__text_channel_members_changed_cb)
# bootstrap by getting the current state. This is where we find
# out whether anyone was lying to us in their PEP info
group.GetAllMembers(reply_handler=got_all_members,
error_handler=self.__error_handler_cb)
def got_self_handle(channel_self_handle):
self.channel_self_handle = channel_self_handle
group.connect_to_signal('GroupFlagsChanged',
self.__text_channel_group_flags_changed_cb)
group.GetGroupFlags(reply_handler=got_group_flags,
error_handler=self.__error_handler_cb)
group.GetSelfHandle(reply_handler=got_self_handle,
error_handler=self.__error_handler_cb)
def __text_channel_members_changed_cb(self, message, added, removed,
local_pending, remote_pending,
actor, reason):
_logger.debug('__text_channel_members_changed_cb added %r removed %r '
'local_pending %r remote_pending %r channel_self_handle '
'%r', added, removed, local_pending, remote_pending,
self.channel_self_handle)
if self.text_channel_group_flags & \
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
self_handle = self.channel_self_handle
else:
self_handle = self._global_self_handle
if self_handle in added:
if PROPERTIES_INTERFACE not in self.text_channel:
self._finished = True
self.emit('finished', None)
else:
self.text_channel[PROPERTIES_INTERFACE].ListProperties(
reply_handler=self.__list_properties_cb,
error_handler=self.__error_handler_cb)
def __list_properties_cb(self, prop_specs):
# FIXME: invite-only ought to be set on private activities; but
# since only the owner can change invite-only, that would break
# activity scope changes.
props = {
# otherwise buddy resolution breaks
'anonymous': False,
# anyone who knows about the channel can join
'invite-only': False,
# so non-owners can invite others
'invite-restricted': False,
# vanish when there are no members
'persistent': False,
# don't appear in server room lists
'private': True,
}
props_to_set = []
for ident, name, sig_, flags in prop_specs:
value = props.pop(name, None)
if value is not None:
if flags & PROPERTY_FLAG_WRITE:
props_to_set.append((ident, value))
# FIXME: else error, but only if we're creating the room?
# FIXME: if props is nonempty, then we want to set props that aren't
# supported here - raise an error?
if props_to_set:
self.text_channel[PROPERTIES_INTERFACE].SetProperties(
props_to_set, reply_handler=self.__set_properties_cb,
error_handler=self.__error_handler_cb)
else:
self._finished = True
self.emit('finished', None)
def __set_properties_cb(self):
self._finished = True
self.emit('finished', None)
+248
View File
@@ -0,0 +1,248 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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.
"""UI interface to a buddy in the presence service
STABLE.
"""
import logging
import gobject
import dbus
import gconf
from telepathy.interfaces import CONNECTION, \
CONNECTION_INTERFACE_ALIASING, \
CONNECTION_INTERFACE_CONTACTS
from telepathy.constants import HANDLE_TYPE_CONTACT
from sugar.presence.connectionmanager import get_connection_manager
ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
_logger = logging.getLogger('sugar.presence.buddy')
class BaseBuddy(gobject.GObject):
"""UI interface for a Buddy in the presence service
Each buddy interface tracks a set of activities and properties
that can be queried to provide UI controls for manipulating
the presence interface.
Properties Dictionary:
'key': public key,
'nick': nickname ,
'color': color (XXX what format),
'current-activity': (XXX dbus path?),
'owner': (XXX dbus path?),
"""
__gtype_name__ = 'PresenceBaseBuddy'
__gsignals__ = {
'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
}
def __init__(self):
gobject.GObject.__init__(self)
self._key = None
self._nick = None
self._color = None
self._current_activity = None
self._owner = False
self._ip4_address = None
self._tags = None
def get_key(self):
return self._key
def set_key(self, key):
self._key = key
key = gobject.property(type=str, getter=get_key, setter=set_key)
def get_nick(self):
return self._nick
def set_nick(self, nick):
self._nick = nick
nick = gobject.property(type=str, getter=get_nick, setter=set_nick)
def get_color(self):
return self._color
def set_color(self, color):
self._color = color
color = gobject.property(type=str, getter=get_color, setter=set_color)
def get_current_activity(self):
if self._current_activity is None:
return None
for activity in self._activities.values():
if activity.props.id == self._current_activity:
return activity
return None
current_activity = gobject.property(type=object,
getter=get_current_activity)
def get_owner(self):
return self._owner
def set_owner(self, owner):
self._owner = owner
owner = gobject.property(type=bool, getter=get_owner, setter=set_owner,
default=False)
def get_ip4_address(self):
return self._ip4_address
def set_ip4_address(self, ip4_address):
self._ip4_address = ip4_address
ip4_address = gobject.property(type=str, getter=get_ip4_address,
setter=set_ip4_address)
def get_tags(self):
return self._tags
def set_tags(self, tags):
self._tags = tags
tags = gobject.property(type=str, getter=get_tags, setter=set_tags)
def object_path(self):
"""Retrieve our dbus object path"""
return None
class Buddy(BaseBuddy):
__gtype_name__ = 'PresenceBuddy'
def __init__(self, account_path, contact_id):
_logger.debug('Buddy.__init__')
BaseBuddy.__init__(self)
self._account_path = account_path
self.contact_id = contact_id
self.contact_handle = None
connection_manager = get_connection_manager()
connection = connection_manager.get_connection(account_path)
connection_name = connection.object_path.replace('/', '.')[1:]
bus = dbus.SessionBus()
obj = bus.get_object(connection_name, connection.object_path)
handles = obj.RequestHandles(HANDLE_TYPE_CONTACT, [self.contact_id],
dbus_interface=CONNECTION)
self.contact_handle = handles[0]
self._get_properties_call = bus.call_async(
connection_name,
connection.object_path,
CONN_INTERFACE_BUDDY_INFO,
'GetProperties',
'u',
(self.contact_handle,),
reply_handler=self.__got_properties_cb,
error_handler=self.__error_handler_cb,
utf8_strings=True,
byte_arrays=True)
self._get_attributes_call = bus.call_async(
connection_name,
connection.object_path,
CONNECTION_INTERFACE_CONTACTS,
'GetContactAttributes',
'auasb',
([self.contact_handle], [CONNECTION_INTERFACE_ALIASING],
False),
reply_handler=self.__got_attributes_cb,
error_handler=self.__error_handler_cb)
def __got_properties_cb(self, properties):
_logger.debug('__got_properties_cb %r', properties)
self._get_properties_call = None
self._update_properties(properties)
def __got_attributes_cb(self, attributes):
_logger.debug('__got_attributes_cb %r', attributes)
self._get_attributes_call = None
self._update_attributes(attributes[self.contact_handle])
def __error_handler_cb(self, error):
_logger.debug('__error_handler_cb %r', error)
def __properties_changed_cb(self, new_props):
_logger.debug('%r: Buddy properties changed to %r', self, new_props)
self._update_properties(new_props)
def _update_properties(self, properties):
if 'key' in properties:
self.props.key = properties['key']
if 'color' in properties:
self.props.color = properties['color']
if 'current-activity' in properties:
self.props.current_activity = properties['current-activity']
if 'owner' in properties:
self.props.owner = properties['owner']
if 'ip4-address' in properties:
self.props.ip4_address = properties['ip4-address']
if 'tags' in properties:
self.props.tags = properties['tags']
def _update_attributes(self, attributes):
nick_key = CONNECTION_INTERFACE_ALIASING + '/alias'
if nick_key in attributes:
self.props.nick = attributes[nick_key]
def do_get_property(self, pspec):
if pspec.name == 'nick' and self._get_attributes_call is not None:
_logger.debug('%r: Blocking on GetContactAttributes() because '
'someone wants property nick', self)
self._get_attributes_call.block()
elif pspec.name != 'nick' and self._get_properties_call is not None:
_logger.debug('%r: Blocking on GetProperties() because someone '
'wants property %s', self, pspec.name)
self._get_properties_call.block()
return BaseBuddy.do_get_property(self, pspec)
class Owner(BaseBuddy):
__gtype_name__ = 'PresenceOwner'
def __init__(self):
BaseBuddy.__init__(self)
client = gconf.client_get_default()
self.props.nick = client.get_string('/desktop/sugar/user/nick')
self.props.color = client.get_string('/desktop/sugar/user/color')
+119
View File
@@ -0,0 +1,119 @@
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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.
"""
UNSTABLE. It should really be internal to the sugar.presence package.
"""
from functools import partial
import dbus
from dbus import PROPERTIES_IFACE
from telepathy.interfaces import ACCOUNT, \
ACCOUNT_MANAGER
from telepathy.constants import CONNECTION_STATUS_CONNECTED
ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
class Connection(object):
def __init__(self, account_path, connection):
self.account_path = account_path
self.connection = connection
self.connected = False
class ConnectionManager(object):
"""Track available telepathy connections"""
def __init__(self):
self._connections_per_account = {}
bus = dbus.SessionBus()
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
dbus_interface=PROPERTIES_IFACE)
for account_path in account_paths:
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
obj.connect_to_signal('AccountPropertyChanged',
partial(self.__account_property_changed_cb, account_path))
connection_path = obj.Get(ACCOUNT, 'Connection')
if connection_path != '/':
self._track_connection(account_path, connection_path)
def __account_property_changed_cb(self, account_path, properties):
if 'Connection' not in properties:
return
if properties['Connection'] == '/':
if account_path in self._connections_per_account:
del self._connections_per_account[account_path]
else:
self._track_connection(account_path, properties['Connection'])
def _track_connection(self, account_path, connection_path):
connection_name = connection_path.replace('/', '.')[1:]
bus = dbus.SessionBus()
connection = bus.get_object(connection_name, connection_path)
connection.connect_to_signal('StatusChanged',
partial(self.__status_changed_cb, account_path))
self._connections_per_account[account_path] = \
Connection(account_path, connection)
account = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
status = account.Get(ACCOUNT, 'ConnectionStatus')
if status == CONNECTION_STATUS_CONNECTED:
self._connections_per_account[account_path].connected = True
else:
self._connections_per_account[account_path].connected = False
def __status_changed_cb(self, account_path, status, reason):
if status == CONNECTION_STATUS_CONNECTED:
self._connections_per_account[account_path].connected = True
else:
self._connections_per_account[account_path].connected = False
def get_preferred_connection(self):
best_connection = None, None
for account_path, connection in self._connections_per_account.items():
if 'salut' in account_path and connection.connected:
best_connection = account_path, connection.connection
elif 'gabble' in account_path and connection.connected:
best_connection = account_path, connection.connection
break
return best_connection
def get_connection(self, account_path):
return self._connections_per_account[account_path].connection
def get_connections_per_account(self):
return self._connections_per_account
def get_account_for_connection(self, connection_path):
for account_path, connection in self._connections_per_account.items():
if connection.connection.object_path == connection_path:
return account_path
return None
_connection_manager = None
def get_connection_manager():
global _connection_manager
if not _connection_manager:
_connection_manager = ConnectionManager()
return _connection_manager
+266
View File
@@ -0,0 +1,266 @@
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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.
"""
STABLE.
"""
import logging
import gobject
import dbus
import dbus.exceptions
import dbus.glib
from dbus import PROPERTIES_IFACE
from sugar.presence.buddy import Buddy, Owner
from sugar.presence.activity import Activity
from sugar.presence.connectionmanager import get_connection_manager
from telepathy.interfaces import ACCOUNT, \
ACCOUNT_MANAGER, \
CONNECTION
from telepathy.constants import HANDLE_TYPE_CONTACT
_logger = logging.getLogger('sugar.presence.presenceservice')
ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
class PresenceService(gobject.GObject):
"""Provides simplified access to the Telepathy framework to activities"""
__gsignals__ = {
'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT])),
}
def __init__(self):
"""Initialise the service and attempt to connect to events
"""
gobject.GObject.__init__(self)
self._activity_cache = None
self._buddy_cache = {}
def get_activity(self, activity_id, warn_if_none=True):
"""Retrieve single Activity object for the given unique id
activity_id -- unique ID for the activity
returns single Activity object or None if the activity
is not found using GetActivityById on the service
"""
if self._activity_cache is not None:
if self._activity_cache.props.id != activity_id:
raise RuntimeError('Activities can only access their own'
' shared instance')
return self._activity_cache
else:
connection_manager = get_connection_manager()
connections_per_account = \
connection_manager.get_connections_per_account()
for account_path, connection in connections_per_account.items():
if not connection.connected:
continue
logging.debug('Calling GetActivity on %s', account_path)
try:
room_handle = connection.connection.GetActivity(
activity_id,
dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
except dbus.exceptions.DBusException, e:
name = 'org.freedesktop.Telepathy.Error.NotAvailable'
if e.get_dbus_name() == name:
logging.debug("There's no shared activity with the id "
"%s", activity_id)
else:
raise
else:
activity = Activity(account_path, connection.connection,
room_handle=room_handle)
self._activity_cache = activity
return activity
return None
def get_activity_by_handle(self, connection_path, room_handle):
if self._activity_cache is not None:
if self._activity_cache.room_handle != room_handle:
raise RuntimeError('Activities can only access their own'
' shared instance')
return self._activity_cache
else:
connection_manager = get_connection_manager()
account_path = \
connection_manager.get_account_for_connection(connection_path)
connection_name = connection_path.replace('/', '.')[1:]
bus = dbus.SessionBus()
connection = bus.get_object(connection_name, connection_path)
activity = Activity(account_path, connection,
room_handle=room_handle)
self._activity_cache = activity
return activity
def get_buddy(self, account_path, contact_id):
if (account_path, contact_id) in self._buddy_cache:
return self._buddy_cache[(account_path, contact_id)]
buddy = Buddy(account_path, contact_id)
self._buddy_cache[(account_path, contact_id)] = buddy
return buddy
# DEPRECATED
def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
handle):
"""Retrieve single Buddy object for the given public key
:Parameters:
`tp_conn_name` : str
The well-known bus name of a Telepathy connection
`tp_conn_path` : dbus.ObjectPath
The object path of the Telepathy connection
`handle` : int or long
The handle of a Telepathy contact on that connection,
of type HANDLE_TYPE_CONTACT. This may not be a
channel-specific handle.
:Returns: the Buddy object, or None if the buddy is not found
"""
bus = dbus.Bus()
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
dbus_interface=PROPERTIES_IFACE)
for account_path in account_paths:
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
connection_path = obj.Get(ACCOUNT, 'Connection')
if connection_path == tp_conn_path:
connection_name = connection_path.replace('/', '.')[1:]
connection = bus.get_object(connection_name, connection_path)
contact_ids = connection.InspectHandles(HANDLE_TYPE_CONTACT,
[handle],
dbus_interface=CONNECTION)
return self.get_buddy(account_path, contact_ids[0])
raise ValueError('Unknown buddy in connection %s with handle %d',
tp_conn_path, handle)
def get_owner(self):
"""Retrieves the laptop Buddy object."""
return Owner()
def __share_activity_cb(self, activity):
"""Finish sharing the activity
"""
self.emit('activity-shared', True, activity, None)
def __share_activity_error_cb(self, activity, error):
"""Notify with GObject event of unsuccessful sharing of activity
"""
self.emit('activity-shared', False, activity, error)
def share_activity(self, activity, properties=None, private=True):
if properties is None:
properties = {}
if 'id' not in properties:
properties['id'] = activity.get_id()
if 'type' not in properties:
properties['type'] = activity.get_bundle_id()
if 'name' not in properties:
properties['name'] = activity.metadata.get('title', None)
if 'color' not in properties:
properties['color'] = activity.metadata.get('icon-color', None)
properties['private'] = private
if self._activity_cache is not None:
raise ValueError('Activity %s is already tracked',
activity.get_id())
connection_manager = get_connection_manager()
account_path, connection = \
connection_manager.get_preferred_connection()
if connection is None:
self.emit('activity-shared', False, None,
'No active connection available')
return
shared_activity = Activity(account_path, connection,
properties=properties)
self._activity_cache = shared_activity
if shared_activity.props.joined:
raise RuntimeError('Activity %s is already shared.' %
activity.props.id)
shared_activity.share(self.__share_activity_cb,
self.__share_activity_error_cb)
def get_preferred_connection(self):
"""Gets the preferred telepathy connection object that an activity
should use when talking directly to telepathy
returns the bus name and the object path of the Telepathy connection
"""
manager = get_connection_manager()
account_path, connection = manager.get_preferred_connection()
if connection is None:
return None
else:
return connection.requested_bus_name, connection.object_path
# DEPRECATED
def get(self, object_path):
raise NotImplementedError()
# DEPRECATED
def get_activities(self):
raise NotImplementedError()
# DEPRECATED
def get_activities_async(self, reply_handler=None, error_handler=None):
raise NotImplementedError()
# DEPRECATED
def get_buddies(self):
raise NotImplementedError()
# DEPRECATED
def get_buddies_async(self, reply_handler=None, error_handler=None):
raise NotImplementedError()
_ps = None
def get_instance(allow_offline_iface=False):
"""Retrieve this process' view of the PresenceService"""
global _ps
if not _ps:
_ps = PresenceService()
return _ps
+63
View File
@@ -0,0 +1,63 @@
# Copyright (C) 2008 One Laptop Per Child
#
# This program 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.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Subclass of TubeConnection that converts handles to Sugar Buddies
STABLE.
"""
from telepathy.constants import (
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES)
from sugar.presence.tubeconn import TubeConnection
from sugar.presence import presenceservice
class SugarTubeConnection(TubeConnection):
"""Subclass of TubeConnection that converts handles to Sugar Buddies"""
def __new__(cls, conn, tubes_iface, tube_id, address=None,
group_iface=None, mainloop=None):
self = super(SugarTubeConnection, cls).__new__(
cls, conn, tubes_iface, tube_id, address=address,
group_iface=group_iface, mainloop=mainloop)
self._conn = conn
self._group_iface = group_iface
return self
def get_buddy(self, cs_handle):
"""Retrieve a Buddy object given a telepathy handle.
cs_handle: A channel-specific CONTACT type handle.
returns: sugar.presence Buddy object or None
"""
pservice = presenceservice.get_instance()
if self.self_handle == cs_handle:
# It's me, just get my global handle
handle = self._conn.GetSelfHandle()
elif self._group_iface.GetGroupFlags() & \
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
# The group (channel) has channel specific handles
handle = self._group_iface.GetHandleOwners([cs_handle])[0]
else:
# The group does not have channel specific handles
handle = cs_handle
# deal with failure to get the handle owner
if handle == 0:
return None
return pservice.get_buddy_by_telepathy_handle(
self._conn.service_name, self._conn.object_path, handle)
+26
View File
@@ -0,0 +1,26 @@
This is a test of presence.
To test this service we will start up a mock dbus library:
>>> from sugar.testing import mockdbus
>>> import dbus
>>> pres_service = mockdbus.MockService(
... 'org.laptop.Presence', '/org/laptop/Presence', name='pres')
>>> pres_service.install()
>>> pres_interface = dbus.Interface(pres_service, 'org.laptop.Presence')
Then we import the library (second, to make sure it connects to our
mocked system, though the lazy instantiation in get_instance() should
handle it):
>>> from sugar.presence import PresenceService
>>> ps = PresenceService.get_instance()
>>> pres_interface.make_response('getServices', [])
>>> ps.get_services()
Called pres.org.laptop.Presence:getServices()
[]
>>> pres_interface.make_response('getBuddies', [])
>>> ps.get_buddies()
Called pres.org.laptop.Presence:getBuddies()
[]
+114
View File
@@ -0,0 +1,114 @@
# This should eventually land in telepathy-python, so has the same license:
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# This program 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.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
STABLE.
"""
__all__ = ('TubeConnection', )
__docformat__ = 'reStructuredText'
import logging
from dbus.connection import Connection
logger = logging.getLogger('telepathy.tubeconn')
class TubeConnection(Connection):
def __new__(cls, conn, tubes_iface, tube_id, address=None,
group_iface=None, mainloop=None):
# pylint: disable=W0212
# Confused by __new__
if address is None:
address = tubes_iface.GetDBusTubeAddress(tube_id)
self = super(TubeConnection, cls).__new__(cls, address,
mainloop=mainloop)
self._tubes_iface = tubes_iface
self.tube_id = tube_id
self.participants = {}
self.bus_name_to_handle = {}
self._mapping_watches = []
if group_iface is None:
method = conn.GetSelfHandle
else:
method = group_iface.GetSelfHandle
method(reply_handler=self._on_get_self_handle_reply,
error_handler=self._on_get_self_handle_error)
return self
def _on_get_self_handle_reply(self, handle):
# pylint: disable=W0201
# Confused by __new__
self.self_handle = handle
match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
self._on_dbus_names_changed)
self._tubes_iface.GetDBusNames(self.tube_id,
reply_handler=self._on_get_dbus_names_reply,
error_handler=self._on_get_dbus_names_error)
self._dbus_names_changed_match = match
def _on_get_self_handle_error(self, e):
logging.basicConfig()
logger.error('GetSelfHandle failed: %s', e)
def close(self):
self._dbus_names_changed_match.remove()
self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
super(TubeConnection, self).close()
def _on_get_dbus_names_reply(self, names):
self._on_dbus_names_changed(self.tube_id, names, ())
def _on_get_dbus_names_error(self, e):
logging.basicConfig()
logger.error('GetDBusNames failed: %s', e)
def _on_dbus_names_changed(self, tube_id, added, removed):
if tube_id == self.tube_id:
for handle, bus_name in added:
if handle == self.self_handle:
# I've just joined - set my unique name
self.set_unique_name(bus_name)
self.participants[handle] = bus_name
self.bus_name_to_handle[bus_name] = handle
# call the callback while the removed people are still in
# participants, so their bus names are available
for callback in self._mapping_watches:
callback(added, removed)
for handle in removed:
bus_name = self.participants.pop(handle, None)
self.bus_name_to_handle.pop(bus_name, None)
def watch_participants(self, callback):
self._mapping_watches.append(callback)
if self.participants:
# GetDBusNames already returned: fake a participant add event
# immediately
added = []
for k, v in self.participants.iteritems():
added.append((k, v))
callback(added, [])
+238
View File
@@ -0,0 +1,238 @@
# Copyright (C) 2006-2007, 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.
"""User settings/configuration loading.
DEPRECATED. We are using GConf now to store preferences.
"""
import gconf
import os
import logging
from ConfigParser import ConfigParser
from sugar import env
from sugar import util
from sugar.graphics.xocolor import XoColor
_profile = None
class Profile(object):
"""Local user's current options/profile information
User settings were previously stored in an INI-style
configuration file. We moved to gconf now. The deprected
API is kept around to not break activities still using it.
The profile is also responsible for loading the user's
public and private ssh keys from disk.
Attributes:
pubkey -- public ssh key
privkey_hash -- SHA has of the child's public key
"""
def __init__(self, path):
self._pubkey = None
self._privkey_hash = None
def _get_pubkey(self):
if self._pubkey is None:
self._pubkey = self._load_pubkey()
return self._pubkey
pubkey = property(fget=_get_pubkey)
def _get_privkey_hash(self):
if self._privkey_hash is None:
self._privkey_hash = self._hash_private_key()
return self._privkey_hash
privkey_hash = property(fget=_get_privkey_hash)
def is_valid(self):
client = gconf.client_get_default()
nick = client.get_string('/desktop/sugar/user/nick')
color = client.get_string('/desktop/sugar/user/color')
return nick is not '' and \
color is not '' and \
self.pubkey is not None and \
self.privkey_hash is not None
def _load_pubkey(self):
key_path = os.path.join(env.get_profile_path(), 'owner.key.pub')
if not os.path.exists(key_path):
return None
try:
f = open(key_path, 'r')
lines = f.readlines()
f.close()
except IOError:
logging.exception('Error reading public key')
return None
magic = 'ssh-dss '
for l in lines:
l = l.strip()
if not l.startswith(magic):
continue
return l[len(magic):]
else:
logging.error('Error parsing public key.')
return None
def _hash_private_key(self):
key_path = os.path.join(env.get_profile_path(), 'owner.key')
if not os.path.exists(key_path):
return None
try:
f = open(key_path, 'r')
lines = f.readlines()
f.close()
except IOError:
logging.exception('Error reading private key')
return None
key = ""
begin_found = False
end_found = False
for l in lines:
l = l.strip()
if l.startswith('-----BEGIN DSA PRIVATE KEY-----'):
begin_found = True
continue
if l.startswith('-----END DSA PRIVATE KEY-----'):
end_found = True
continue
key += l
if not (len(key) and begin_found and end_found):
logging.error('Error parsing public key.')
return None
# hash it
key_hash = util.sha_data(key)
return util.printable_hash(key_hash)
def convert_profile(self):
cp = ConfigParser()
path = os.path.join(env.get_profile_path(), 'config')
cp.read([path])
client = gconf.client_get_default()
if cp.has_option('Buddy', 'NickName'):
name = cp.get('Buddy', 'NickName')
# decode nickname from ascii-safe chars to unicode
nick = name.decode('utf-8')
client.set_string('/desktop/sugar/user/nick', nick)
if cp.has_option('Buddy', 'Color'):
color = cp.get('Buddy', 'Color')
client.set_string('/desktop/sugar/user/color', color)
if cp.has_option('Jabber', 'Server'):
server = cp.get('Jabber', 'Server')
client.set_string('/desktop/sugar/collaboration/jabber_server',
server)
if cp.has_option('Date', 'Timezone'):
timezone = cp.get('Date', 'Timezone')
client.set_string('/desktop/sugar/date/timezone', timezone)
if cp.has_option('Frame', 'HotCorners'):
delay = float(cp.get('Frame', 'HotCorners'))
client.set_int('/desktop/sugar/frame/corner_delay', int(delay))
if cp.has_option('Frame', 'WarmEdges'):
delay = float(cp.get('Frame', 'WarmEdges'))
client.set_int('/desktop/sugar/frame/edge_delay', int(delay))
if cp.has_option('Server', 'Backup1'):
backup1 = cp.get('Server', 'Backup1')
client.set_string('/desktop/sugar/backup_url', backup1)
if cp.has_option('Sound', 'Volume'):
volume = float(cp.get('Sound', 'Volume'))
client.set_int('/desktop/sugar/sound/volume', int(volume))
if cp.has_option('Power', 'AutomaticPM'):
state = cp.get('Power', 'AutomaticPM')
if state.lower() == 'true':
client.set_bool('/desktop/sugar/power/automatic', True)
if cp.has_option('Power', 'ExtremePM'):
state = cp.get('Power', 'ExtremePM')
if state.lower() == 'true':
client.set_bool('/desktop/sugar/power/extreme', True)
if cp.has_option('Shell', 'FavoritesLayout'):
layout = cp.get('Shell', 'FavoritesLayout')
client.set_string('/desktop/sugar/desktop/favorites_layout',
layout)
del cp
try:
os.unlink(path)
except OSError:
logging.error('Error removing old profile.')
def create_debug_file(self):
path = os.path.join(os.path.expanduser('~/.sugar'), 'debug')
fd = open(path, 'w')
text = '# Uncomment the following lines to turn on many' \
'sugar debugging\n'\
'# log files and features\n'\
'#export LM_DEBUG=net\n' \
'#export GABBLE_DEBUG=all\n' \
'#export GABBLE_LOGFILE=' \
'$HOME/.sugar/default/logs/telepathy-gabble.log\n' \
'#export SALUT_DEBUG=all\n' \
'#export SALUT_LOGFILE=' \
'$HOME/.sugar/default/logs/telepathy-salut.log\n' \
'#export GIBBER_DEBUG=all\n' \
'#export WOCKY_DEBUG=all\n' \
'#export MC_LOGFILE=' \
'$HOME/.sugar/default/logs/mission-control.log\n' \
'#export MC_DEBUG=all\n' \
'#export PRESENCESERVICE_DEBUG=1\n' \
'#export SUGAR_LOGGER_LEVEL=debug\n\n' \
'# Uncomment the following line to enable core dumps\n' \
'#ulimit -c unlimited\n'
fd.write(text)
fd.close()
def get_profile():
global _profile
if not _profile:
path = os.path.join(env.get_profile_path(), 'config')
_profile = Profile(path)
return _profile
def get_nick_name():
client = gconf.client_get_default()
return client.get_string('/desktop/sugar/user/nick')
def get_color():
client = gconf.client_get_default()
color = client.get_string('/desktop/sugar/user/color')
return XoColor(color)
def get_pubkey():
return get_profile().pubkey
+54
View File
@@ -0,0 +1,54 @@
# 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.
"""
UNSTABLE. Used only internally by jarabe.
"""
import os
from sugar import _sugarext
class XSMPClient(_sugarext.SMClientXSMP):
def __init__(self):
_sugarext.SMClientXSMP.__init__(self)
class SessionManager(object):
def __init__(self):
address = _sugarext.xsmp_init()
os.environ['SESSION_MANAGER'] = address
_sugarext.xsmp_run()
self.session = _sugarext.session_create_global()
def start(self):
self.session.start()
self.session.connect('shutdown_completed',
self.__shutdown_completed_cb)
def initiate_shutdown(self):
self.session.initiate_shutdown()
def shutdown_completed(self):
_sugarext.xsmp_shutdown()
def __shutdown_completed_cb(self, session):
self.shutdown_completed()
+120
View File
@@ -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;
}
+63
View File
@@ -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__ */
+287
View File
@@ -0,0 +1,287 @@
/*
* Copyright (C) 2006-2007, 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 <X11/X.h>
#include <gdk/gdkscreen.h>
#include <gdk/gdkx.h>
#include <gdk/gdk.h>
#include "sugar-key-grabber.h"
#include "eggaccelerators.h"
#include "sugar-marshal.h"
/* we exclude shift, GDK_CONTROL_MASK and GDK_MOD1_MASK since we know what
these modifiers mean
these are the mods whose combinations are bound by the keygrabbing code */
#define IGNORED_MODS (0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | \
GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK)
/* these are the ones we actually use for global keys, we always only check
* for these set */
#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)
enum {
KEY_PRESSED,
KEY_RELEASED,
N_SIGNALS
};
typedef struct {
char *key;
guint keysym;
guint state;
guint keycode;
} Key;
G_DEFINE_TYPE(SugarKeyGrabber, sugar_key_grabber, G_TYPE_OBJECT)
static guint signals[N_SIGNALS];
static void
free_key_info(Key *key_info)
{
g_free(key_info->key);
g_free(key_info);
}
static void
sugar_key_grabber_dispose (GObject *object)
{
SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(object);
if (grabber->keys) {
g_list_foreach(grabber->keys, (GFunc)free_key_info, NULL);
g_list_free(grabber->keys);
grabber->keys = NULL;
}
}
static void
sugar_key_grabber_class_init(SugarKeyGrabberClass *grabber_class)
{
GObjectClass *g_object_class = G_OBJECT_CLASS (grabber_class);
g_object_class->dispose = sugar_key_grabber_dispose;
signals[KEY_PRESSED] = g_signal_new ("key-pressed",
G_TYPE_FROM_CLASS (grabber_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (SugarKeyGrabberClass, key_pressed),
NULL, NULL,
sugar_marshal_BOOLEAN__UINT_UINT_UINT,
G_TYPE_BOOLEAN, 3,
G_TYPE_UINT,
G_TYPE_UINT,
G_TYPE_UINT);
signals[KEY_RELEASED] = g_signal_new ("key-released",
G_TYPE_FROM_CLASS (grabber_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (SugarKeyGrabberClass, key_released),
NULL, NULL,
sugar_marshal_BOOLEAN__UINT_UINT_UINT,
G_TYPE_BOOLEAN, 3,
G_TYPE_UINT,
G_TYPE_UINT,
G_TYPE_UINT);
}
char *
sugar_key_grabber_get_key(SugarKeyGrabber *grabber, guint keycode, guint state)
{
GList *l;
for (l = grabber->keys; l != NULL; l = l->next) {
Key *keyinfo = (Key *)l->data;
if ((keyinfo->keycode == keycode) &&
((state & USED_MODS) == keyinfo->state)) {
return g_strdup(keyinfo->key);
}
}
return NULL;
}
static GdkFilterReturn
filter_events(GdkXEvent *xevent, GdkEvent *event, gpointer data)
{
SugarKeyGrabber *grabber = (SugarKeyGrabber *)data;
XEvent *xev = (XEvent *)xevent;
if (xev->type == KeyRelease) {
int return_value;
g_signal_emit (grabber, signals[KEY_RELEASED], 0, xev->xkey.keycode,
xev->xkey.state, xev->xkey.time, &return_value);
if(return_value)
return GDK_FILTER_REMOVE;
}
if (xev->type == KeyPress) {
int return_value;
g_signal_emit (grabber, signals[KEY_PRESSED], 0, xev->xkey.keycode,
xev->xkey.state, xev->xkey.time, &return_value);
if(return_value)
return GDK_FILTER_REMOVE;
}
return GDK_FILTER_CONTINUE;
}
static void
sugar_key_grabber_init(SugarKeyGrabber *grabber)
{
GdkScreen *screen;
screen = gdk_screen_get_default();
grabber->root = gdk_screen_get_root_window(screen);
grabber->keys = NULL;
gdk_window_add_filter(grabber->root, filter_events, grabber);
}
/* grab_key and grab_key_real are from
* gnome-control-center/gnome-settings-daemon/gnome-settings-multimedia-keys.c
*/
static void
grab_key_real (Key *key, GdkWindow *root, gboolean grab, int result)
{
if (grab)
XGrabKey (GDK_DISPLAY(), key->keycode, (result | key->state),
GDK_WINDOW_XID (root), True, GrabModeAsync, GrabModeAsync);
else
XUngrabKey(GDK_DISPLAY(), key->keycode, (result | key->state),
GDK_WINDOW_XID (root));
}
#define N_BITS 32
static void
grab_key (SugarKeyGrabber *grabber, Key *key, gboolean grab)
{
int indexes[N_BITS];/*indexes of bits we need to flip*/
int i, bit, bits_set_cnt;
int uppervalue;
guint mask_to_traverse = IGNORED_MODS & ~key->state & GDK_MODIFIER_MASK;
bit = 0;
for (i = 0; i < N_BITS; i++) {
if (mask_to_traverse & (1<<i))
indexes[bit++]=i;
}
bits_set_cnt = bit;
uppervalue = 1<<bits_set_cnt;
for (i = 0; i < uppervalue; i++) {
int j, result = 0;
for (j = 0; j < bits_set_cnt; j++) {
if (i & (1<<j))
result |= (1<<indexes[j]);
}
grab_key_real (key, grabber->root, grab, result);
}
}
void
sugar_key_grabber_grab_keys(SugarKeyGrabber *grabber, const char **keys)
{
const char **cur = keys;
const char *key;
Key *keyinfo = NULL;
int min_keycodes, max_keycodes;
XDisplayKeycodes(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
&min_keycodes, &max_keycodes);
while (*cur != NULL) {
key = *cur;
cur += 1;
keyinfo = g_new0 (Key, 1);
keyinfo->key = g_strdup(key);
if (!egg_accelerator_parse_virtual (key, &keyinfo->keysym,
&keyinfo->keycode,
&keyinfo->state)) {
g_warning ("Invalid key specified: %s", key);
continue;
}
if (keyinfo->keycode < min_keycodes || keyinfo->keycode > max_keycodes) {
g_warning ("Keycode out of bounds: %d for key %s", keyinfo->keycode, key);
continue;
}
gdk_error_trap_push();
grab_key(grabber, keyinfo, TRUE);
gdk_flush();
gint error_code = gdk_error_trap_pop ();
if(!error_code)
grabber->keys = g_list_append(grabber->keys, keyinfo);
else if(error_code == BadAccess)
g_warning ("Grab failed, another application may already have access to key '%s'", key);
else if(error_code == BadValue)
g_warning ("Grab failed, invalid key %s specified. keysym: %u keycode: %u state: %u",
key, keyinfo->keysym, keyinfo->keycode, keyinfo->state);
else
g_warning ("Grab failed for key '%s' for unknown reason '%d'", key, error_code);
}
}
gboolean
sugar_key_grabber_is_modifier(SugarKeyGrabber *grabber, guint keycode, guint mask)
{
Display *xdisplay;
XModifierKeymap *modmap;
gint start, end, i, mod_index;
gboolean is_modifier = FALSE;
xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
modmap = XGetModifierMapping(xdisplay);
if (mask != -1) {
mod_index = 0;
mask = mask >> 1;
while (mask != 0) {
mask = mask >> 1;
mod_index += 1;
}
start = mod_index * modmap->max_keypermod;
end = (mod_index + 1) * modmap->max_keypermod;
} else {
start = 0;
end = 8 * modmap->max_keypermod;
}
for (i = start; i < end; i++) {
if (keycode == modmap->modifiermap[i]) {
is_modifier = TRUE;
break;
}
}
XFreeModifiermap (modmap);
return is_modifier;
}
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2006-2007, 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_KEY_GRABBER_H__
#define __SUGAR_KEY_GRABBER_H__
#include <glib-object.h>
#include <gdk/gdkwindow.h>
G_BEGIN_DECLS
typedef struct _SugarKeyGrabber SugarKeyGrabber;
typedef struct _SugarKeyGrabberClass SugarKeyGrabberClass;
typedef struct _SugarKeyGrabberPrivate SugarKeyGrabberPrivate;
#define SUGAR_TYPE_KEY_GRABBER (sugar_key_grabber_get_type())
#define SUGAR_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabber))
#define SUGAR_KEY_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
#define SUGAR_IS_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_KEY_GRABBER))
#define SUGAR_IS_KEYGRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_KEY_GRABBER))
#define SUGAR_KEY_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
struct _SugarKeyGrabber {
GObject base_instance;
GdkWindow *root;
GList *keys;
};
struct _SugarKeyGrabberClass {
GObjectClass base_class;
gboolean (* key_pressed) (SugarKeyGrabber *grabber,
guint keycode,
guint state);
gboolean (* key_released) (SugarKeyGrabber *grabber,
guint keycode,
guint state);
};
GType sugar_key_grabber_get_type (void);
void sugar_key_grabber_grab_keys (SugarKeyGrabber *grabber,
const char **keys);
char *sugar_key_grabber_get_key (SugarKeyGrabber *grabber,
guint keycode,
guint state);
gboolean sugar_key_grabber_is_modifier (SugarKeyGrabber *grabber,
guint keycode,
guint mask);
G_END_DECLS
#endif /* __SUGAR_KEY_GRABBER_H__ */
+1
View File
@@ -0,0 +1 @@
BOOLEAN:UINT,UINT,UINT
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2006-2007, 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 <gtk/gtkwindow.h>
#include "sugar-menu.h"
static void sugar_menu_class_init (SugarMenuClass *menu_class);
static void sugar_menu_init (SugarMenu *menu);
G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU)
void
sugar_menu_set_active(SugarMenu *menu, gboolean active)
{
GTK_MENU_SHELL(menu)->active = active;
}
void
sugar_menu_embed(SugarMenu *menu, GtkContainer *parent)
{
menu->orig_toplevel = GTK_MENU(menu)->toplevel;
GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
}
void
sugar_menu_unembed(SugarMenu *menu)
{
if (menu->orig_toplevel) {
GTK_MENU(menu)->toplevel = menu->orig_toplevel;
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel));
}
}
static void
sugar_menu_class_init(SugarMenuClass *menu_class)
{
}
static void
sugar_menu_init(SugarMenu *menu)
{
menu->orig_toplevel = NULL;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2006-2007, 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_MENU_H__
#define __SUGAR_MENU_H__
#include <gtk/gtkmenu.h>
G_BEGIN_DECLS
typedef struct _SugarMenu SugarMenu;
typedef struct _SugarMenuClass SugarMenuClass;
#define SUGAR_TYPE_MENU (sugar_menu_get_type())
#define SUGAR_MENU(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_MENU, SugarMenu))
#define SUGAR_MENU_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_MENU, SugarMenuClass))
#define SUGAR_IS_MENU(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_MENU))
#define SUGAR_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_MENU))
#define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass))
struct _SugarMenu {
GtkMenu base_instance;
GtkWidget *orig_toplevel;
int min_width;
};
struct _SugarMenuClass {
GtkMenuClass base_class;
};
GType sugar_menu_get_type (void);
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
#endif /* __SUGAR_MENU_H__ */
+354
View File
@@ -0,0 +1,354 @@
"""Various utility functions"""
# Copyright (C) 2006-2007 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.
"""
UNSTABLE. We have been adding helpers randomly to this module.
"""
import os
import time
import hashlib
import random
import binascii
import gettext
import tempfile
import logging
import atexit
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def printable_hash(in_hash):
"""Convert binary hash data into printable characters."""
printable = ""
for char in in_hash:
printable = printable + binascii.b2a_hex(char)
return printable
def sha_data(data):
"""sha1 hash some bytes."""
sha_hash = hashlib.sha1()
sha_hash.update(data)
return sha_hash.digest()
def unique_id(data=''):
"""Generate a likely-unique ID for whatever purpose
data -- suffix appended to working data before hashing
Returns a 40-character string with hexidecimal digits
representing an SHA hash of the time, a random digit
within a constrained range and the data passed.
Note: these are *not* crypotographically secure or
globally unique identifiers. While they are likely
to be unique-enough, no attempt is made to make
perfectly unique values.
"""
data_string = '%s%s%s' % (time.time(), random.randint(10000, 100000), data)
return printable_hash(sha_data(data_string))
ACTIVITY_ID_LEN = 40
def is_hex(s):
try:
int(s, 16)
except ValueError:
return False
return True
def validate_activity_id(actid):
"""Validate an activity ID."""
if not isinstance(actid, (str, unicode)):
return False
if len(actid) != ACTIVITY_ID_LEN:
return False
if not is_hex(actid):
return False
return True
def set_proc_title(title):
"""Sets the process title so ps and top show more
descriptive names. This does not modify argv[0]
and only the first 15 characters will be shown.
title -- the title you wish to change the process
title to
Returns True on success. We don't raise exceptions
because if something goes wrong here it is not a big
deal as this is intended as a nice thing to have for
debugging
"""
try:
import ctypes
libc = ctypes.CDLL('libc.so.6')
libc.prctl(15, str(title), 0, 0, 0)
return True
except Exception:
return False
class Node(object):
__slots__ = ['prev', 'next', 'me']
def __init__(self, prev, me):
self.prev = prev
self.me = me
self.next = None
class LRU:
"""
Implementation of a length-limited O(1) LRU queue.
Built for and used by PyPE:
http://pype.sourceforge.net
Copyright 2003 Josiah Carlson.
"""
def __init__(self, count, pairs=[]):
# pylint: disable=W0102,W0612
self.count = max(count, 1)
self.d = {}
self.first = None
self.last = None
for key, value in pairs:
self[key] = value
def __contains__(self, obj):
return obj in self.d
def __getitem__(self, obj):
a = self.d[obj].me
self[a[0]] = a[1]
return a[1]
def __setitem__(self, obj, val):
if obj in self.d:
del self[obj]
nobj = Node(self.last, (obj, val))
if self.first is None:
self.first = nobj
if self.last:
self.last.next = nobj
self.last = nobj
self.d[obj] = nobj
if len(self.d) > self.count:
if self.first == self.last:
self.first = None
self.last = None
return
a = self.first
a.next.prev = None
self.first = a.next
a.next = None
del self.d[a.me[0]]
del a
def __delitem__(self, obj):
nobj = self.d[obj]
if nobj.prev:
nobj.prev.next = nobj.next
else:
self.first = nobj.next
if nobj.next:
nobj.next.prev = nobj.prev
else:
self.last = nobj.prev
del self.d[obj]
def __iter__(self):
cur = self.first
while cur != None:
cur2 = cur.next
yield cur.me[1]
cur = cur2
def iteritems(self):
cur = self.first
while cur != None:
cur2 = cur.next
yield cur.me
cur = cur2
def iterkeys(self):
return iter(self.d)
def itervalues(self):
for i_, j in self.iteritems():
yield j
def keys(self):
return self.d.keys()
units = [['%d year', '%d years', 356 * 24 * 60 * 60],
['%d month', '%d months', 30 * 24 * 60 * 60],
['%d week', '%d weeks', 7 * 24 * 60 * 60],
['%d day', '%d days', 24 * 60 * 60],
['%d hour', '%d hours', 60 * 60],
['%d minute', '%d minutes', 60]]
AND = _(' and ')
COMMA = _(', ')
# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
NOW = _('Seconds ago')
# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
ELAPSED = _('%s ago')
# Explanation of the following hack:
# The xgettext utility extracts plural forms by reading the strings included as
# parameters of ngettext(). As our plurals are not passed to ngettext()
# straight away because there needs to be a calculation before we know which
# strings need to be used, then we need to call ngettext() in a fake way so
# xgettext will pick them up as plurals.
def ngettext(singular, plural, n):
pass
# TRANS: Relative dates (eg. 1 month and 5 days).
ngettext('%d year', '%d years', 1)
ngettext('%d month', '%d months', 1)
ngettext('%d week', '%d weeks', 1)
ngettext('%d day', '%d days', 1)
ngettext('%d hour', '%d hours', 1)
ngettext('%d minute', '%d minutes', 1)
del ngettext
# End of plurals hack
# gettext perfs hack (#7959)
_i18n_timestamps_cache = LRU(60)
def timestamp_to_elapsed_string(timestamp, max_levels=2):
levels = 0
time_period = ''
elapsed_seconds = int(time.time() - timestamp)
for name_singular, name_plural, factor in units:
elapsed_units = elapsed_seconds / factor
if elapsed_units > 0:
if levels > 0:
time_period += COMMA
key = ''.join((os.environ['LANG'], name_singular,
str(elapsed_units)))
if key in _i18n_timestamps_cache:
time_period += _i18n_timestamps_cache[key]
else:
tmp = gettext.dngettext('sugar-toolkit',
name_singular,
name_plural,
elapsed_units)
# FIXME: This is a hack so we don't crash when a translation
# doesn't contain the expected number of placeholders (#2354)
try:
translation = tmp % elapsed_units
except TypeError:
translation = tmp
_i18n_timestamps_cache[key] = translation
time_period += translation
elapsed_seconds -= elapsed_units * factor
if time_period != '':
levels += 1
if levels == max_levels:
break
if levels == 0:
return NOW
return ELAPSED % time_period
_tracked_paths = {}
class TempFilePath(str):
def __new__(cls, path=None):
if path is None:
fd, path = tempfile.mkstemp()
os.close(fd)
logging.debug('TempFilePath created %r', path)
if path in _tracked_paths:
_tracked_paths[path] += 1
else:
_tracked_paths[path] = 1
return str.__new__(cls, path)
def __del__(self):
if _tracked_paths[self] == 1:
del _tracked_paths[self]
if os.path.exists(self):
os.unlink(self)
logging.debug('TempFilePath deleted %r', self)
else:
logging.warning('TempFilePath already deleted %r', self)
else:
_tracked_paths[self] -= 1
def _cleanup_temp_files():
logging.debug('_cleanup_temp_files')
for path in _tracked_paths.keys():
try:
os.unlink(path)
except:
# pylint: disable=W0702
logging.exception('Exception occured in _cleanup_temp_files')
atexit.register(_cleanup_temp_files)
def format_size(size):
if not size:
return _('Empty')
elif size < 1024:
return _('%d B') % size
elif size < 1024 ** 2:
return _('%d KB') % (size / 1024)
elif size < 1024 ** 3:
return _('%d MB') % (size / 1024 ** 2)
else:
return _('%d GB') % (size / 1024 ** 3)
+88
View File
@@ -0,0 +1,88 @@
# Copyright (C) 2007, 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.
"""
UNSTABLE. Used only internally by Activity and jarabe.
"""
import gtk
import logging
def _property_get_trapped(window, prop, prop_type):
gtk.gdk.error_trap_push()
prop_info = window.property_get(prop, prop_type)
# We just log a message
error = gtk.gdk.error_trap_pop()
if error:
logging.debug('Received X Error (%i) while getting '
'a property on a window', error)
return prop_info
def _property_change_trapped(window, prop, prop_type, format, mode, data):
# pylint: disable=W0622
gtk.gdk.error_trap_push()
window.property_change(prop, prop_type, format, mode, data)
error = gtk.gdk.error_trap_pop()
if error:
logging.debug('Received X Error (%i) while setting '
'a property on a window', error)
raise RuntimeError('Received X Error (%i) while setting '
'a property on a window' % error)
def get_activity_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
prop_info = _property_get_trapped(window, '_SUGAR_ACTIVITY_ID', 'STRING')
if prop_info is None:
return None
else:
return prop_info[2]
def get_bundle_id(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
prop_info = _property_get_trapped(window, '_SUGAR_BUNDLE_ID', 'STRING')
if prop_info is None:
return None
else:
return prop_info[2]
def get_sugar_window_type(wnck_window):
window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
prop_info = _property_get_trapped(window, '_SUGAR_WINDOW_TYPE', 'STRING')
if prop_info is None:
return None
else:
return prop_info[2]
def set_activity_id(window, activity_id):
_property_change_trapped(window, '_SUGAR_ACTIVITY_ID', 'STRING', 8,
gtk.gdk.PROP_MODE_REPLACE, activity_id)
def set_bundle_id(window, bundle_id):
_property_change_trapped(window, '_SUGAR_BUNDLE_ID', 'STRING', 8,
gtk.gdk.PROP_MODE_REPLACE, bundle_id)