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:
@@ -0,0 +1,4 @@
|
||||
sugar-marshal.c
|
||||
sugar-marshal.h
|
||||
_sugarext.c
|
||||
_sugarext.c
|
||||
@@ -0,0 +1 @@
|
||||
LGPL
|
||||
@@ -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
|
||||
@@ -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*")
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
%%
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
sugardir = $(pythondir)/sugar3/bundle
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
bundle.py \
|
||||
activitybundle.py \
|
||||
bundleversion.py \
|
||||
contentbundle.py
|
||||
@@ -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.
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,4 @@
|
||||
sugardir = $(pythondir)/sugar3/datastore
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
datastore.py
|
||||
@@ -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.
|
||||
@@ -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'))
|
||||
@@ -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 "<Control>a" or "<Shift><Alt>F1" or
|
||||
* "<Release>z" (the last one is for key release). The parser
|
||||
* is fairly liberal and allows lower or upper case, and also
|
||||
* abbreviations such as "<Ctl>" and "<Ctrl>".
|
||||
*
|
||||
* 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 "<Control>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;
|
||||
}
|
||||
@@ -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
@@ -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__ */
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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]
|
||||
@@ -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
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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.
|
||||
*/
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
sugardir = $(pythondir)/sugar3/presence
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
buddy.py \
|
||||
connectionmanager.py \
|
||||
sugartubeconn.py \
|
||||
tubeconn.py \
|
||||
presenceservice.py
|
||||
|
||||
@@ -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".
|
||||
"""
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
[]
|
||||
|
||||
@@ -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, [])
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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__ */
|
||||
@@ -0,0 +1 @@
|
||||
BOOLEAN:UINT,UINT,UINT
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user