Merge branch 'master' of git+ssh://mcfletch@dev.laptop.org/git/sugar

This commit is contained in:
Mike C. Fletcher 2007-04-21 14:05:08 -04:00
commit df4919de2f
73 changed files with 2200 additions and 1149 deletions

1
.gitignore vendored
View File

@ -54,3 +54,4 @@ services/clipboard/org.laptop.Clipboard.service
services/console/org.laptop.sugar.Console.service services/console/org.laptop.sugar.Console.service
services/presence/org.laptop.Sugar.Presence.service services/presence/org.laptop.Sugar.Presence.service
bin/sugar bin/sugar
shell/extensions/_extensions.c

View File

@ -1,3 +1,6 @@
sugardir = $(pkgdatadir)/bin
sugar_SCRIPTS = sugar-activity-factory
bin_SCRIPTS = \ bin_SCRIPTS = \
sugar \ sugar \
sugar-activity \ sugar-activity \
@ -6,6 +9,7 @@ bin_SCRIPTS = \
EXTRA_DIST = \ EXTRA_DIST = \
$(bin_SCRIPTS) \ $(bin_SCRIPTS) \
$(sugar_SCRIPTS) \
sugar.in sugar.in
DISTCLEANFILES = \ DISTCLEANFILES = \

View File

@ -1,2 +1,2 @@
export GTK2_RC_FILES=@prefix@/share/sugar/gtkrc export GTK2_RC_FILES=@prefix@/share/sugar/data/gtkrc
dbus-launch --exit-with-session sugar-shell dbus-launch --exit-with-session sugar-shell

View File

@ -0,0 +1,190 @@
#include <config.h>
#ifdef HAVE_GECKO_1_9
#include <stdio.h>
#include <gtkmozembed.h>
#include <gtkmozembed_internal.h>
#include <nsIRequest.h>
#include <nsNetUtil.h>
#include <nsISeekableStream.h>
#include <nsIHttpChannel.h>
#include <nsIUploadChannel.h>
#include <nsIWebBrowser.h>
#include <nsISHistory.h>
#include <nsIHistoryEntry.h>
#include <nsISHEntry.h>
#include <nsIInputStream.h>
#include <nsIWebNavigation.h>
#include "GeckoBrowserPersist.h"
GeckoBrowserPersist::GeckoBrowserPersist(SugarBrowser *browser)
: mBrowser(browser)
{
}
GeckoBrowserPersist::~GeckoBrowserPersist()
{
}
static nsresult
NewURI(const char *uri, nsIURI **result)
{
nsresult rv;
nsCOMPtr<nsIServiceManager> mgr;
NS_GetServiceManager (getter_AddRefs (mgr));
NS_ENSURE_TRUE(mgr, FALSE);
nsCOMPtr<nsIIOService> ioService;
rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
NS_GET_IID (nsIIOService),
getter_AddRefs(ioService));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCString cSpec(uri);
return ioService->NewURI (cSpec, nsnull, nsnull, result);
}
static nsresult
GetPostData(SugarBrowser *browser, nsIInputStream **postData)
{
#ifdef HAVE_NS_WEB_BROWSER
nsCOMPtr<nsIWebBrowser> webBrowser;
gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
getter_AddRefs(webBrowser));
NS_ENSURE_TRUE(webBrowser, NS_ERROR_FAILURE);
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(webBrowser));
NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
PRInt32 sindex;
nsCOMPtr<nsISHistory> sessionHistory;
webNav->GetSessionHistory(getter_AddRefs(sessionHistory));
NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
nsCOMPtr<nsIHistoryEntry> entry;
sessionHistory->GetIndex(&sindex);
sessionHistory->GetEntryAtIndex(sindex, PR_FALSE, getter_AddRefs(entry));
nsCOMPtr<nsISHEntry> shEntry(do_QueryInterface(entry));
if (shEntry) {
shEntry->GetPostData(postData);
}
return NS_OK;
#endif
return NS_ERROR_NOT_IMPLEMENTED;
}
bool
GeckoBrowserPersist::SaveURI(const char *uri, const char *filename)
{
nsresult rv;
nsCOMPtr<nsIURI> sourceURI;
rv = NewURI(uri, getter_AddRefs(sourceURI));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCOMPtr<nsILocalFile> destFile = do_CreateInstance("@mozilla.org/file/local;1");
NS_ENSURE_TRUE(destFile, FALSE);
destFile->InitWithNativePath(nsCString(filename));
nsCOMPtr<nsIInputStream> postData;
GetPostData(mBrowser, getter_AddRefs(postData));
nsCOMPtr<nsIChannel> inputChannel;
rv = NS_NewChannel(getter_AddRefs(inputChannel), sourceURI,
nsnull, nsnull, nsnull, nsIRequest::LOAD_NORMAL);
NS_ENSURE_SUCCESS(rv, FALSE);
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
if (httpChannel) {
nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(postData));
if (stream)
{
// Rewind the postdata stream
stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
// Attach the postdata to the http channel
uploadChannel->SetUploadStream(postData, EmptyCString(), -1);
}
}
nsCOMPtr<nsIInputStream> stream;
rv = inputChannel->Open(getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCOMPtr<nsIFileOutputStream> fileOutputStream =
do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, FALSE);
rv = fileOutputStream->Init(destFile, -1, -1, 0);
NS_ENSURE_SUCCESS(rv, FALSE);
// Read data from the input and write to the output
char buffer[8192];
PRUint32 bytesRead;
PRUint32 bytesRemaining;
PRBool cancel = PR_FALSE;
PRBool readError;
rv = stream->Available(&bytesRemaining);
NS_ENSURE_SUCCESS(rv, FALSE);
while (!cancel && bytesRemaining)
{
readError = PR_TRUE;
rv = stream->Read(buffer, PR_MIN(sizeof(buffer), bytesRemaining), &bytesRead);
if (NS_SUCCEEDED(rv))
{
readError = PR_FALSE;
// Write out the data until something goes wrong, or, it is
// all written. We loop because for some errors (e.g., disk
// full), we get NS_OK with some bytes written, then an error.
// So, we want to write again in that case to get the actual
// error code.
const char *bufPtr = buffer; // Where to write from.
while (NS_SUCCEEDED(rv) && bytesRead)
{
PRUint32 bytesWritten = 0;
rv = fileOutputStream->Write(bufPtr, bytesRead, &bytesWritten);
if (NS_SUCCEEDED(rv))
{
bytesRead -= bytesWritten;
bufPtr += bytesWritten;
bytesRemaining -= bytesWritten;
// Force an error if (for some reason) we get NS_OK but
// no bytes written.
if (!bytesWritten)
{
rv = NS_ERROR_FAILURE;
cancel = PR_TRUE;
}
}
else
{
// Disaster - can't write out the bytes - disk full / permission?
cancel = PR_TRUE;
}
}
}
else
{
// Disaster - can't read the bytes - broken link / file error?
cancel = PR_TRUE;
}
}
NS_ENSURE_SUCCESS(rv, FALSE);
stream->Close();
return TRUE;
}
#endif

View File

@ -0,0 +1,19 @@
#ifndef __GECKO_BROWSER_PERSIST_H__
#define __GECKO_BROWSER_PERSIST_H__
#include "sugar-browser.h"
class GeckoBrowserPersist
{
public:
GeckoBrowserPersist(SugarBrowser *browser);
~GeckoBrowserPersist();
bool SaveURI(const char *uri, const char *filename);
private:
SugarBrowser *mBrowser;
protected:
/* additional members */
};
#endif // __GECKO_BROWSER_PERSIST_H__

View File

@ -0,0 +1,241 @@
#include <config.h>
#ifdef HAVE_GECKO_1_9
#include <unistd.h>
#include <glib.h>
#include <imgICache.h>
#include <nsComponentManagerUtils.h>
#include <nsCOMPtr.h>
#include <nsIDOMHTMLElement.h>
#include <nsIInterfaceRequestorUtils.h>
#include <nsIIOService.h>
#include <nsILocalFile.h>
#include <nsIMIMEHeaderParam.h>
#include <nsIProperties.h>
#include <nsISupportsPrimitives.h>
#include <nsIURI.h>
#include <nsIURL.h>
#include <nsServiceManagerUtils.h>
#include <nsStringAPI.h>
#include "GeckoDocumentObject.h"
#include "GeckoBrowserPersist.h"
GeckoDocumentObject::GeckoDocumentObject(SugarBrowser *browser, nsIDOMNode *node)
: mBrowser(browser),
mNode(node),
mImage(NULL)
{
}
GeckoDocumentObject::~GeckoDocumentObject()
{
}
bool GeckoDocumentObject::IsImage()
{
if(mImage) {
return true;
}
nsresult rv;
PRUint16 type;
rv = mNode->GetNodeType(&type);
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mNode);
if ((nsIDOMNode::ELEMENT_NODE == type) && element) {
nsString uTag;
rv = element->GetLocalName(uTag);
if(NS_FAILED(rv)) return rv;
nsCString tag;
NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag);
if (g_ascii_strcasecmp (tag.get(), "img") == 0) {
nsCOMPtr <nsIDOMHTMLImageElement> imagePtr;
imagePtr = do_QueryInterface(mNode, &rv);
if(NS_FAILED(rv)) return rv;
mImage = imagePtr;
return true;
}
}
return false;
}
static nsresult
NewURI(const char *uri, nsIURI **result)
{
nsresult rv;
nsCOMPtr<nsIServiceManager> mgr;
NS_GetServiceManager (getter_AddRefs (mgr));
NS_ENSURE_TRUE(mgr, FALSE);
nsCOMPtr<nsIIOService> ioService;
rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
NS_GET_IID (nsIIOService),
getter_AddRefs(ioService));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCString cSpec(uri);
return ioService->NewURI (cSpec, nsnull, nsnull, result);
}
static nsresult
FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName)
{
nsresult rv;
nsCString fallbackCharset;
nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
do_GetService("@mozilla.org/network/mime-hdrparam;1");
NS_ENSURE_TRUE(mimehdrpar, NS_ERROR_FAILURE);
nsString aFileName;
rv = mimehdrpar->GetParameter (contentDisposition, "filename",
fallbackCharset, PR_TRUE, nsnull,
aFileName);
if (NS_FAILED(rv) || !fileName.Length()) {
rv = mimehdrpar->GetParameter (contentDisposition, "name",
fallbackCharset, PR_TRUE, nsnull,
aFileName);
}
if (NS_SUCCEEDED(rv) && fileName.Length()) {
NS_UTF16ToCString (aFileName, NS_CSTRING_ENCODING_UTF8, fileName);
}
return NS_OK;
}
static nsresult
GetImageProperties(char *imgURIStr, nsIProperties **properties)
{
nsresult rv;
nsCOMPtr<nsIURI> imageURI;
rv = NewURI(imgURIStr, getter_AddRefs(imageURI));
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIServiceManager> mgr;
NS_GetServiceManager(getter_AddRefs(mgr));
NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
nsCOMPtr<imgICache> imgCache;
rv = mgr->GetServiceByContractID("@mozilla.org/image/cache;1",
NS_GET_IID(imgICache),
getter_AddRefs(imgCache));
if(NS_FAILED(rv)) return rv;
imgCache->FindEntryProperties(imageURI, properties);
NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
return NS_OK;
}
char *
GeckoDocumentObject::GetImageName()
{
if(!IsImage()) {
return NULL;
}
if(mImageName.Length()) {
return g_strdup(mImageName.get());
}
nsresult rv;
char *imgURIStr = GetImageURI();
nsCOMPtr<nsIProperties> imgProperties;
rv = GetImageProperties(imgURIStr, getter_AddRefs(imgProperties));
NS_ENSURE_SUCCESS(rv, NULL);
if (imgProperties) {
nsCOMPtr<nsISupportsCString> dispositionCString;
imgProperties->Get("content-disposition",
NS_GET_IID(nsISupportsCString),
getter_AddRefs(dispositionCString));
if (dispositionCString) {
nsCString contentDisposition;
dispositionCString->GetData(contentDisposition);
FilenameFromContentDisposition(contentDisposition, mImageName);
}
}
if (!mImageName.Length()) {
nsCOMPtr<nsIURI> imageURI;
rv = NewURI(imgURIStr, getter_AddRefs(imageURI));
NS_ENSURE_SUCCESS(rv, NULL);
nsCOMPtr<nsIURL> url(do_QueryInterface(imageURI));
if (url) {
url->GetFileName(mImageName);
}
}
return mImageName.Length() ? g_strdup(mImageName.get()) : NULL;
}
char *
GeckoDocumentObject::GetImageMimeType()
{
if(!IsImage()) {
return NULL;
}
if(mImageMimeType.Length()) {
return g_strdup(mImageMimeType.get());
}
nsresult rv;
char *imgURIStr = GetImageURI();
nsCOMPtr<nsIProperties> imgProperties;
rv = GetImageProperties(imgURIStr, getter_AddRefs(imgProperties));
NS_ENSURE_SUCCESS(rv, NULL);
if (imgProperties) {
nsCOMPtr<nsISupportsCString> typeCString;
imgProperties->Get("type",
NS_GET_IID(nsISupportsCString),
getter_AddRefs(typeCString));
if (typeCString) {
typeCString->GetData(mImageMimeType);
}
}
return mImageMimeType.Length() ? g_strdup(mImageMimeType.get()) : NULL;
}
char *
GeckoDocumentObject::GetImageURI()
{
if(!IsImage()) {
return NULL;
}
if(!mImageURI.Length()) {
nsresult rv;
nsString img;
rv = mImage->GetSrc(img);
if (NS_FAILED(rv)) return NULL;
NS_UTF16ToCString (img, NS_CSTRING_ENCODING_UTF8, mImageURI);
}
return g_strdup(mImageURI.get());
}
bool
GeckoDocumentObject::SaveImage(const char *filename)
{
GeckoBrowserPersist browserPersist(mBrowser);
return browserPersist.SaveURI(mImageURI.get(), filename);
}
#endif

View File

@ -0,0 +1,31 @@
#ifndef __GECKO_DOCUMENT_OBJECT_H__
#define __GECKO_DOCUMENT_OBJECT_H__
#include <nsIDOMNode.h>
#include <nsIDOMHTMLImageElement.h>
#include "sugar-browser.h"
class GeckoDocumentObject
{
public:
GeckoDocumentObject(SugarBrowser *browser, nsIDOMNode *node);
~GeckoDocumentObject();
bool IsImage();
char *GetImageURI();
char *GetImageName();
char *GetImageMimeType();
bool SaveImage(const char *filename);
private:
SugarBrowser *mBrowser;
nsCOMPtr<nsIDOMNode> mNode;
nsCOMPtr<nsIDOMHTMLImageElement> mImage;
nsCString mImageURI;
nsCString mImageName;
nsCString mImageMimeType;
protected:
/* additional members */
};
#endif // __GECKO_DOCUMENT_OBJECT_H__

View File

@ -0,0 +1,218 @@
#include <config.h>
#ifdef HAVE_GECKO_1_9
#include <sys/time.h>
#include <time.h>
#include <glib.h>
#include <nsStringAPI.h>
#include <nsCOMPtr.h>
#include <nsITransferable.h>
#include <nsISupportsPrimitives.h>
#include <nsIDOMEventTarget.h>
#include <nsComponentManagerUtils.h>
#include <nsServiceManagerUtils.h>
#include <nsIInterfaceRequestorUtils.h>
#include <nsIDOMMouseEvent.h>
#include <nsIMIMEService.h>
#include "GeckoDragDropHooks.h"
#include "GeckoDocumentObject.h"
#define TEXT_URI_LIST "text/uri-list"
#define TEXT_X_MOZ_URL "text/x-moz-url"
#define FILE_LOCALHOST "file://"
//*****************************************************************************
// UriListDataProvider
//*****************************************************************************
class UriListDataProvider : public nsIFlavorDataProvider
{
public:
UriListDataProvider(GeckoDocumentObject *mDocumentObject);
virtual ~UriListDataProvider();
NS_DECL_ISUPPORTS
NS_DECL_NSIFLAVORDATAPROVIDER
private:
GeckoDocumentObject *mDocumentObject;
nsCString mFilePath;
};
//*****************************************************************************
NS_IMPL_ISUPPORTS1(UriListDataProvider, nsIFlavorDataProvider)
UriListDataProvider::UriListDataProvider(GeckoDocumentObject *documentObject)
: mDocumentObject(documentObject)
{
}
UriListDataProvider::~UriListDataProvider()
{
if(mFilePath.Length()) {
remove(mFilePath.get());
}
delete mDocumentObject;
}
NS_IMETHODIMP
UriListDataProvider::GetFlavorData(nsITransferable *aTransferable,
const char *aFlavor, nsISupports **aData,
PRUint32 *aDataLen)
{
NS_ENSURE_ARG_POINTER(aData && aDataLen);
nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
char *image_name;
char *mime_type;
char *file_ext;
nsCString mime_ext;
timeval timestamp;
*aData = nsnull;
*aDataLen = 0;
if(g_ascii_strcasecmp(aFlavor, TEXT_URI_LIST) != 0) {
return rv;
}
gettimeofday(&timestamp, NULL);
mFilePath.Append(g_get_tmp_dir());
mFilePath.Append("/");
mFilePath.AppendInt(timestamp.tv_sec);
mFilePath.AppendInt(timestamp.tv_usec);
image_name = mDocumentObject->GetImageName();
file_ext = strrchr(image_name, '.');
mime_type = mDocumentObject->GetImageMimeType();
nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1",
&rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = mimeService->GetPrimaryExtension(nsCString(mime_type),
EmptyCString(),
mime_ext);
NS_ENSURE_SUCCESS(rv, rv);
if(!file_ext) {
mFilePath.Append(image_name);
mFilePath.Append(".");
mFilePath.Append(mime_ext);
} else if(strcmp(file_ext, mime_ext.get())) {
image_name[strlen(file_ext)] = 0;
mFilePath.Append(image_name);
mFilePath.Append(".");
mFilePath.Append(mime_ext);
} else {
mFilePath.Append(image_name);
}
g_free(mime_type);
g_free(image_name);
if(!mDocumentObject->SaveImage(mFilePath.get())) {
return NS_ERROR_FAILURE;
}
nsCString localURI(FILE_LOCALHOST);
localURI.Append(mFilePath);
nsString localURI16;
NS_CStringToUTF16(localURI, NS_CSTRING_ENCODING_UTF8, localURI16);
nsCOMPtr<nsISupportsString> localURIData(do_CreateInstance(
"@mozilla.org/supports-string;1", &rv));
if(NS_FAILED(rv)) return rv;
rv = localURIData->SetData(localURI16);
if(NS_FAILED(rv)) return rv;
CallQueryInterface(localURIData, aData);
*aDataLen = sizeof(nsISupportsString*);
// FIXME: Why do we need this? Is there a leak in mozilla?
this->Release();
return rv;
}
//*****************************************************************************
// GeckoDragDropHooks
//*****************************************************************************
NS_IMPL_ISUPPORTS1(GeckoDragDropHooks, nsIClipboardDragDropHooks)
GeckoDragDropHooks::GeckoDragDropHooks(SugarBrowser *browser)
: mBrowser(browser)
{
}
GeckoDragDropHooks::~GeckoDragDropHooks()
{
}
NS_IMETHODIMP
GeckoDragDropHooks::AllowStartDrag(nsIDOMEvent *event, PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
GeckoDragDropHooks::AllowDrop(nsIDOMEvent *event, nsIDragSession *session,
PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
GeckoDragDropHooks::OnCopyOrDrag(nsIDOMEvent *aEvent, nsITransferable *trans,
PRBool *_retval)
{
nsresult rv;
*_retval = true;
nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
mouseEvent = do_QueryInterface(aEvent, &rv);
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDOMEventTarget> eventTarget;
rv = mouseEvent->GetTarget(getter_AddRefs(eventTarget));
if(NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDOMNode> targetNode;
targetNode = do_QueryInterface(eventTarget, &rv);
if(NS_FAILED(rv)) return rv;
GeckoDocumentObject *documentObject = new GeckoDocumentObject(mBrowser,
targetNode);
if(documentObject->IsImage()) {
rv = trans->RemoveDataFlavor(TEXT_X_MOZ_URL);
if(NS_FAILED(rv)) return rv;
rv = trans->AddDataFlavor(TEXT_URI_LIST);
if(NS_FAILED(rv)) return rv;
UriListDataProvider *rawPtr = new UriListDataProvider(documentObject);
nsCOMPtr<nsISupports> dataProvider(do_QueryInterface(rawPtr, &rv));
if(NS_FAILED(rv)) return rv;
rv = trans->SetTransferData(TEXT_URI_LIST, dataProvider, 0);
if(NS_FAILED(rv)) return rv;
}
return rv;
}
NS_IMETHODIMP
GeckoDragDropHooks::OnPasteOrDrop(nsIDOMEvent *event, nsITransferable *trans,
PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
#endif

View File

@ -0,0 +1,25 @@
#ifndef __GECKO_DRAG_DROP_HOOKS_H__
#define __GECKO_DRAG_DROP_HOOKS_H__
#include <nsIClipboardDragDropHooks.h>
#include "sugar-browser.h"
class GeckoDragDropHooks : public nsIClipboardDragDropHooks
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICLIPBOARDDRAGDROPHOOKS
GeckoDragDropHooks(SugarBrowser *browser);
private:
~GeckoDragDropHooks();
SugarBrowser *mBrowser;
protected:
/* additional members */
};
#endif // __GECKO_DRAG_DROP_HOOKS_H__

View File

@ -3,6 +3,7 @@ libsugarbrowser_la_CPPFLAGS = \
$(LIB_CFLAGS) \ $(LIB_CFLAGS) \
$(GECKO_CFLAGS) \ $(GECKO_CFLAGS) \
$(NSPR_CFLAGS) \ $(NSPR_CFLAGS) \
-I$(MOZILLA_INCLUDE_DIR)/commandhandler \
-I$(MOZILLA_INCLUDE_DIR)/dom \ -I$(MOZILLA_INCLUDE_DIR)/dom \
-I$(MOZILLA_INCLUDE_DIR)/docshell \ -I$(MOZILLA_INCLUDE_DIR)/docshell \
-I$(MOZILLA_INCLUDE_DIR)/exthandler \ -I$(MOZILLA_INCLUDE_DIR)/exthandler \
@ -15,6 +16,8 @@ libsugarbrowser_la_CPPFLAGS = \
-I$(MOZILLA_INCLUDE_DIR)/uriloader \ -I$(MOZILLA_INCLUDE_DIR)/uriloader \
-I$(MOZILLA_INCLUDE_DIR)/webbrwsr \ -I$(MOZILLA_INCLUDE_DIR)/webbrwsr \
-I$(MOZILLA_INCLUDE_DIR)/webbrowserpersist \ -I$(MOZILLA_INCLUDE_DIR)/webbrowserpersist \
-I$(MOZILLA_INCLUDE_DIR)/widget \
-I$(MOZILLA_INCLUDE_DIR)/xpcom \
-DPLUGIN_DIR=\"$(libdir)/mozilla/plugins\" \ -DPLUGIN_DIR=\"$(libdir)/mozilla/plugins\" \
-DSHARE_DIR=\"$(pkgdatadir)\" -DSHARE_DIR=\"$(pkgdatadir)\"
@ -26,8 +29,14 @@ libsugarbrowser_la_LIBADD = \
libsugarbrowser_la_SOURCES = \ libsugarbrowser_la_SOURCES = \
$(BUILT_SOURCES) \ $(BUILT_SOURCES) \
GeckoBrowserPersist.h \
GeckoBrowserPersist.cpp \
GeckoContentHandler.h \ GeckoContentHandler.h \
GeckoContentHandler.cpp \ GeckoContentHandler.cpp \
GeckoDocumentObject.h \
GeckoDocumentObject.cpp \
GeckoDragDropHooks.h \
GeckoDragDropHooks.cpp \
GeckoDownload.h \ GeckoDownload.h \
GeckoDownload.cpp \ GeckoDownload.cpp \
sugar-address-entry.c \ sugar-address-entry.c \

View File

@ -24,6 +24,12 @@
#include "GeckoContentHandler.h" #include "GeckoContentHandler.h"
#include "GeckoDownload.h" #include "GeckoDownload.h"
#ifdef HAVE_GECKO_1_9
#include "GeckoDragDropHooks.h"
#include "GeckoDocumentObject.h"
#include "GeckoBrowserPersist.h"
#endif
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#include <gtkmozembed_internal.h> #include <gtkmozembed_internal.h>
#include <nsCOMPtr.h> #include <nsCOMPtr.h>
@ -56,6 +62,10 @@
#include <nsIHistoryEntry.h> #include <nsIHistoryEntry.h>
#include <nsISHEntry.h> #include <nsISHEntry.h>
#include <nsIInputStream.h> #include <nsIInputStream.h>
#include <nsICommandManager.h>
#include <nsIClipboardDragDropHooks.h>
#define SUGAR_PATH "SUGAR_PATH"
enum { enum {
PROP_0, PROP_0,
@ -75,6 +85,8 @@ enum {
static guint signals[N_SIGNALS]; static guint signals[N_SIGNALS];
static GObjectClass *parent_class = NULL;
static const nsModuleComponentInfo sSugarComponents[] = { static const nsModuleComponentInfo sSugarComponents[] = {
{ {
"Gecko Content Handler", "Gecko Content Handler",
@ -151,9 +163,11 @@ sugar_browser_startup(const char *profile_path, const char *profile_name)
NS_ENSURE_TRUE(prefService, FALSE); NS_ENSURE_TRUE(prefService, FALSE);
/* Read our predefined default prefs */ /* Read our predefined default prefs */
nsCString pathToPrefs(g_getenv(SUGAR_PATH));
pathToPrefs.Append("/data/gecko-prefs.js");
nsCOMPtr<nsILocalFile> file; nsCOMPtr<nsILocalFile> file;
NS_NewNativeLocalFile(nsCString(SHARE_DIR"/gecko-prefs.js"), NS_NewNativeLocalFile(pathToPrefs, PR_TRUE, getter_AddRefs(file));
PR_TRUE, getter_AddRefs(file));
NS_ENSURE_TRUE(file, FALSE); NS_ENSURE_TRUE(file, FALSE);
rv = prefService->ReadUserPrefs (file); rv = prefService->ReadUserPrefs (file);
@ -166,7 +180,10 @@ sugar_browser_startup(const char *profile_path, const char *profile_name)
prefService->GetBranch ("", getter_AddRefs(pref)); prefService->GetBranch ("", getter_AddRefs(pref));
NS_ENSURE_TRUE(pref, FALSE); NS_ENSURE_TRUE(pref, FALSE);
pref->SetCharPref ("helpers.private_mime_types_file", SHARE_DIR"/mime.types"); nsCString pathToMimeTypes(g_getenv(SUGAR_PATH));
pathToMimeTypes.Append("/data/mime.types");
pref->SetCharPref ("helpers.private_mime_types_file", pathToMimeTypes.get());
rv = prefService->ReadUserPrefs (nsnull); rv = prefService->ReadUserPrefs (nsnull);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
@ -210,25 +227,6 @@ sugar_browser_shutdown(void)
G_DEFINE_TYPE(SugarBrowser, sugar_browser, GTK_TYPE_MOZ_EMBED) G_DEFINE_TYPE(SugarBrowser, sugar_browser, GTK_TYPE_MOZ_EMBED)
static nsresult
NewURI(const char *uri, nsIURI **result)
{
nsresult rv;
nsCOMPtr<nsIServiceManager> mgr;
NS_GetServiceManager (getter_AddRefs (mgr));
NS_ENSURE_TRUE(mgr, FALSE);
nsCOMPtr<nsIIOService> ioService;
rv = mgr->GetServiceByContractID ("@mozilla.org/network/io-service;1",
NS_GET_IID (nsIIOService),
getter_AddRefs(ioService));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCString cSpec(uri);
return ioService->NewURI (cSpec, nsnull, nsnull, result);
}
static nsresult static nsresult
FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName) FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName)
{ {
@ -258,38 +256,6 @@ FilenameFromContentDisposition(nsCString contentDisposition, nsCString &fileName
return NS_OK; return NS_OK;
} }
static nsresult
ImageNameFromCache(nsIURI *imgURI, nsCString &imgName)
{
nsresult rv;
nsCOMPtr<nsIServiceManager> mgr;
NS_GetServiceManager (getter_AddRefs (mgr));
NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE);
nsCOMPtr<imgICache> imgCache;
rv = mgr->GetServiceByContractID("@mozilla.org/image/cache;1",
NS_GET_IID (imgICache),
getter_AddRefs(imgCache));
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
nsCOMPtr<nsIProperties> imgProperties;
imgCache->FindEntryProperties(imgURI, getter_AddRefs(imgProperties));
if (imgProperties) {
nsCOMPtr<nsISupportsCString> dispositionCString;
imgProperties->Get("content-disposition",
NS_GET_IID(nsISupportsCString),
getter_AddRefs(dispositionCString));
if (dispositionCString) {
nsCString contentDisposition;
dispositionCString->GetData(contentDisposition);
FilenameFromContentDisposition(contentDisposition, imgName);
}
}
return NS_OK;
}
static SugarBrowserMetadata * static SugarBrowserMetadata *
sugar_browser_get_document_metadata(SugarBrowser *browser) sugar_browser_get_document_metadata(SugarBrowser *browser)
{ {
@ -382,13 +348,47 @@ sugar_browser_get_property(GObject *object,
} }
} }
static void
sugar_browser_realize(GtkWidget *widget)
{
GTK_WIDGET_CLASS(parent_class)->realize(widget);
#ifdef HAVE_NS_WEB_BROWSER
GtkMozEmbed *embed = GTK_MOZ_EMBED(widget);
nsCOMPtr<nsIWebBrowser> webBrowser;
gtk_moz_embed_get_nsIWebBrowser(embed, getter_AddRefs(webBrowser));
NS_ENSURE_TRUE(webBrowser, );
nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(webBrowser);
if (commandManager) {
nsresult rv;
nsIClipboardDragDropHooks *rawPtr = new GeckoDragDropHooks(
SUGAR_BROWSER(widget));
nsCOMPtr<nsIClipboardDragDropHooks> geckoDragDropHooks(
do_QueryInterface(rawPtr, &rv));
NS_ENSURE_SUCCESS(rv, );
nsCOMPtr<nsIDOMWindow> DOMWindow = do_GetInterface(webBrowser);
nsCOMPtr<nsICommandParams> cmdParamsObj = do_CreateInstance(
NS_COMMAND_PARAMS_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, );
cmdParamsObj->SetISupportsValue("addhook", geckoDragDropHooks);
commandManager->DoCommand("cmd_clipboardDragDropHook", cmdParamsObj,
DOMWindow);
}
#endif
}
static void static void
sugar_browser_class_init(SugarBrowserClass *browser_class) sugar_browser_class_init(SugarBrowserClass *browser_class)
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS(browser_class); GObjectClass *gobject_class = G_OBJECT_CLASS(browser_class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(browser_class);
gobject_class->get_property = sugar_browser_get_property; parent_class = (GObjectClass *) g_type_class_peek_parent(browser_class);
gobject_class->get_property = sugar_browser_get_property;
widget_class->realize = sugar_browser_realize;
signals[MOUSE_CLICK] = g_signal_new ("mouse_click", signals[MOUSE_CLICK] = g_signal_new ("mouse_click",
SUGAR_TYPE_BROWSER, SUGAR_TYPE_BROWSER,
@ -570,33 +570,11 @@ location_cb(GtkMozEmbed *embed)
update_navigation_properties(browser); update_navigation_properties(browser);
} }
static char *
get_image_name(const char *uri)
{
nsresult rv;
nsCString imgName;
nsCOMPtr<nsIURI> imgURI;
rv = NewURI(uri, getter_AddRefs(imgURI));
NS_ENSURE_SUCCESS(rv, NULL);
ImageNameFromCache(imgURI, imgName);
if (!imgName.Length()) {
nsCOMPtr<nsIURL> url(do_QueryInterface(imgURI));
if (url) {
url->GetFileName(imgName);
}
}
return imgName.Length() ? g_strdup(imgName.get()) : NULL;
}
static gboolean static gboolean
dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent) dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
{ {
SugarBrowser *browser = SUGAR_BROWSER(embed); #ifdef HAVE_GECKO_1_9
SugarBrowser *browser = SUGAR_BROWSER(embed);
SugarBrowserEvent *event; SugarBrowserEvent *event;
gint return_value = FALSE; gint return_value = FALSE;
@ -610,36 +588,10 @@ dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
event = sugar_browser_event_new(); event = sugar_browser_event_new();
nsresult rv; GeckoDocumentObject documentObject(browser, targetNode);
if(documentObject.IsImage()) {
PRUint16 type; event->image_uri = documentObject.GetImageURI();
rv = targetNode->GetNodeType(&type); event->image_name = documentObject.GetImageName();
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(targetNode);
if ((nsIDOMNode::ELEMENT_NODE == type) && element) {
nsString uTag;
rv = element->GetLocalName(uTag);
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
nsCString tag;
NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag);
if (g_ascii_strcasecmp (tag.get(), "img") == 0) {
nsString img;
nsCOMPtr <nsIDOMHTMLImageElement> image;
image = do_QueryInterface(targetNode, &rv);
if (NS_FAILED(rv) || !image) return NS_ERROR_FAILURE;
rv = image->GetSrc(img);
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
nsCString cImg;
NS_UTF16ToCString (img, NS_CSTRING_ENCODING_UTF8, cImg);
event->image_uri = g_strdup(cImg.get());
event->image_name = get_image_name(event->image_uri);
}
} }
PRUint16 btn = 0; PRUint16 btn = 0;
@ -651,6 +603,9 @@ dom_mouse_click_cb(GtkMozEmbed *embed, nsIDOMMouseEvent *mouseEvent)
sugar_browser_event_free(event); sugar_browser_event_free(event);
return return_value; return return_value;
#else
return FALSE;
#endif
} }
static void static void
@ -712,71 +667,14 @@ sugar_browser_grab_focus(SugarBrowser *browser)
} }
} }
static nsresult
GetPostData(SugarBrowser *browser, nsIInputStream **postData)
{
#ifdef HAVE_NS_WEB_BROWSER
nsCOMPtr<nsIWebBrowser> webBrowser;
gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
getter_AddRefs(webBrowser));
NS_ENSURE_TRUE(webBrowser, NS_ERROR_FAILURE);
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(webBrowser));
NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
PRInt32 sindex;
nsCOMPtr<nsISHistory> sessionHistory;
webNav->GetSessionHistory(getter_AddRefs(sessionHistory));
NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
nsCOMPtr<nsIHistoryEntry> entry;
sessionHistory->GetIndex(&sindex);
sessionHistory->GetEntryAtIndex(sindex, PR_FALSE, getter_AddRefs(entry));
nsCOMPtr<nsISHEntry> shEntry(do_QueryInterface(entry));
if (shEntry) {
shEntry->GetPostData(postData);
}
return NS_OK;
#endif
return NS_ERROR_NOT_IMPLEMENTED;
}
gboolean gboolean
sugar_browser_save_uri(SugarBrowser *browser, sugar_browser_save_uri(SugarBrowser *browser,
const char *uri, const char *uri,
const char *filename) const char *filename)
{ {
#ifdef HAVE_NS_WEB_BROWSER #ifdef HAVE_GECKO_1_9
nsresult rv; GeckoBrowserPersist browserPersist(browser);
return browserPersist.SaveURI(uri, filename);
nsCOMPtr<nsIURI> sourceURI;
rv = NewURI(uri, getter_AddRefs(sourceURI));
NS_ENSURE_SUCCESS(rv, FALSE);
nsCOMPtr<nsILocalFile> destFile = do_CreateInstance("@mozilla.org/file/local;1");
NS_ENSURE_TRUE(destFile, FALSE);
destFile->InitWithNativePath(nsCString(filename));
nsCOMPtr<nsIWebBrowser> webBrowser;
gtk_moz_embed_get_nsIWebBrowser(GTK_MOZ_EMBED(browser),
getter_AddRefs(webBrowser));
NS_ENSURE_TRUE(webBrowser, FALSE);
nsCOMPtr<nsIWebBrowserPersist> webPersist = do_QueryInterface (webBrowser);
NS_ENSURE_TRUE(webPersist, FALSE);
nsCOMPtr<nsIInputStream> postData;
GetPostData(browser, getter_AddRefs(postData));
rv = webPersist->SaveURI(sourceURI, nsnull, nsnull, postData, nsnull, destFile);
NS_ENSURE_SUCCESS(rv, FALSE);
return TRUE;
#else #else
return FALSE; return FALSE;
#endif #endif

View File

@ -1,6 +1,7 @@
sugardir = $(pkgdatadir) sugardir = $(pkgdatadir)/data
sugar_DATA = \ sugar_DATA = \
gtkrc \ sugar.gtkrc \
sugar-xo.gtkrc \
gecko-prefs.js \ gecko-prefs.js \
mime.types mime.types

View File

@ -1,6 +0,0 @@
gtk-theme-name = "olpc"
gtk-icon-theme-name = "olpc"
gtk-font-name = "Bitstream Vera Sans 7"
gtk-icon-sizes = "gtk-menu=32,32:gtk-button=32,32"
gtk-cursor-theme-name = "olpc"
gtk-cursor-theme-size = 48

View File

@ -1,2 +1,3 @@
application/x-squeak-project pr application/x-squeak-project pr
application/x-abiword abw application/x-abiword abw
application/vnd.olpc-x-sugar xo

7
data/sugar-xo.gtkrc Normal file
View File

@ -0,0 +1,7 @@
gtk-theme-name = "sugar-xo"
gtk-icon-theme-name = "sugar"
gtk-font-name = "Sans Serif 7"
gtk-icon-sizes = "gtk-menu=32,32:gtk-button=32,32"
gtk-cursor-theme-name = "sugar"
gtk-cursor-theme-size = 48
gtk-toolbar-style = GTK_TOOLBAR_ICONS

5
data/sugar.gtkrc Normal file
View File

@ -0,0 +1,5 @@
gtk-theme-name = "sugar"
gtk-icon-theme-name = "sugar"
gtk-font-name = "Sans Serif 11"
gtk-cursor-theme-name = "sugar"
gtk-toolbar-style = GTK_TOOLBAR_ICONS

View File

@ -1,3 +1,6 @@
import os
import logging
import typeregistry import typeregistry
class ClipboardObject: class ClipboardObject:
@ -8,6 +11,10 @@ class ClipboardObject:
self._percent = 0 self._percent = 0
self._formats = {} self._formats = {}
def destroy(self):
for type, format in self._formats.iteritems():
format.destroy()
def get_id(self): def get_id(self):
return self._id return self._id
@ -49,6 +56,10 @@ class Format:
self._data = data self._data = data
self._on_disk = on_disk self._on_disk = on_disk
def destroy(self):
if self._on_disk:
os.remove(self._data.replace('file://', ''))
def get_type(self): def get_type(self):
return self._type return self._type

View File

@ -109,7 +109,8 @@ class ClipboardDBusServiceHelper(dbus.service.Object):
@dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
in_signature="o", out_signature="") in_signature="o", out_signature="")
def delete_object(self, object_path): def delete_object(self, object_path):
del self._objects[str(object_path)] cb_object = self._objects.pop(str(object_path))
cb_object.destroy()
self.object_deleted(object_path) self.object_deleted(object_path)
logging.debug('Deleted object with object_id ' + object_path) logging.debug('Deleted object with object_id ' + object_path)

View File

@ -32,7 +32,7 @@ import dbus.glib
from sugar import env from sugar import env
sys.path.insert(0, env.get_service_path('clipboard')) sys.path.append(env.get_service_path('clipboard'))
from clipboardservice import ClipboardService from clipboardservice import ClipboardService

View File

@ -1,5 +1,7 @@
import logging import logging
from gettext import gettext as _ from gettext import gettext as _
import urlparse
import posixpath
class FileType: class FileType:
def __init__(self, formats): def __init__(self, formats):
@ -197,6 +199,64 @@ class OOTextFileType(FileType):
return mime_type in cls._types return mime_type in cls._types
matches_mime_type = classmethod(matches_mime_type) matches_mime_type = classmethod(matches_mime_type)
class UriListFileType(FileType):
_types = set(['text/uri-list'])
def _is_image(self):
uris = self._formats['text/uri-list'].get_data().split('\n')
if len(uris) == 1:
uri = urlparse.urlparse(uris[0])
ext = posixpath.splitext(uri.path)[1]
logging.debug(ext)
# FIXME: Bad hack, the type registry should treat text/uri-list as a special case.
if ext in ['.jpg', '.jpeg', '.gif', '.png', '.svg']:
return True
return False
def get_name(self):
if self._is_image():
return _('Image')
else:
return _('File')
def get_icon(self):
if self._is_image():
return 'theme:object-image'
else:
return 'theme:stock-missing'
def get_preview(self):
return ''
def get_activity(self):
return ''
def matches_mime_type(cls, mime_type):
return mime_type in cls._types
matches_mime_type = classmethod(matches_mime_type)
class XoFileType(FileType):
_types = set(['application/vnd.olpc-x-sugar'])
def get_name(self):
return _('Activity package')
def get_icon(self):
return 'theme:stock-missing'
def get_preview(self):
return ''
def get_activity(self):
return ''
def matches_mime_type(cls, mime_type):
return mime_type in cls._types
matches_mime_type = classmethod(matches_mime_type)
class UnknownFileType(FileType): class UnknownFileType(FileType):
def get_name(self): def get_name(self):
return _('Object') return _('Object')
@ -221,11 +281,13 @@ class TypeRegistry:
self._types.append(MsWordFileType) self._types.append(MsWordFileType)
self._types.append(RtfFileType) self._types.append(RtfFileType)
self._types.append(OOTextFileType) self._types.append(OOTextFileType)
self._types.append(UriListFileType)
self._types.append(UriFileType) self._types.append(UriFileType)
self._types.append(ImageFileType) self._types.append(ImageFileType)
self._types.append(AbiwordFileType) self._types.append(AbiwordFileType)
self._types.append(TextFileType) self._types.append(TextFileType)
self._types.append(SqueakProjectFileType) self._types.append(SqueakProjectFileType)
self._types.append(XoFileType)
def get_type(self, formats): def get_type(self, formats):
for file_type in self._types: for file_type in self._types:

View File

@ -7,6 +7,6 @@ import os
import sys import sys
from sugar import env from sugar import env
sys.path.insert(0, env.get_service_path('console')) sys.path.append(env.get_service_path('console'))
import console import console

View File

@ -24,7 +24,7 @@ import logging
from sugar import logger from sugar import logger
from sugar import env from sugar import env
sys.path.insert(0, env.get_service_path('datastore')) sys.path.append(env.get_service_path('datastore'))
logger.start('data-store') logger.start('data-store')
logging.info('Starting the data store...') logging.info('Starting the data store...')

View File

@ -0,0 +1,36 @@
"""Service to track buddies and activities on the network
Model objects:
activity.Activity -- tracks a (shared/shareable) activity
with many properties and observable events
buddy.Buddy -- tracks a reference to a particular actor
on the network
buddy.GenericOwner -- actor who owns a particular
activity on the network
buddy.ShellOwner -- actor who owns the local machine
connects to the owner module (on the server)
Controller objects:
presenceservice.PresenceService -- controller which connects
a networking plugin to a DBUS service. Generates events
for networking events, forwards updates/requests to the
server plugin.
server_plugin.ServerPlugin -- implementation of networking
plugin using telepathy Python (Jabber) to provide the
underlying communications layer. Generates GObject
events that the PresenceService observes to forward onto
the DBUS clients.
Utility machinery:
buddyiconcache.BuddyIconCache -- caches buddy icons on disk
based on the "jid" XXX Jabber ID? of the buddy.
psutils -- trivial function to decode int-list to characters
"""

View File

@ -30,6 +30,9 @@ class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGOb
class Activity(DBusGObject): class Activity(DBusGObject):
"""Represents a potentially shareable activity on the network.
"""
__gtype_name__ = "Activity" __gtype_name__ = "Activity"
__gsignals__ = { __gsignals__ = {
@ -50,6 +53,15 @@ class Activity(DBusGObject):
} }
def __init__(self, bus_name, object_id, tp, **kwargs): def __init__(self, bus_name, object_id, tp, **kwargs):
"""Initializes the activity and sets its properties to default values.
bus_name -- DBUS name for lookup on local host
object_id -- The unique worldwide ID for this activity
tp -- The server plugin object (stands for "telepathy plugin")
kwargs -- Keyword arguments for the GObject properties
"""
if not bus_name: if not bus_name:
raise ValueError("DBus bus name must be valid") raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int): if not object_id or not isinstance(object_id, int):
@ -88,6 +100,13 @@ class Activity(DBusGObject):
tp.update_activity_properties(self._id) tp.update_activity_properties(self._id)
def do_get_property(self, pspec): def do_get_property(self, pspec):
"""Gets the value of a property associated with this activity.
pspec -- Property specifier
returns The value of the given property.
"""
if pspec.name == "id": if pspec.name == "id":
return self._id return self._id
elif pspec.name == "name": elif pspec.name == "name":
@ -104,6 +123,15 @@ class Activity(DBusGObject):
return self._local return self._local
def do_set_property(self, pspec, value): def do_set_property(self, pspec, value):
"""Sets the value of a property associated with this activity.
pspec -- Property specifier
value -- Desired value
Note that the "type" property can be set only once; attempting to set it
to something different later will raise a RuntimeError.
"""
if pspec.name == "id": if pspec.name == "id":
self._id = value self._id = value
elif pspec.name == "name": elif pspec.name == "name":
@ -122,6 +150,15 @@ class Activity(DBusGObject):
self._update_validity() self._update_validity()
def _update_validity(self): def _update_validity(self):
"""Sends a "validity-changed" signal if this activity's validity has changed.
Determines whether this activity's status has changed from valid to
invalid, or invalid to valid, and emits a "validity-changed" signal
if either is true. "Valid" means that the object's type, ID, name,
colour and type properties have all been set to something valid
(i.e., not "None").
"""
try: try:
old_valid = self._valid old_valid = self._valid
if self._color and self._name and self._id and self._type: if self._color and self._name and self._id and self._type:
@ -138,42 +175,80 @@ class Activity(DBusGObject):
@dbus.service.signal(_ACTIVITY_INTERFACE, @dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o") signature="o")
def BuddyJoined(self, buddy_path): def BuddyJoined(self, buddy_path):
"""Generates DBUS signal when a buddy joins this activity.
buddy_path -- DBUS path to buddy object
"""
pass pass
@dbus.service.signal(_ACTIVITY_INTERFACE, @dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o") signature="o")
def BuddyLeft(self, buddy_path): def BuddyLeft(self, buddy_path):
"""Generates DBUS signal when a buddy leaves this activity.
buddy_path -- DBUS path to buddy object
"""
pass pass
@dbus.service.signal(_ACTIVITY_INTERFACE, @dbus.service.signal(_ACTIVITY_INTERFACE,
signature="o") signature="o")
def NewChannel(self, channel_path): def NewChannel(self, channel_path):
"""Generates DBUS signal when a new channel is created for this activity.
channel_path -- DBUS path to new channel
XXX - what is this supposed to do? Who is supposed to call it?
What is the channel path? Right now this is never called.
"""
pass pass
# dbus methods # dbus methods
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def GetId(self): def GetId(self):
"""DBUS method to get this activity's ID
returns Activity ID
"""
return self.props.id return self.props.id
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def GetColor(self): def GetColor(self):
"""DBUS method to get this activity's colour
returns Activity colour
"""
return self.props.color return self.props.color
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def GetType(self): def GetType(self):
"""DBUS method to get this activity's type
returns Activity type
"""
return self.props.type return self.props.type
@dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="", @dbus.service.method(_ACTIVITY_INTERFACE, in_signature="", out_signature="",
async_callbacks=('async_cb', 'async_err_cb')) async_callbacks=('async_cb', 'async_err_cb'))
def Join(self, async_cb, async_err_cb): def Join(self, async_cb, async_err_cb):
"""DBUS method to for the local user to attempt to join the activity
async_cb -- Callback method to be called if join attempt is successful
async_err_cb -- Callback method to be called if join attempt is unsuccessful
"""
self.join(async_cb, async_err_cb) self.join(async_cb, async_err_cb)
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="ao") in_signature="", out_signature="ao")
def GetJoinedBuddies(self): def GetJoinedBuddies(self):
"""DBUS method to return a list of valid buddies who are joined in this activity
returns A list of buddy object paths
"""
ret = [] ret = []
for buddy in self._buddies: for buddy in self._buddies:
if buddy.props.valid: if buddy.props.valid:
@ -183,18 +258,37 @@ class Activity(DBusGObject):
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="soao") in_signature="", out_signature="soao")
def GetChannels(self): def GetChannels(self):
"""DBUS method to get the list of channels associated with this activity
returns XXX - Not sure what this returns as get_channels doesn't actually return
a list of channels!
"""
return self.get_channels() return self.get_channels()
@dbus.service.method(_ACTIVITY_INTERFACE, @dbus.service.method(_ACTIVITY_INTERFACE,
in_signature="", out_signature="s") in_signature="", out_signature="s")
def GetName(self): def GetName(self):
"""DBUS method to get this activity's name
returns Activity name
"""
return self.props.name return self.props.name
# methods # methods
def object_path(self): def object_path(self):
"""Retrieves our dbus.ObjectPath object
returns DBUS ObjectPath object
"""
return dbus.ObjectPath(self._object_path) return dbus.ObjectPath(self._object_path)
def get_joined_buddies(self): def get_joined_buddies(self):
"""Local method to return a list of valid buddies who are joined in this activity
This method is called by the PresenceService on the local machine.
returns A list of buddy objects
"""
ret = [] ret = []
for buddy in self._buddies: for buddy in self._buddies:
if buddy.props.valid: if buddy.props.valid:
@ -202,18 +296,40 @@ class Activity(DBusGObject):
return ret return ret
def buddy_joined(self, buddy): def buddy_joined(self, buddy):
"""Adds a buddy to this activity and sends a BuddyJoined signal
buddy -- Buddy object representing the buddy being added
Adds a buddy to this activity if the buddy is not already in the buddy list.
If this activity is "valid", a BuddyJoined signal is also sent.
This method is called by the PresenceService on the local machine.
"""
if buddy not in self._buddies: if buddy not in self._buddies:
self._buddies.append(buddy) self._buddies.append(buddy)
if self.props.valid: if self.props.valid:
self.BuddyJoined(buddy.object_path()) self.BuddyJoined(buddy.object_path())
def buddy_left(self, buddy): def buddy_left(self, buddy):
"""Removes a buddy from this activity and sends a BuddyLeft signal.
buddy -- Buddy object representing the buddy being removed
Removes a buddy from this activity if the buddy is in the buddy list.
If this activity is "valid", a BuddyLeft signal is also sent.
This method is called by the PresenceService on the local machine.
"""
if buddy in self._buddies: if buddy in self._buddies:
self._buddies.remove(buddy) self._buddies.remove(buddy)
if self.props.valid: if self.props.valid:
self.BuddyLeft(buddy.object_path()) self.BuddyLeft(buddy.object_path())
def _handle_share_join(self, tp, text_channel): def _handle_share_join(self, tp, text_channel):
"""Called when a join to a network activity was successful.
Called by the _shared_cb and _joined_cb methods.
"""
if not text_channel: if not text_channel:
logging.debug("Error sharing: text channel was None, shouldn't happen") logging.debug("Error sharing: text channel was None, shouldn't happen")
raise RuntimeError("Plugin returned invalid text channel") raise RuntimeError("Plugin returned invalid text channel")
@ -225,6 +341,8 @@ class Activity(DBusGObject):
return True return True
def _shared_cb(self, tp, activity_id, text_channel, exc, userdata): def _shared_cb(self, tp, activity_id, text_channel, exc, userdata):
"""XXX - not documented yet
"""
if activity_id != self.props.id: if activity_id != self.props.id:
# Not for us # Not for us
return return
@ -243,6 +361,11 @@ class Activity(DBusGObject):
logging.debug("Share of activity %s succeeded." % self._id) logging.debug("Share of activity %s succeeded." % self._id)
def _share(self, (async_cb, async_err_cb), owner): def _share(self, (async_cb, async_err_cb), owner):
"""XXX - not documented yet
XXX - This method is called externally by the PresenceService despite the fact
that this is supposed to be an internal method!
"""
logging.debug("Starting share of activity %s" % self._id) logging.debug("Starting share of activity %s" % self._id)
if self._joined: if self._joined:
async_err_cb(RuntimeError("Already shared activity %s" % self.props.id)) async_err_cb(RuntimeError("Already shared activity %s" % self.props.id))
@ -252,6 +375,8 @@ class Activity(DBusGObject):
logging.debug("done with share attempt %s" % self._id) logging.debug("done with share attempt %s" % self._id)
def _joined_cb(self, tp, activity_id, text_channel, exc, userdata): def _joined_cb(self, tp, activity_id, text_channel, exc, userdata):
"""XXX - not documented yet
"""
if activity_id != self.props.id: if activity_id != self.props.id:
# Not for us # Not for us
return return
@ -266,6 +391,16 @@ class Activity(DBusGObject):
async_cb() async_cb()
def join(self, async_cb, async_err_cb): def join(self, async_cb, async_err_cb):
"""Local method for the local user to attempt to join the activity.
async_cb -- Callback method to be called if join attempt is successful
async_err_cb -- Callback method to be called if join attempt is unsuccessful
The two callbacks are passed to the server_plugin ("tp") object, which in turn
passes them back as parameters in a callback to the _joined_cb method; this
callback is set up within this method.
"""
if self._joined: if self._joined:
async_err_cb(RuntimeError("Already joined activity %s" % self.props.id)) async_err_cb(RuntimeError("Already joined activity %s" % self.props.id))
return return
@ -273,19 +408,35 @@ class Activity(DBusGObject):
self._tp.join_activity(self.props.id, (sigid, async_cb, async_err_cb)) self._tp.join_activity(self.props.id, (sigid, async_cb, async_err_cb))
def get_channels(self): def get_channels(self):
"""Local method to get the list of channels associated with this activity
returns XXX - expected a list of channels, instead returning a tuple? ???
"""
conn = self._tp.get_connection() conn = self._tp.get_connection()
# FIXME add tubes and others channels # FIXME add tubes and others channels
return str(conn.service_name), conn.object_path, [self._text_channel.object_path] return str(conn.service_name), conn.object_path, [self._text_channel.object_path]
def leave(self): def leave(self):
"""Local method called when the user wants to leave the activity.
(XXX - doesn't appear to be called anywhere!)
"""
if self._joined: if self._joined:
self._text_channel[CHANNEL_INTERFACE].Close() self._text_channel[CHANNEL_INTERFACE].Close()
def _text_channel_closed_cb(self): def _text_channel_closed_cb(self):
"""Callback method called when the text channel is closed.
This callback is set up in the _handle_share_join method.
"""
self._joined = False self._joined = False
self._text_channel = None self._text_channel = None
def send_properties(self): def send_properties(self):
"""Tells the Telepathy server what the properties of this activity are.
"""
props = {} props = {}
props['name'] = self._name props['name'] = self._name
props['color'] = self._color props['color'] = self._color
@ -293,6 +444,17 @@ class Activity(DBusGObject):
self._tp.set_activity_properties(self.props.id, props) self._tp.set_activity_properties(self.props.id, props)
def set_properties(self, properties): def set_properties(self, properties):
"""Sets name, colour and/or type properties for this activity all at once.
properties - Dictionary object containing properties keyed by property names
Note that if any of the name, colour and/or type property values is changed from
what it originally was, the update_validity method will be called, resulting in
a "validity-changed" signal being generated. (Also note that unlike with the
do_set_property method, it *is* possible to change an already-set activity type
to something else; this may be a bug.) Called by the PresenceService on the
local machine.
"""
changed = False changed = False
if "name" in properties.keys(): if "name" in properties.keys():
name = properties["name"] name = properties["name"]

View File

@ -1,3 +1,4 @@
"""An "actor" on the network, whether remote or local"""
# Copyright (C) 2007, Red Hat, Inc. # Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd. # Copyright (C) 2007, Collabora Ltd.
# #
@ -29,6 +30,7 @@ _BUDDY_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
_OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner" _OWNER_INTERFACE = "org.laptop.Sugar.Presence.Buddy.Owner"
class NotFoundError(dbus.DBusException): class NotFoundError(dbus.DBusException):
"""Raised when a given actor is not found on the network"""
def __init__(self): def __init__(self):
dbus.DBusException.__init__(self) dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound' self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
@ -37,9 +39,35 @@ class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pas
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
_PROP_NICK = "nick"
_PROP_KEY = "key"
_PROP_ICON = "icon"
_PROP_CURACT = "current-activity"
_PROP_COLOR = "color"
_PROP_OWNER = "owner"
_PROP_VALID = "valid"
class Buddy(DBusGObject): class Buddy(DBusGObject):
"""Represents another person on the network and keeps track of the """Person on the network (tracks properties and shared activites)
activities and resources they make available for sharing."""
The Buddy is a collection of metadata describing a particular
actor/person on the network. The Buddy object tracks a set of
activities which the actor has shared with the presence service.
Buddies have a "valid" property which is used to flag Buddies
which are no longer reachable. That is, a Buddy may represent
a no-longer reachable target on the network.
The Buddy emits GObject events that the PresenceService uses
to track changes in its status.
Attributes:
_activities -- dictionary mapping activity ID to
activity.Activity objects
handles -- dictionary mapping telepresence client to
"handle" (XXX what's that)
"""
__gsignals__ = { __gsignals__ = {
'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'validity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@ -51,17 +79,26 @@ class Buddy(DBusGObject):
} }
__gproperties__ = { __gproperties__ = {
'key' : (str, None, None, None, _PROP_KEY : (str, None, None, None,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
'icon' : (object, None, None, gobject.PARAM_READWRITE), _PROP_ICON : (object, None, None, gobject.PARAM_READWRITE),
'nick' : (str, None, None, None, gobject.PARAM_READWRITE), _PROP_NICK : (str, None, None, None, gobject.PARAM_READWRITE),
'color' : (str, None, None, None, gobject.PARAM_READWRITE), _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE),
'current-activity' : (str, None, None, None, gobject.PARAM_READWRITE), _PROP_CURACT : (str, None, None, None, gobject.PARAM_READWRITE),
'valid' : (bool, None, None, False, gobject.PARAM_READABLE), _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE),
'owner' : (bool, None, None, False, gobject.PARAM_READABLE) _PROP_OWNER : (bool, None, None, False, gobject.PARAM_READABLE)
} }
def __init__(self, bus_name, object_id, **kwargs): def __init__(self, bus_name, object_id, **kwargs):
"""Initialize the Buddy object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
kwargs -- used to initialize the object's properties
constructs a DBUS "object path" from the _BUDDY_PATH
and object_id
"""
if not bus_name: if not bus_name:
raise ValueError("DBus bus name must be valid") raise ValueError("DBus bus name must be valid")
if not object_id or not isinstance(object_id, int): if not object_id or not isinstance(object_id, int):
@ -83,44 +120,62 @@ class Buddy(DBusGObject):
self._nick = None self._nick = None
self._color = None self._color = None
if not kwargs.get("key"): if not kwargs.get(_PROP_KEY):
raise ValueError("key required") raise ValueError("key required")
_ALLOWED_INIT_PROPS = [_PROP_NICK, _PROP_KEY, _PROP_ICON, _PROP_CURACT, _PROP_COLOR]
for (key, value) in kwargs.items():
if key not in _ALLOWED_INIT_PROPS:
logging.debug("Invalid init property '%s'; ignoring..." % key)
del kwargs[key]
gobject.GObject.__init__(self, **kwargs) gobject.GObject.__init__(self, **kwargs)
def do_get_property(self, pspec): def do_get_property(self, pspec):
if pspec.name == "key": """Retrieve current value for the given property specifier
pspec -- property specifier with a "name" attribute
"""
if pspec.name == _PROP_KEY:
return self._key return self._key
elif pspec.name == "icon": elif pspec.name == _PROP_ICON:
return self._icon return self._icon
elif pspec.name == "nick": elif pspec.name == _PROP_NICK:
return self._nick return self._nick
elif pspec.name == "color": elif pspec.name == _PROP_COLOR:
return self._color return self._color
elif pspec.name == "current-activity": elif pspec.name == _PROP_CURACT:
if not self._current_activity: if not self._current_activity:
return None return None
if not self._activities.has_key(self._current_activity): if not self._activities.has_key(self._current_activity):
return None return None
return self._current_activity return self._current_activity
elif pspec.name == "valid": elif pspec.name == _PROP_VALID:
return self._valid return self._valid
elif pspec.name == "owner": elif pspec.name == _PROP_OWNER:
return self._owner return self._owner
def do_set_property(self, pspec, value): def do_set_property(self, pspec, value):
if pspec.name == "icon": """Set given property
pspec -- property specifier with a "name" attribute
value -- value to set
emits 'icon-changed' signal on icon setting
calls _update_validity on all calls
"""
if pspec.name == _PROP_ICON:
if str(value) != self._icon: if str(value) != self._icon:
self._icon = str(value) self._icon = str(value)
self.IconChanged(self._icon) self.IconChanged(self._icon)
self.emit('icon-changed', self._icon) self.emit('icon-changed', self._icon)
elif pspec.name == "nick": elif pspec.name == _PROP_NICK:
self._nick = value self._nick = value
elif pspec.name == "color": elif pspec.name == _PROP_COLOR:
self._color = value self._color = value
elif pspec.name == "current-activity": elif pspec.name == _PROP_CURACT:
self._current_activity = value self._current_activity = value
elif pspec.name == "key": elif pspec.name == _PROP_KEY:
self._key = value self._key = value
self._update_validity() self._update_validity()
@ -129,27 +184,42 @@ class Buddy(DBusGObject):
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="ay") signature="ay")
def IconChanged(self, icon_data): def IconChanged(self, icon_data):
pass """Generates DBUS signal with icon_data"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="o") signature="o")
def JoinedActivity(self, activity_path): def JoinedActivity(self, activity_path):
pass """Generates DBUS signal when buddy joins activity
activity_path -- DBUS path to the activity object
"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="o") signature="o")
def LeftActivity(self, activity_path): def LeftActivity(self, activity_path):
pass """Generates DBUS signal when buddy leaves activity
activity_path -- DBUS path to the activity object
"""
@dbus.service.signal(_BUDDY_INTERFACE, @dbus.service.signal(_BUDDY_INTERFACE,
signature="a{sv}") signature="a{sv}")
def PropertyChanged(self, updated): def PropertyChanged(self, updated):
pass """Generates DBUS signal when buddy's property changes
updated -- updated property-set (dictionary) with the
Buddy's property (changed) values. Note: not the
full set of properties, just the changes.
"""
# dbus methods # dbus methods
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ay") in_signature="", out_signature="ay")
def GetIcon(self): def GetIcon(self):
"""Retrieve Buddy's icon data
returns empty string or dbus.ByteArray
"""
if not self.props.icon: if not self.props.icon:
return "" return ""
return dbus.ByteArray(self.props.icon) return dbus.ByteArray(self.props.icon)
@ -157,6 +227,11 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="ao") in_signature="", out_signature="ao")
def GetJoinedActivities(self): def GetJoinedActivities(self):
"""Retrieve set of Buddy's joined activities (paths)
returns list of dbus service paths for the Buddy's joined
activities
"""
acts = [] acts = []
for act in self.get_joined_activities(): for act in self.get_joined_activities():
acts.append(act.object_path()) acts.append(act.object_path())
@ -165,22 +240,41 @@ class Buddy(DBusGObject):
@dbus.service.method(_BUDDY_INTERFACE, @dbus.service.method(_BUDDY_INTERFACE,
in_signature="", out_signature="a{sv}") in_signature="", out_signature="a{sv}")
def GetProperties(self): def GetProperties(self):
"""Retrieve set of Buddy's properties
returns dictionary of
nick : str(nickname)
owner : bool( whether this Buddy is an owner??? )
XXX what is the owner flag for?
key : str(public-key)
color: Buddy's icon colour
XXX what type?
current-activity: Buddy's current activity_id, or
"" if no current activity
"""
props = {} props = {}
props['nick'] = self.props.nick props[_PROP_NICK] = self.props.nick
props['owner'] = self.props.owner props[_PROP_OWNER] = self.props.owner
props['key'] = self.props.key props[_PROP_KEY] = self.props.key
props['color'] = self.props.color props[_PROP_COLOR] = self.props.color
if self.props.current_activity: if self.props.current_activity:
props['current-activity'] = self.props.current_activity props[_PROP_CURACT] = self.props.current_activity
else: else:
props['current-activity'] = "" props[_PROP_CURACT] = ""
return props return props
# methods # methods
def object_path(self): def object_path(self):
"""Retrieve our dbus.ObjectPath object"""
return dbus.ObjectPath(self._object_path) return dbus.ObjectPath(self._object_path)
def add_activity(self, activity): def add_activity(self, activity):
"""Add an activity to the Buddy's set of activities
activity -- activity.Activity instance
calls JoinedActivity
"""
actid = activity.props.id actid = activity.props.id
if self._activities.has_key(actid): if self._activities.has_key(actid):
return return
@ -189,6 +283,12 @@ class Buddy(DBusGObject):
self.JoinedActivity(activity.object_path()) self.JoinedActivity(activity.object_path())
def remove_activity(self, activity): def remove_activity(self, activity):
"""Remove the activity from the Buddy's set of activities
activity -- activity.Activity instance
calls LeftActivity
"""
actid = activity.props.id actid = activity.props.id
if not self._activities.has_key(actid): if not self._activities.has_key(actid):
return return
@ -197,6 +297,7 @@ class Buddy(DBusGObject):
self.LeftActivity(activity.object_path()) self.LeftActivity(activity.object_path())
def get_joined_activities(self): def get_joined_activities(self):
"""Retrieves list of still-valid activity objects"""
acts = [] acts = []
for act in self._activities.values(): for act in self._activities.values():
if act.props.valid: if act.props.valid:
@ -204,36 +305,54 @@ class Buddy(DBusGObject):
return acts return acts
def set_properties(self, properties): def set_properties(self, properties):
"""Set the given set of properties on the object
properties -- set of property values to set
if no change, no events generated
if change, generates property-changed and
calls _update_validity
"""
changed = False changed = False
if "nick" in properties.keys(): changed_props = {}
nick = properties["nick"] if _PROP_NICK in properties.keys():
nick = properties[_PROP_NICK]
if nick != self._nick: if nick != self._nick:
self._nick = nick self._nick = nick
changed_props[_PROP_NICK] = nick
changed = True changed = True
if "color" in properties.keys(): if _PROP_COLOR in properties.keys():
color = properties["color"] color = properties[_PROP_COLOR]
if color != self._color: if color != self._color:
self._color = color self._color = color
changed_props[_PROP_COLOR] = color
changed = True changed = True
if "current-activity" in properties.keys(): if _PROP_CURACT in properties.keys():
curact = properties["current-activity"] curact = properties[_PROP_CURACT]
if curact != self._current_activity: if curact != self._current_activity:
self._current_activity = curact self._current_activity = curact
changed_props[_PROP_CURACT] = curact
changed = True changed = True
if not changed: if not changed or not len(changed_props.keys()):
return return
# Try emitting PropertyChanged before updating validity # Try emitting PropertyChanged before updating validity
# to avoid leaking a PropertyChanged signal before the buddy is # to avoid leaking a PropertyChanged signal before the buddy is
# actually valid the first time after creation # actually valid the first time after creation
if self._valid: if self._valid:
self.PropertyChanged(properties) self.PropertyChanged(changed_props)
self.emit('property-changed', properties) self.emit('property-changed', changed_props)
self._update_validity() self._update_validity()
def _update_validity(self): def _update_validity(self):
"""Check whether we are now valid
validity is True if color, nick and key are non-null
emits validity-changed if we have changed validity
"""
try: try:
old_valid = self._valid old_valid = self._valid
if self._color and self._nick and self._key: if self._color and self._nick and self._key:
@ -247,6 +366,13 @@ class Buddy(DBusGObject):
self._valid = False self._valid = False
class GenericOwner(Buddy): class GenericOwner(Buddy):
"""Common functionality for Local User-like objects
The TestOwner wants to produce something *like* a
ShellOwner, but with randomised changes and the like.
This class provides the common features for a real
local owner and a testing one.
"""
__gtype_name__ = "GenericOwner" __gtype_name__ = "GenericOwner"
__gproperties__ = { __gproperties__ = {
@ -255,7 +381,17 @@ class GenericOwner(Buddy):
'key-hash' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT) 'key-hash' : (str, None, None, None, gobject.PARAM_READABLE | gobject.PARAM_CONSTRUCT)
} }
def __init__(self, bus_name, object_id, **kwargs): def __init__(self, ps, bus_name, object_id, **kwargs):
"""Initialize the GenericOwner instance
ps -- presenceservice.PresenceService object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
kwargs -- used to initialize the object's properties
calls Buddy.__init__
"""
self._ps = ps
self._server = 'olpc.collabora.co.uk' self._server = 'olpc.collabora.co.uk'
self._key_hash = None self._key_hash = None
self._registered = False self._registered = False
@ -273,28 +409,47 @@ class GenericOwner(Buddy):
self._owner = True self._owner = True
def get_registered(self): def get_registered(self):
"""Retrieve whether owner has registered with presence server"""
return self._registered return self._registered
def get_server(self): def get_server(self):
"""Retrieve presence server (XXX url??)"""
return self._server return self._server
def get_key_hash(self): def get_key_hash(self):
"""Retrieve the user's private-key hash"""
return self._key_hash return self._key_hash
def set_registered(self, registered): def set_registered(self, registered):
"""Customisation point: handle the registration of the owner"""
raise RuntimeError("Subclasses must implement") raise RuntimeError("Subclasses must implement")
class ShellOwner(GenericOwner): class ShellOwner(GenericOwner):
"""Class representing the owner of the machine. This is the client """Representation of the local-machine owner using Sugar's Shell
portion of the Owner, paired with the server portion in Owner.py."""
The ShellOwner uses the Sugar Shell's dbus services to
register for updates about the user's profile description.
"""
__gtype_name__ = "ShellOwner" __gtype_name__ = "ShellOwner"
_SHELL_SERVICE = "org.laptop.Shell" _SHELL_SERVICE = "org.laptop.Shell"
_SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner" _SHELL_OWNER_INTERFACE = "org.laptop.Shell.Owner"
_SHELL_PATH = "/org/laptop/Shell" _SHELL_PATH = "/org/laptop/Shell"
def __init__(self, bus_name, object_id, test=False): def __init__(self, ps, bus_name, object_id, test=False):
"""Initialize the ShellOwner instance
ps -- presenceservice.PresenceService object
bus_name -- DBUS object bus name (identifier)
object_id -- the activity's unique identifier
test -- ignored
Retrieves initial property values from the profile
module. Loads the buddy icon from file as well.
XXX note: no error handling on that
calls GenericOwner.__init__
"""
server = profile.get_server() server = profile.get_server()
key_hash = profile.get_private_key_hash() key_hash = profile.get_private_key_hash()
registered = profile.get_server_registered() registered = profile.get_server_registered()
@ -307,7 +462,7 @@ class ShellOwner(GenericOwner):
icon = f.read() icon = f.read()
f.close() f.close()
GenericOwner.__init__(self, bus_name, object_id, key=key, nick=nick, GenericOwner.__init__(self, ps, bus_name, object_id, key=key, nick=nick,
color=color, icon=icon, server=server, key_hash=key_hash, color=color, icon=icon, server=server, key_hash=key_hash,
registered=registered) registered=registered)
@ -324,10 +479,17 @@ class ShellOwner(GenericOwner):
pass pass
def set_registered(self, value): def set_registered(self, value):
"""Handle notification that we have been registered"""
if value: if value:
profile.set_server_registered() profile.set_server_registered()
def _name_owner_changed_handler(self, name, old, new): def _name_owner_changed_handler(self, name, old, new):
"""Handle DBUS notification of a new / renamed service
Watches for the _SHELL_SERVICE, i.e. the Sugar Shell,
and registers with it if we have not yet registered
with it (using _connect_to_shell).
"""
if name != self._SHELL_SERVICE: if name != self._SHELL_SERVICE:
return return
if (old and len(old)) and (not new and not len(new)): if (old and len(old)) and (not new and not len(new)):
@ -338,6 +500,11 @@ class ShellOwner(GenericOwner):
self._connect_to_shell() self._connect_to_shell()
def _connect_to_shell(self): def _connect_to_shell(self):
"""Connect to the Sugar Shell service to watch for events
Connects the various XChanged events on the Sugar Shell
service to our _x_changed_cb methods.
"""
obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH) obj = self._bus.get_object(self._SHELL_SERVICE, self._SHELL_PATH)
self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE) self._shell_owner = dbus.Interface(obj, self._SHELL_OWNER_INTERFACE)
self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb) self._shell_owner.connect_to_signal('IconChanged', self._icon_changed_cb)
@ -347,21 +514,30 @@ class ShellOwner(GenericOwner):
self._cur_activity_changed_cb) self._cur_activity_changed_cb)
def _icon_changed_cb(self, icon): def _icon_changed_cb(self, icon):
"""Handle icon change, set property to generate event"""
self.props.icon = icon self.props.icon = icon
def _color_changed_cb(self, color): def _color_changed_cb(self, color):
props = {'color': color} """Handle color change, set property to generate event"""
props = {_PROP_COLOR: color}
self.set_properties(props) self.set_properties(props)
def _nick_changed_cb(self, nick): def _nick_changed_cb(self, nick):
props = {'nick': nick} """Handle nickname change, set property to generate event"""
props = {_PROP_NICK: nick}
self.set_properties(props) self.set_properties(props)
def _cur_activity_changed_cb(self, activity_id): def _cur_activity_changed_cb(self, activity_id):
"""Handle current-activity change, set property to generate event
Filters out local activities (those not in self.activites)
because the network users can't join those activities, so
the activity_id shared will be None in those cases...
"""
if not self._activities.has_key(activity_id): if not self._activities.has_key(activity_id):
# This activity is local-only # This activity is local-only
activity_id = None activity_id = None
props = {'current-activity': activity_id} props = {_PROP_CURACT: activity_id}
self.set_properties(props) self.set_properties(props)
@ -371,9 +547,12 @@ class TestOwner(GenericOwner):
__gtype_name__ = "TestOwner" __gtype_name__ = "TestOwner"
def __init__(self, bus_name, object_id, test_num): def __init__(self, ps, bus_name, object_id, test_num):
self._cp = ConfigParser() self._cp = ConfigParser()
self._section = "Info" self._section = "Info"
self._test_activities = []
self._test_cur_act = ""
self._change_timeout = 0
self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num) self._cfg_file = os.path.join(env.get_profile_path(), 'test-buddy-%d' % test_num)
@ -392,11 +571,46 @@ class TestOwner(GenericOwner):
color = xocolor.XoColor().to_string() color = xocolor.XoColor().to_string()
icon = _get_random_image() icon = _get_random_image()
GenericOwner.__init__(self, bus_name, object_id, key=pubkey, nick=nick, logging.debug("pubkey is %s" % pubkey)
GenericOwner.__init__(self, ps, bus_name, object_id, key=pubkey, nick=nick,
color=color, icon=icon, registered=registered, key_hash=privkey_hash) color=color, icon=icon, registered=registered, key_hash=privkey_hash)
self._ps.connect('connection-status', self._ps_connection_status_cb)
def _share_reply_cb(self, actid, object_path):
activity = self._ps.internal_get_activity(actid)
if not activity or not object_path:
logging.debug("Couldn't find activity %s even though it was shared." % actid)
return
logging.debug("Shared activity %s (%s)." % (actid, activity.props.name))
self._test_activities.append(activity)
def _share_error_cb(self, actid, err):
logging.debug("Error sharing activity %s: %s" % (actid, str(err)))
def _ps_connection_status_cb(self, ps, connected):
if not connected:
return
if not len(self._test_activities):
# Share some activities
actid = util.unique_id("Activity 1")
callbacks = (lambda *args: self._share_reply_cb(actid, *args),
lambda *args: self._share_error_cb(actid, *args))
atype = "org.laptop.WebActivity"
properties = {"foo": "bar"}
self._ps._share_activity(actid, atype, "Wembley Stadium", properties, callbacks)
actid2 = util.unique_id("Activity 2")
callbacks = (lambda *args: self._share_reply_cb(actid2, *args),
lambda *args: self._share_error_cb(actid2, *args))
atype = "org.laptop.WebActivity"
properties = {"baz": "bar"}
self._ps._share_activity(actid2, atype, "Maine Road", properties, callbacks)
# Change a random property ever 10 seconds # Change a random property ever 10 seconds
gobject.timeout_add(10000, self._update_something) if self._change_timeout == 0:
self._change_timeout = gobject.timeout_add(10000, self._update_something)
def set_registered(self, value): def set_registered(self, value):
if value: if value:
@ -437,23 +651,26 @@ class TestOwner(GenericOwner):
self.props.icon = _get_random_image() self.props.icon = _get_random_image()
elif it == 1: elif it == 1:
from sugar.graphics import xocolor from sugar.graphics import xocolor
props = {'color': xocolor.XoColor().to_string()} props = {_PROP_COLOR: xocolor.XoColor().to_string()}
self.set_properties(props) self.set_properties(props)
elif it == 2: elif it == 2:
props = {'nick': _get_random_name()} props = {_PROP_NICK: _get_random_name()}
self.set_properties(props) self.set_properties(props)
elif it == 3: elif it == 3:
bork = random.randint(25, 65) actid = ""
it = "" idx = random.randint(0, len(self._test_activities))
for i in range(0, bork): # if idx == len(self._test_activites), it means no current
it += chr(random.randint(40, 127)) # activity
from sugar import util if idx < len(self._test_activities):
props = {'current-activity': util.unique_id(it)} activity = self._test_activities[idx]
actid = activity.props.id
props = {_PROP_CURACT: actid}
self.set_properties(props) self.set_properties(props)
return True return True
def _hash_private_key(self): def _hash_private_key(self):
"""Unused method to has a private key, see profile"""
self.privkey_hash = None self.privkey_hash = None
key_path = os.path.join(env.get_profile_path(), 'owner.key') key_path = os.path.join(env.get_profile_path(), 'owner.key')
@ -504,6 +721,7 @@ def _extract_public_key(keyfile):
return key return key
def _extract_private_key(keyfile): def _extract_private_key(keyfile):
"""Get a private key from a private key file"""
# Extract the private key # Extract the private key
try: try:
f = open(keyfile, "r") f = open(keyfile, "r")
@ -527,6 +745,7 @@ def _extract_private_key(keyfile):
return key return key
def _get_new_keypair(num): def _get_new_keypair(num):
"""Retrieve a public/private key pair for testing"""
# Generate keypair # Generate keypair
privkeyfile = os.path.join("/tmp", "test%d.key" % num) privkeyfile = os.path.join("/tmp", "test%d.key" % num)
pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num) pubkeyfile = os.path.join("/tmp", 'test%d.key.pub' % num)
@ -559,10 +778,12 @@ def _get_new_keypair(num):
return (pubkey, privkey) return (pubkey, privkey)
def _get_random_name(): def _get_random_name():
"""Produce random names for testing"""
names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"] names = ["Liam", "Noel", "Guigsy", "Whitey", "Bonehead"]
return names[random.randint(0, len(names) - 1)] return names[random.randint(0, len(names) - 1)]
def _get_random_image(): def _get_random_image():
"""Produce a random image for display"""
import cairo, math, random, gtk import cairo, math, random, gtk
def rand(): def rand():

View File

@ -40,24 +40,36 @@ class NotFoundError(dbus.DBusException):
dbus.DBusException.__init__(self) dbus.DBusException.__init__(self)
self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound' self._dbus_error_name = _PRESENCE_INTERFACE + '.NotFound'
class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pass
class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass
class PresenceService(DBusGObject):
__gtype_name__ = "PresenceService"
__gsignals__ = {
'connection-status': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_BOOLEAN]))
}
class PresenceService(dbus.service.Object):
def __init__(self, test=0): def __init__(self, test=0):
self._next_object_id = 0 self._next_object_id = 0
self._connected = False
self._buddies = {} # key -> Buddy self._buddies = {} # key -> Buddy
self._handles_buddies = {} # tp client -> (handle -> Buddy) self._handles_buddies = {} # tp client -> (handle -> Buddy)
self._activities = {} # activity id -> Activity self._activities = {} # activity id -> Activity
gobject.GObject.__init__(self)
bus = dbus.SessionBus() bus = dbus.SessionBus()
self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus) self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=bus)
# Create the Owner object # Create the Owner object
objid = self._get_next_object_id() objid = self._get_next_object_id()
if test > 0: if test > 0:
self._owner = TestOwner(self._bus_name, objid, test) self._owner = TestOwner(self, self._bus_name, objid, test)
else: else:
self._owner = ShellOwner(self._bus_name, objid) self._owner = ShellOwner(self, self._bus_name, objid)
self._buddies[self._owner.props.key] = self._owner self._buddies[self._owner.props.key] = self._owner
self._registry = ManagerRegistry() self._registry = ManagerRegistry()
@ -92,8 +104,15 @@ class PresenceService(dbus.service.Object):
async_err_cb(exc) async_err_cb(exc)
def _server_status_cb(self, plugin, status, reason): def _server_status_cb(self, plugin, status, reason):
# FIXME: figure out connection status when we have a salut plugin too
old_status = self._connected
if status == CONNECTION_STATUS_CONNECTED: if status == CONNECTION_STATUS_CONNECTED:
pass self._connected = True
else:
self._connected = False
if self._connected != old_status:
self.emit('connection-status', self._connected)
def _contact_online(self, tp, handle, props): def _contact_online(self, tp, handle, props):
new_buddy = False new_buddy = False
@ -120,8 +139,11 @@ class PresenceService(dbus.service.Object):
else: else:
self.BuddyDisappeared(buddy.object_path()) self.BuddyDisappeared(buddy.object_path())
logging.debug("Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color)) logging.debug("Buddy left: %s (%s)" % (buddy.props.nick, buddy.props.color))
def _contact_offline(self, tp, handle): def _contact_offline(self, tp, handle):
if not self._handles_buddies[tp].has_key(handle):
return
buddy = self._handles_buddies[tp].pop(handle) buddy = self._handles_buddies[tp].pop(handle)
key = buddy.props.key key = buddy.props.key
@ -144,11 +166,11 @@ class PresenceService(dbus.service.Object):
logging.debug("Buddy %s icon updated" % buddy.props.nick) logging.debug("Buddy %s icon updated" % buddy.props.nick)
buddy.props.icon = avatar buddy.props.icon = avatar
def _buddy_properties_changed(self, tp, handle, prop): def _buddy_properties_changed(self, tp, handle, properties):
buddy = self._handles_buddies[tp].get(handle) buddy = self._handles_buddies[tp].get(handle)
if buddy: if buddy:
buddy.set_properties(prop) buddy.set_properties(properties)
logging.debug("Buddy %s properties updated" % buddy.props.nick) logging.debug("Buddy %s properties updated: %s" % (buddy.props.nick, properties.keys()))
def _new_activity(self, activity_id, tp): def _new_activity(self, activity_id, tp):
try: try:
@ -257,11 +279,10 @@ class PresenceService(dbus.service.Object):
@dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o") @dbus.service.method(_PRESENCE_INTERFACE, in_signature="s", out_signature="o")
def GetActivityById(self, actid): def GetActivityById(self, actid):
if self._activities.has_key(actid): act = self.internal_get_activity(actid)
act = self._activities[actid] if not act or not act.props.valid:
if act.props.valid: raise NotFoundError("The activity was not found.")
return act.object_path() return act.object_path()
raise NotFoundError("The activity was not found.")
@dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao") @dbus.service.method(_PRESENCE_INTERFACE, out_signature="ao")
def GetBuddies(self): def GetBuddies(self):
@ -330,6 +351,11 @@ class PresenceService(dbus.service.Object):
if activity: if activity:
activity.set_properties(props) activity.set_properties(props)
def internal_get_activity(self, actid):
if not self._activities.has_key(actid):
return None
return self._activities[actid]
def main(test=False): def main(test=False):
loop = gobject.MainLoop() loop = gobject.MainLoop()

View File

@ -16,7 +16,11 @@
def bytes_to_string(bytes): def bytes_to_string(bytes):
# Handle both DBus byte arrays and strings """The function converts a D-BUS byte array provided by dbus to string format.
bytes -- a D-Bus array of bytes. Handle both DBus byte arrays and strings
"""
try: try:
# DBus Byte array # DBus Byte array
ret = ''.join([chr(item) for item in bytes]) ret = ''.join([chr(item) for item in bytes])

View File

@ -1,3 +1,4 @@
"""Telepathy-python presence server interface/implementation plugin"""
# Copyright (C) 2007, Red Hat, Inc. # Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, Collabora Ltd. # Copyright (C) 2007, Collabora Ltd.
# #
@ -41,7 +42,7 @@ CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
_PROTOCOL = "jabber" _PROTOCOL = "jabber"
class InvalidBuddyError(Exception): class InvalidBuddyError(Exception):
pass """(Unused) exception to indicate an invalid buddy specifier"""
def _buddy_icon_save_cb(buf, data): def _buddy_icon_save_cb(buf, data):
data[0] += buf data[0] += buf
@ -76,6 +77,13 @@ def _get_buddy_icon_at_size(icon, maxw, maxh, maxsize):
class ServerPlugin(gobject.GObject): class ServerPlugin(gobject.GObject):
"""Telepathy-python-based presence server interface
The ServerPlugin instance translates network events from
Telepathy Python into GObject events. It provides direct
python calls to perform the required network operations
to implement the PresenceService.
"""
__gsignals__ = { __gsignals__ = {
'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'contact-online': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
@ -104,6 +112,14 @@ class ServerPlugin(gobject.GObject):
} }
def __init__(self, registry, owner): def __init__(self, registry, owner):
"""Initialize the ServerPlugin instance
registry -- telepathy.client.ManagerRegistry from the
PresenceService, used to find the "gabble" connection
manager in this case...
owner -- presence.buddy.GenericOwner instance (normally a
presence.buddy.ShellOwner instance)
"""
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._icon_cache = BuddyIconCache() self._icon_cache = BuddyIconCache()
@ -125,6 +141,20 @@ class ServerPlugin(gobject.GObject):
self._reconnect_id = 0 self._reconnect_id = 0
def _owner_property_changed_cb(self, owner, properties): def _owner_property_changed_cb(self, owner, properties):
"""Local user's configuration properties have changed
owner -- the Buddy object for the local user
properties -- set of updated properties
calls:
_set_self_current_activity current-activity
_set_self_alias nick
_set_self_olpc_properties color
depending on which properties are present in the
set of properties.
"""
logging.debug("Owner properties changed: %s" % properties) logging.debug("Owner properties changed: %s" % properties)
if properties.has_key("current-activity"): if properties.has_key("current-activity"):
@ -137,10 +167,21 @@ class ServerPlugin(gobject.GObject):
self._set_self_olpc_properties() self._set_self_olpc_properties()
def _owner_icon_changed_cb(self, owner, icon): def _owner_icon_changed_cb(self, owner, icon):
"""Owner has changed their icon, forward to network"""
logging.debug("Owner icon changed to size %d" % len(str(icon))) logging.debug("Owner icon changed to size %d" % len(str(icon)))
self._set_self_avatar(icon) self._set_self_avatar(icon)
def _get_account_info(self): def _get_account_info(self):
"""Retrieve metadata dictionary describing this account
returns dictionary with:
server : server url from owner
account : printable-ssh-key-hash@server
password : ssh-key-hash
register : whether to register (i.e. whether not yet
registered)
"""
account_info = {} account_info = {}
account_info['server'] = self._owner.get_server() account_info['server'] = self._owner.get_server()
@ -155,6 +196,16 @@ class ServerPlugin(gobject.GObject):
return account_info return account_info
def _find_existing_connection(self): def _find_existing_connection(self):
"""Try to find an existing Telepathy connection to this server
filters the set of connections from
telepathy.client.Connection.get_connections
to find a connection using our protocol with the
"self handle" of that connection being a handle
which matches our account (see _get_account_info)
returns connection or None
"""
our_name = self._account['account'] our_name = self._account['account']
# Search existing connections, if any, that we might be able to use # Search existing connections, if any, that we might be able to use
@ -173,9 +224,20 @@ class ServerPlugin(gobject.GObject):
return None return None
def get_connection(self): def get_connection(self):
"""Retrieve our telepathy.client.Connection object"""
return self._conn return self._conn
def _init_connection(self): def _init_connection(self):
"""Set up our connection
if there is no existing connection
(_find_existing_connection returns None)
produce a new connection with our protocol for our
account.
if there is an existing connection, reuse it by
registering for various of events on it.
"""
conn = self._find_existing_connection() conn = self._find_existing_connection()
if not conn: if not conn:
acct = self._account.copy() acct = self._account.copy()
@ -201,6 +263,10 @@ class ServerPlugin(gobject.GObject):
return conn return conn
def _request_list_channel(self, name): def _request_list_channel(self, name):
"""Request a contact-list channel from Telepathy
name -- publish/subscribe, for the type of channel
"""
handle = self._conn[CONN_INTERFACE].RequestHandles( handle = self._conn[CONN_INTERFACE].RequestHandles(
CONNECTION_HANDLE_TYPE_LIST, [name])[0] CONNECTION_HANDLE_TYPE_LIST, [name])[0]
chan_path = self._conn[CONN_INTERFACE].RequestChannel( chan_path = self._conn[CONN_INTERFACE].RequestChannel(
@ -212,6 +278,9 @@ class ServerPlugin(gobject.GObject):
return channel return channel
def _connected_cb(self): def _connected_cb(self):
"""Callback on successful connection to a server
"""
if self._account['register']: if self._account['register']:
# we successfully register this account # we successfully register this account
self._owner.props.registered = True self._owner.props.registered = True
@ -257,7 +326,7 @@ class ServerPlugin(gobject.GObject):
self._activity_properties_changed_cb) self._activity_properties_changed_cb)
# Set initial buddy properties, avatar, and activities # Set initial buddy properties, avatar, and activities
self._set_self_olpc_properties(True) self._set_self_olpc_properties()
self._set_self_alias() self._set_self_alias()
self._set_self_activities() self._set_self_activities()
self._set_self_current_activity() self._set_self_current_activity()
@ -324,34 +393,58 @@ class ServerPlugin(gobject.GObject):
def _internal_join_activity(self, activity_id, signal, userdata): def _internal_join_activity(self, activity_id, signal, userdata):
handle = self._activities.get(activity_id) handle = self._activities.get(activity_id)
if not handle: if not handle:
self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [activity_id], # FIXME: figure out why the server can't figure this out itself
room_jid = activity_id + "@conference." + self._account["server"]
self._conn[CONN_INTERFACE].RequestHandles(CONNECTION_HANDLE_TYPE_ROOM, [room_jid],
reply_handler=lambda *args: self._join_activity_get_channel_cb(activity_id, signal, userdata, *args), reply_handler=lambda *args: self._join_activity_get_channel_cb(activity_id, signal, userdata, *args),
error_handler=lambda *args: self._join_error_cb(activity_id, signal, userdata, *args)) error_handler=lambda *args: self._join_error_cb(activity_id, signal, userdata, *args))
else: else:
self._join_activity_get_channel_cb(activity_id, userdata, [handle]) self._join_activity_get_channel_cb(activity_id, userdata, [handle])
def share_activity(self, activity_id, userdata): def share_activity(self, activity_id, userdata):
"""Share activity with the network
activity_id -- unique ID for the activity
userdata -- opaque token to be passed in the resulting event
(id, callback, errback) normally
Asks the Telepathy server to create a "conference" channel
for the activity or return a handle to an already created
conference channel for the activity.
"""
self._internal_join_activity(activity_id, "activity-shared", userdata) self._internal_join_activity(activity_id, "activity-shared", userdata)
def join_activity(self, activity_id, userdata): def join_activity(self, activity_id, userdata):
"""Join an activity on the network (or locally)
activity_id -- unique ID for the activity
userdata -- opaque token to be passed in the resulting event
(id, callback, errback) normally
Asks the Telepathy server to create a "conference" channel
for the activity or return a handle to an already created
conference channel for the activity.
"""
self._internal_join_activity(activity_id, "activity-joined", userdata) self._internal_join_activity(activity_id, "activity-joined", userdata)
def _ignore_success_cb(self): def _ignore_success_cb(self):
pass """Ignore an event (null-operation)"""
def _log_error_cb(self, msg, err): def _log_error_cb(self, msg, err):
"""Log a message (error) at debug level with prefix msg"""
logging.debug("Error %s: %s" % (msg, err)) logging.debug("Error %s: %s" % (msg, err))
def _set_self_olpc_properties(self, set_key=False): def _set_self_olpc_properties(self):
"""Set color and key on our Telepathy server identity"""
props = {} props = {}
props['color'] = self._owner.props.color props['color'] = self._owner.props.color
if set_key: props['key'] = dbus.ByteArray(self._owner.props.key)
props['key'] = dbus.ByteArray(self._owner.props.key)
self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props, self._conn[CONN_INTERFACE_BUDDY_INFO].SetProperties(props,
reply_handler=self._ignore_success_cb, reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting properties", *args)) error_handler=lambda *args: self._log_error_cb("setting properties", *args))
def _set_self_alias(self): def _set_self_alias(self):
"""Forwarded to SetActivities on AliasInfo channel"""
alias = self._owner.props.nick alias = self._owner.props.nick
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias}, self._conn[CONN_INTERFACE_ALIASING].SetAliases({self_handle : alias},
@ -359,11 +452,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting alias", *args)) error_handler=lambda *args: self._log_error_cb("setting alias", *args))
def _set_self_activities(self): def _set_self_activities(self):
"""Forward set of joined activities to network
uses SetActivities on BuddyInfo channel
"""
self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities, self._conn[CONN_INTERFACE_BUDDY_INFO].SetActivities(self._joined_activities,
reply_handler=self._ignore_success_cb, reply_handler=self._ignore_success_cb,
error_handler=lambda *args: self._log_error_cb("setting activities", *args)) error_handler=lambda *args: self._log_error_cb("setting activities", *args))
def _set_self_current_activity(self): def _set_self_current_activity(self):
"""Forward our current activity (or "") to network
uses SetCurrentActivity on BuddyInfo channel
"""
cur_activity = self._owner.props.current_activity cur_activity = self._owner.props.current_activity
cur_activity_handle = 0 cur_activity_handle = 0
if not cur_activity: if not cur_activity:
@ -381,12 +482,20 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting current activity", *args)) error_handler=lambda *args: self._log_error_cb("setting current activity", *args))
def _get_handle_for_activity(self, activity_id): def _get_handle_for_activity(self, activity_id):
"""Retrieve current handle for given activity or None"""
for (act, handle) in self._joined_activities: for (act, handle) in self._joined_activities:
if activity_id == act: if activity_id == act:
return handle return handle
return None return None
def _status_changed_cb(self, state, reason): def _status_changed_cb(self, state, reason):
"""Handle notification of connection-status change
state -- CONNECTION_STATUS_*
reason -- integer code describing the reason...
returns False XXX what does that mean?
"""
if state == CONNECTION_STATUS_CONNECTING: if state == CONNECTION_STATUS_CONNECTING:
logging.debug("State: connecting...") logging.debug("State: connecting...")
elif state == CONNECTION_STATUS_CONNECTED: elif state == CONNECTION_STATUS_CONNECTED:
@ -403,6 +512,16 @@ class ServerPlugin(gobject.GObject):
return False return False
def start(self): def start(self):
"""Start up the Telepathy networking connections
if we are already connected, query for the initial contact
information.
if we are already connecting, do nothing
otherwise initiate a connection and transfer control to
_connect_reply_cb or _connect_error_cb
"""
logging.debug("Starting up...") logging.debug("Starting up...")
# If the connection is already connected query initial contacts # If the connection is already connected query initial contacts
conn_status = self._conn[CONN_INTERFACE].GetStatus() conn_status = self._conn[CONN_INTERFACE].GetStatus()
@ -418,25 +537,30 @@ class ServerPlugin(gobject.GObject):
error_handler=self._connect_error_cb) error_handler=self._connect_error_cb)
def _connect_reply_cb(self): def _connect_reply_cb(self):
"""Handle connection success"""
if self._reconnect_id > 0: if self._reconnect_id > 0:
gobject.source_remove(self._reconnect_id) gobject.source_remove(self._reconnect_id)
def _reconnect(self): def _reconnect(self):
"""Reset number-of-attempted connections and re-attempt"""
self._reconnect_id = 0 self._reconnect_id = 0
self.start() self.start()
return False return False
def _connect_error_cb(self, exception): def _connect_error_cb(self, exception):
"""Handle connection failure"""
logging.debug("Connect error: %s" % exception) logging.debug("Connect error: %s" % exception)
if not self._reconnect_id: if not self._reconnect_id:
self._reconnect_id = gobject.timeout_add(10000, self._reconnect) self._reconnect_id = gobject.timeout_add(10000, self._reconnect)
def cleanup(self): def cleanup(self):
"""If we still have a connection, disconnect it"""
if not self._conn: if not self._conn:
return return
self._conn[CONN_INTERFACE].Disconnect() self._conn[CONN_INTERFACE].Disconnect()
def _contact_offline(self, handle): def _contact_offline(self, handle):
"""Handle contact going offline (send message, update set)"""
if not self._online_contacts.has_key(handle): if not self._online_contacts.has_key(handle):
return return
if self._online_contacts[handle]: if self._online_contacts[handle]:
@ -444,13 +568,16 @@ class ServerPlugin(gobject.GObject):
del self._online_contacts[handle] del self._online_contacts[handle]
def _contact_online_activities_cb(self, handle, activities): def _contact_online_activities_cb(self, handle, activities):
"""Handle contact's activity list update"""
self._buddy_activities_changed_cb(handle, activities) self._buddy_activities_changed_cb(handle, activities)
def _contact_online_activities_error_cb(self, handle, err): def _contact_online_activities_error_cb(self, handle, err):
"""Handle contact's activity list being unavailable"""
logging.debug("Handle %s - Error getting activities: %s" % (handle, err)) logging.debug("Handle %s - Error getting activities: %s" % (handle, err))
self._contact_offline(handle) self._contact_offline(handle)
def _contact_online_aliases_cb(self, handle, props, aliases): def _contact_online_aliases_cb(self, handle, props, aliases):
"""Handle contact's alias being received (do further queries)"""
if not aliases or not len(aliases): if not aliases or not len(aliases):
logging.debug("Handle %s - No aliases" % handle) logging.debug("Handle %s - No aliases" % handle)
self._contact_offline(handle) self._contact_offline(handle)
@ -466,10 +593,12 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_activities_error_cb(handle, *args)) error_handler=lambda *args: self._contact_online_activities_error_cb(handle, *args))
def _contact_online_aliases_error_cb(self, handle, err): def _contact_online_aliases_error_cb(self, handle, err):
"""Handle failure to retrieve given user's alias/information"""
logging.debug("Handle %s - Error getting nickname: %s" % (handle, err)) logging.debug("Handle %s - Error getting nickname: %s" % (handle, err))
self._contact_offline(handle) self._contact_offline(handle)
def _contact_online_properties_cb(self, handle, props): def _contact_online_properties_cb(self, handle, props):
"""Handle failure to retrieve given user's alias/information"""
if not props.has_key('key'): if not props.has_key('key'):
logging.debug("Handle %s - invalid key." % handle) logging.debug("Handle %s - invalid key." % handle)
self._contact_offline(handle) self._contact_offline(handle)
@ -487,16 +616,19 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._contact_online_aliases_error_cb(handle, *args)) error_handler=lambda *args: self._contact_online_aliases_error_cb(handle, *args))
def _contact_online_properties_error_cb(self, handle, err): def _contact_online_properties_error_cb(self, handle, err):
"""Handle error retrieving property-set for a user (handle)"""
logging.debug("Handle %s - Error getting properties: %s" % (handle, err)) logging.debug("Handle %s - Error getting properties: %s" % (handle, err))
self._contact_offline(handle) self._contact_offline(handle)
def _contact_online(self, handle): def _contact_online(self, handle):
"""Handle a contact coming online"""
self._online_contacts[handle] = None self._online_contacts[handle] = None
self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle, self._conn[CONN_INTERFACE_BUDDY_INFO].GetProperties(handle,
reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args), reply_handler=lambda *args: self._contact_online_properties_cb(handle, *args),
error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args)) error_handler=lambda *args: self._contact_online_properties_error_cb(handle, *args))
def _presence_update_cb(self, presence): def _presence_update_cb(self, presence):
"""Send update for online/offline status of presence"""
for handle in presence: for handle in presence:
timestamp, statuses = presence[handle] timestamp, statuses = presence[handle]
online = handle in self._online_contacts online = handle in self._online_contacts
@ -511,18 +643,19 @@ class ServerPlugin(gobject.GObject):
self._contact_offline(handle) self._contact_offline(handle)
def _avatar_updated_cb(self, handle, new_avatar_token): def _avatar_updated_cb(self, handle, new_avatar_token):
"""Handle update of given user (handle)'s avatar"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those # ignore network events for Owner property changes since those
# are handled locally # are handled locally
return return
if not self._online_contacts.has_key(handle): if not self._online_contacts.has_key(handle):
logging.debug("Handle %s not valid yet...") logging.debug("Handle %s unknown." % handle)
return return
jid = self._online_contacts[handle] jid = self._online_contacts[handle]
if not jid: if not jid:
logging.debug("Handle %s not valid yet...") logging.debug("Handle %s not valid yet..." % handle)
return return
icon = self._icon_cache.get_icon(jid, new_avatar_token) icon = self._icon_cache.get_icon(jid, new_avatar_token)
@ -535,6 +668,7 @@ class ServerPlugin(gobject.GObject):
self.emit("avatar-updated", handle, icon) self.emit("avatar-updated", handle, icon)
def _alias_changed_cb(self, aliases): def _alias_changed_cb(self, aliases):
"""Handle update of aliases for all users"""
for handle, alias in aliases: for handle, alias in aliases:
prop = {'nick': alias} prop = {'nick': alias}
#print "Buddy %s alias changed to %s" % (handle, alias) #print "Buddy %s alias changed to %s" % (handle, alias)
@ -542,14 +676,16 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop) self._buddy_properties_changed_cb(handle, prop)
def _buddy_properties_changed_cb(self, handle, properties): def _buddy_properties_changed_cb(self, handle, properties):
"""Handle update of given user (handle)'s properties"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner property changes since those # ignore network events for Owner property changes since those
# are handled locally # are handled locally
return return
if self._online_contacts.has_key(handle) and self._online_contacts[handle]: if self._online_contacts.has_key(handle) and self._online_contacts[handle]:
self.emit("buddy-properties-changed", handle, properties) self.emit("buddy-properties-changed", handle, properties)
def _buddy_activities_changed_cb(self, handle, activities): def _buddy_activities_changed_cb(self, handle, activities):
"""Handle update of given user (handle)'s activities"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner activity changes since those # ignore network events for Owner activity changes since those
# are handled locally # are handled locally
@ -563,6 +699,8 @@ class ServerPlugin(gobject.GObject):
self.emit("buddy-activities-changed", handle, activities_id) self.emit("buddy-activities-changed", handle, activities_id)
def _buddy_current_activity_changed_cb(self, handle, activity, channel): def _buddy_current_activity_changed_cb(self, handle, activity, channel):
"""Handle update of given user (handle)'s current activity"""
if handle == self._conn[CONN_INTERFACE].GetSelfHandle(): if handle == self._conn[CONN_INTERFACE].GetSelfHandle():
# ignore network events for Owner current activity changes since those # ignore network events for Owner current activity changes since those
# are handled locally # are handled locally
@ -577,6 +715,8 @@ class ServerPlugin(gobject.GObject):
self._buddy_properties_changed_cb(handle, prop) self._buddy_properties_changed_cb(handle, prop)
def _new_channel_cb(self, object_path, channel_type, handle_type, handle, suppress_handler): def _new_channel_cb(self, object_path, channel_type, handle_type, handle, suppress_handler):
"""Handle creation of a new channel
"""
if handle_type == CONNECTION_HANDLE_TYPE_ROOM and channel_type == CHANNEL_TYPE_TEXT: if handle_type == CONNECTION_HANDLE_TYPE_ROOM and channel_type == CHANNEL_TYPE_TEXT:
channel = Channel(self._conn._dbus_object._named_service, object_path) channel = Channel(self._conn._dbus_object._named_service, object_path)
@ -595,6 +735,7 @@ class ServerPlugin(gobject.GObject):
self.emit("private-invitation", object_path) self.emit("private-invitation", object_path)
def update_activity_properties(self, act_id): def update_activity_properties(self, act_id):
"""Request update from network on the activity properties of act_id"""
handle = self._activities.get(act_id) handle = self._activities.get(act_id)
if not handle: if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.") raise RuntimeError("Unknown activity %s: couldn't find handle.")
@ -604,6 +745,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("getting activity properties", *args)) error_handler=lambda *args: self._log_error_cb("getting activity properties", *args))
def set_activity_properties(self, act_id, props): def set_activity_properties(self, act_id, props):
"""Send update to network on the activity properties of act_id (props)"""
handle = self._activities.get(act_id) handle = self._activities.get(act_id)
if not handle: if not handle:
raise RuntimeError("Unknown activity %s: couldn't find handle.") raise RuntimeError("Unknown activity %s: couldn't find handle.")
@ -613,6 +755,7 @@ class ServerPlugin(gobject.GObject):
error_handler=lambda *args: self._log_error_cb("setting activity properties", *args)) error_handler=lambda *args: self._log_error_cb("setting activity properties", *args))
def _activity_properties_changed_cb(self, room, properties): def _activity_properties_changed_cb(self, room, properties):
"""Handle update of properties for a "room" (activity handle)"""
for act_id, act_handle in self._activities.items(): for act_id, act_handle in self._activities.items():
if room == act_handle: if room == act_handle:
self.emit("activity-properties-changed", act_id, properties) self.emit("activity-properties-changed", act_id, properties)

View File

@ -24,7 +24,7 @@ import os
from sugar import logger from sugar import logger
from sugar import env from sugar import env
sys.path.insert(0, env.get_service_path('presence')) sys.path.append(env.get_service_path('presence'))
test=0 test=0
if len(sys.argv) > 1: if len(sys.argv) > 1:

View File

@ -4,7 +4,6 @@ bin_SCRIPTS = sugar-shell
sugardir = $(pkgdatadir)/shell sugardir = $(pkgdatadir)/shell
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \
shellservice.py shellservice.py
confdir = $(pkgdatadir)/shell confdir = $(pkgdatadir)/shell

View File

@ -1,27 +1,26 @@
sugardir = $(pkgdatadir)/shell sugardir = $(pkgdatadir)/shell/extensions
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py __init__.py
pkgpyexecdir = $(pkgdatadir)/shell pkgpyexecdir = $(pkgdatadir)/shell/extensions
pkgpyexec_LTLIBRARIES = extensions.la pkgpyexec_LTLIBRARIES = _extensions.la
extensions_la_LDFLAGS = -module -avoid-version _extensions_la_LDFLAGS = -module -avoid-version
extensions_la_CFLAGS = \ _extensions_la_CFLAGS = \
$(WARN_CFLAGS) \ $(WARN_CFLAGS) \
$(PYTHON_INCLUDES) \ $(PYTHON_INCLUDES) \
$(PYGTK_CFLAGS) \ $(PYGTK_CFLAGS) \
$(SHELL_CFLAGS) \ $(SHELL_CFLAGS)
$(top_srcdir)/shell/extensions
extensions_la_LIBADD = \ _extensions_la_LIBADD = \
$(SHELL_LIBS) \ $(SHELL_LIBS) \
$(PYCAIRO_LIBS) \ $(PYCAIRO_LIBS) \
-lgstinterfaces-0.10 \ -lgstinterfaces-0.10 \
-lgstaudio-0.10 -lgstaudio-0.10
extensions_la_SOURCES = \ _extensions_la_SOURCES = \
$(BUILT_SOURCES) \ $(BUILT_SOURCES) \
eggaccelerators.h \ eggaccelerators.h \
eggaccelerators.c \ eggaccelerators.c \
@ -29,10 +28,10 @@ extensions_la_SOURCES = \
sugar-audio-manager.h \ sugar-audio-manager.h \
sugar-key-grabber.h \ sugar-key-grabber.h \
sugar-key-grabber.c \ sugar-key-grabber.c \
extensionsmodule.c _extensionsmodule.c
BUILT_SOURCES = \ BUILT_SOURCES = \
extensions.c \ _extensions.c \
sugar-shell-marshal.c \ sugar-shell-marshal.c \
sugar-shell-marshal.h sugar-shell-marshal.h
@ -58,9 +57,9 @@ CLEANFILES = $(stamp_files) $(BUILT_SOURCES)
DISTCLEANFILES = $(stamp_files) $(BUILT_SOURCES) DISTCLEANFILES = $(stamp_files) $(BUILT_SOURCES)
MAINTAINERCLEANFILES = $(stamp_files) $(BUILT_SOURCES) MAINTAINERCLEANFILES = $(stamp_files) $(BUILT_SOURCES)
EXTRA_DIST = sugar-marshal.list extensions.override extensions.defs EXTRA_DIST = sugar-marshal.list _extensions.override _extensions.defs
extensions.c: extensions.defs extensions.override extensions.c: _extensions.defs _extensions.override
.defs.c: .defs.c:
(cd $(srcdir)\ (cd $(srcdir)\

View File

@ -14,4 +14,8 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from extensions import extensions try:
from extensions._extensions import *
except ImportError:
from sugar import ltihooks
from extensions._extensions import *

View File

@ -5,21 +5,21 @@
/* include this first, before NO_IMPORT_PYGOBJECT is defined */ /* include this first, before NO_IMPORT_PYGOBJECT is defined */
#include <pygobject.h> #include <pygobject.h>
void pyextensions_register_classes (PyObject *d); void py_extensions_register_classes (PyObject *d);
extern PyMethodDef pyextensions_functions[]; extern PyMethodDef py_extensions_functions[];
DL_EXPORT(void) DL_EXPORT(void)
initextensions(void) init_extensions(void)
{ {
PyObject *m, *d; PyObject *m, *d;
init_pygobject (); init_pygobject ();
m = Py_InitModule ("extensions", pyextensions_functions); m = Py_InitModule ("_extensions", py_extensions_functions);
d = PyModule_GetDict (m); d = PyModule_GetDict (m);
pyextensions_register_classes (d); py_extensions_register_classes (d);
if (PyErr_Occurred ()) { if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugar"); Py_FatalError ("can't initialise module _sugar");

View File

@ -1,260 +0,0 @@
/* -- THIS FILE IS GENERATED - DO NOT EDIT *//* -*- Mode: C; c-basic-offset: 4 -*- */
#include <Python.h>
#line 4 "extensions.override"
#include <Python.h>
#include "pygobject.h"
#include "sugar-key-grabber.h"
#include "sugar-address-entry.h"
#include "sugar-audio-manager.h"
#line 16 "extensions.c"
/* ---------- types from other modules ---------- */
static PyTypeObject *_PyGObject_Type;
#define PyGObject_Type (*_PyGObject_Type)
static PyTypeObject *_PyGtkEntry_Type;
#define PyGtkEntry_Type (*_PyGtkEntry_Type)
/* ---------- forward type declarations ---------- */
PyTypeObject G_GNUC_INTERNAL PySugarKeyGrabber_Type;
PyTypeObject G_GNUC_INTERNAL PySugarAudioManager_Type;
#line 30 "extensions.c"
/* ----------- SugarKeyGrabber ----------- */
static PyObject *
_wrap_sugar_key_grabber_grab(PyGObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = { "key", NULL };
char *key;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,"s:SugarKeyGrabber.grab", kwlist, &key))
return NULL;
sugar_key_grabber_grab(SUGAR_KEY_GRABBER(self->obj), key);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
_wrap_sugar_key_grabber_get_key(PyGObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = { "keycode", "state", NULL };
PyObject *py_keycode = NULL, *py_state = NULL;
gchar *ret;
guint keycode = 0, state = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,"OO:SugarKeyGrabber.get_key", kwlist, &py_keycode, &py_state))
return NULL;
if (py_keycode) {
if (PyLong_Check(py_keycode))
keycode = PyLong_AsUnsignedLong(py_keycode);
else if (PyInt_Check(py_keycode))
keycode = PyInt_AsLong(py_keycode);
else
PyErr_SetString(PyExc_TypeError, "Parameter 'keycode' must be an int or a long");
if (PyErr_Occurred())
return NULL;
}
if (py_state) {
if (PyLong_Check(py_state))
state = PyLong_AsUnsignedLong(py_state);
else if (PyInt_Check(py_state))
state = PyInt_AsLong(py_state);
else
PyErr_SetString(PyExc_TypeError, "Parameter 'state' must be an int or a long");
if (PyErr_Occurred())
return NULL;
}
ret = sugar_key_grabber_get_key(SUGAR_KEY_GRABBER(self->obj), keycode, state);
if (ret) {
PyObject *py_ret = PyString_FromString(ret);
g_free(ret);
return py_ret;
}
Py_INCREF(Py_None);
return Py_None;
}
static const PyMethodDef _PySugarKeyGrabber_methods[] = {
{ "grab", (PyCFunction)_wrap_sugar_key_grabber_grab, METH_VARARGS|METH_KEYWORDS,
NULL },
{ "get_key", (PyCFunction)_wrap_sugar_key_grabber_get_key, METH_VARARGS|METH_KEYWORDS,
NULL },
{ NULL, NULL, 0, NULL }
};
PyTypeObject G_GNUC_INTERNAL PySugarKeyGrabber_Type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
"extensions.KeyGrabber", /* tp_name */
sizeof(PyGObject), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)0, /* tp_dealloc */
(printfunc)0, /* tp_print */
(getattrfunc)0, /* tp_getattr */
(setattrfunc)0, /* tp_setattr */
(cmpfunc)0, /* tp_compare */
(reprfunc)0, /* tp_repr */
(PyNumberMethods*)0, /* tp_as_number */
(PySequenceMethods*)0, /* tp_as_sequence */
(PyMappingMethods*)0, /* tp_as_mapping */
(hashfunc)0, /* tp_hash */
(ternaryfunc)0, /* tp_call */
(reprfunc)0, /* tp_str */
(getattrofunc)0, /* tp_getattro */
(setattrofunc)0, /* tp_setattro */
(PyBufferProcs*)0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
NULL, /* Documentation string */
(traverseproc)0, /* tp_traverse */
(inquiry)0, /* tp_clear */
(richcmpfunc)0, /* tp_richcompare */
offsetof(PyGObject, weakreflist), /* tp_weaklistoffset */
(getiterfunc)0, /* tp_iter */
(iternextfunc)0, /* tp_iternext */
(struct PyMethodDef*)_PySugarKeyGrabber_methods, /* tp_methods */
(struct PyMemberDef*)0, /* tp_members */
(struct PyGetSetDef*)0, /* tp_getset */
NULL, /* tp_base */
NULL, /* tp_dict */
(descrgetfunc)0, /* tp_descr_get */
(descrsetfunc)0, /* tp_descr_set */
offsetof(PyGObject, inst_dict), /* tp_dictoffset */
(initproc)0, /* tp_init */
(allocfunc)0, /* tp_alloc */
(newfunc)0, /* tp_new */
(freefunc)0, /* tp_free */
(inquiry)0 /* tp_is_gc */
};
/* ----------- SugarAudioManager ----------- */
static PyObject *
_wrap_sugar_audio_manager_set_volume(PyGObject *self, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = { "level", NULL };
int level;
if (!PyArg_ParseTupleAndKeywords(args, kwargs,"i:SugarAudioManager.set_volume", kwlist, &level))
return NULL;
sugar_audio_manager_set_volume(SUGAR_AUDIO_MANAGER(self->obj), level);
Py_INCREF(Py_None);
return Py_None;
}
static const PyMethodDef _PySugarAudioManager_methods[] = {
{ "set_volume", (PyCFunction)_wrap_sugar_audio_manager_set_volume, METH_VARARGS|METH_KEYWORDS,
NULL },
{ NULL, NULL, 0, NULL }
};
PyTypeObject G_GNUC_INTERNAL PySugarAudioManager_Type = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
"extensions.AudioManager", /* tp_name */
sizeof(PyGObject), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)0, /* tp_dealloc */
(printfunc)0, /* tp_print */
(getattrfunc)0, /* tp_getattr */
(setattrfunc)0, /* tp_setattr */
(cmpfunc)0, /* tp_compare */
(reprfunc)0, /* tp_repr */
(PyNumberMethods*)0, /* tp_as_number */
(PySequenceMethods*)0, /* tp_as_sequence */
(PyMappingMethods*)0, /* tp_as_mapping */
(hashfunc)0, /* tp_hash */
(ternaryfunc)0, /* tp_call */
(reprfunc)0, /* tp_str */
(getattrofunc)0, /* tp_getattro */
(setattrofunc)0, /* tp_setattro */
(PyBufferProcs*)0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
NULL, /* Documentation string */
(traverseproc)0, /* tp_traverse */
(inquiry)0, /* tp_clear */
(richcmpfunc)0, /* tp_richcompare */
offsetof(PyGObject, weakreflist), /* tp_weaklistoffset */
(getiterfunc)0, /* tp_iter */
(iternextfunc)0, /* tp_iternext */
(struct PyMethodDef*)_PySugarAudioManager_methods, /* tp_methods */
(struct PyMemberDef*)0, /* tp_members */
(struct PyGetSetDef*)0, /* tp_getset */
NULL, /* tp_base */
NULL, /* tp_dict */
(descrgetfunc)0, /* tp_descr_get */
(descrsetfunc)0, /* tp_descr_set */
offsetof(PyGObject, inst_dict), /* tp_dictoffset */
(initproc)0, /* tp_init */
(allocfunc)0, /* tp_alloc */
(newfunc)0, /* tp_new */
(freefunc)0, /* tp_free */
(inquiry)0 /* tp_is_gc */
};
/* ----------- functions ----------- */
const PyMethodDef pyextensions_functions[] = {
{ NULL, NULL, 0, NULL }
};
/* initialise stuff extension classes */
void
pyextensions_register_classes(PyObject *d)
{
PyObject *module;
if ((module = PyImport_ImportModule("gobject")) != NULL) {
_PyGObject_Type = (PyTypeObject *)PyObject_GetAttrString(module, "GObject");
if (_PyGObject_Type == NULL) {
PyErr_SetString(PyExc_ImportError,
"cannot import name GObject from gobject");
return ;
}
} else {
PyErr_SetString(PyExc_ImportError,
"could not import gobject");
return ;
}
if ((module = PyImport_ImportModule("gtk")) != NULL) {
_PyGtkEntry_Type = (PyTypeObject *)PyObject_GetAttrString(module, "Entry");
if (_PyGtkEntry_Type == NULL) {
PyErr_SetString(PyExc_ImportError,
"cannot import name Entry from gtk");
return ;
}
} else {
PyErr_SetString(PyExc_ImportError,
"could not import gtk");
return ;
}
#line 256 "extensions.c"
pygobject_register_class(d, "SugarKeyGrabber", SUGAR_TYPE_KEY_GRABBER, &PySugarKeyGrabber_Type, Py_BuildValue("(O)", &PyGObject_Type));
pyg_set_object_has_new_constructor(SUGAR_TYPE_KEY_GRABBER);
pygobject_register_class(d, "SugarAudioManager", SUGAR_TYPE_AUDIO_MANAGER, &PySugarAudioManager_Type, Py_BuildValue("(O)", &PyGObject_Type));
pyg_set_object_has_new_constructor(SUGAR_TYPE_AUDIO_MANAGER);
}

View File

@ -84,6 +84,11 @@ class BuddyModel(gobject.GObject):
def get_buddy(self): def get_buddy(self):
return self._buddy return self._buddy
def is_owner(self):
if not self._buddy:
return False
return self._buddy.props.owner
def is_present(self): def is_present(self):
if self._buddy: if self._buddy:
return True return True
@ -125,6 +130,8 @@ class BuddyModel(gobject.GObject):
if 'color' in keys: if 'color' in keys:
self._set_color_from_string(self._buddy.props.color) self._set_color_from_string(self._buddy.props.color)
self.emit('color-changed', self.get_color()) self.emit('color-changed', self.get_color())
if 'current-activity' in keys:
self.emit('current-activity-changed', buddy.props.current_activity)
def _buddy_disappeared_cb(self, buddy): def _buddy_disappeared_cb(self, buddy):
if buddy != self._buddy: if buddy != self._buddy:
@ -139,8 +146,3 @@ class BuddyModel(gobject.GObject):
def _buddy_icon_changed_cb(self, buddy): def _buddy_icon_changed_cb(self, buddy):
self.emit('icon-changed') self.emit('icon-changed')
def _buddy_current_activity_changed_cb(self, buddy, activity=None):
if not self._buddy:
return
self.emit('current-activity-changed', activity)

View File

@ -160,19 +160,14 @@ class MeshModel(gobject.GObject):
def get_buddies(self): def get_buddies(self):
return self._buddies.values() return self._buddies.values()
def _buddy_activity_changed_cb(self, buddy, cur_activity): def _buddy_activity_changed_cb(self, model, cur_activity):
if not self._buddies.has_key(buddy.props.key): if not self._buddies.has_key(model.get_key()):
return return
buddy_model = self._buddies[buddy.props.key] if cur_activity and self._activities.has_key(cur_activity.get_id()):
if cur_activity == None:
self.emit('buddy-moved', buddy_model, None)
else:
self._notify_buddy_change(buddy_model, cur_activity)
def _notify_buddy_change(self, buddy_model, cur_activity):
if self._activities.has_key(cur_activity.get_id()):
activity_model = self._activities[cur_activity.get_id()] activity_model = self._activities[cur_activity.get_id()]
self.emit('buddy-moved', buddy_model, activity_model) self.emit('buddy-moved', model, activity_model)
else:
self.emit('buddy-moved', model, None)
def _buddy_appeared_cb(self, pservice, buddy): def _buddy_appeared_cb(self, pservice, buddy):
if self._buddies.has_key(buddy.props.key): if self._buddies.has_key(buddy.props.key):
@ -186,7 +181,7 @@ class MeshModel(gobject.GObject):
cur_activity = buddy.props.current_activity cur_activity = buddy.props.current_activity
if cur_activity: if cur_activity:
self._notify_buddy_change(model, cur_activity) self._buddy_activity_changed_cb(model, cur_activity)
def _buddy_disappeared_cb(self, pservice, buddy): def _buddy_disappeared_cb(self, pservice, buddy):
if not self._buddies.has_key(buddy.props.key): if not self._buddies.has_key(buddy.props.key):

View File

@ -166,7 +166,7 @@ class HomeModel(gobject.GObject):
else: else:
# activity got lost, took longer to launch than we allow, # activity got lost, took longer to launch than we allow,
# or it was launched by something other than the shell # or it was launched by something other than the shell
act_type = act_service.get_service_name() act_type = service.get_service_name()
bundle = self._bundle_registry.get_bundle(act_type) bundle = self._bundle_registry.get_bundle(act_type)
if not bundle: if not bundle:
raise RuntimeError("No bundle for activity type '%s'." % act_type) raise RuntimeError("No bundle for activity type '%s'." % act_type)

View File

@ -88,7 +88,7 @@ class BuddyMenu(Menu):
activity = shell_model.get_home().get_current_activity() activity = shell_model.get_home().get_current_activity()
if activity != None: if activity != None:
activity_ps = pservice.get_activity(activity.get_id()) activity_ps = pservice.get_activity(activity.get_activity_id())
# FIXME check that the buddy is not in the activity already # FIXME check that the buddy is not in the activity already

View File

@ -17,6 +17,7 @@
import logging import logging
import os import os
import urlparse
import gobject import gobject
@ -74,8 +75,13 @@ class ClipboardIcon(CanvasIcon):
self.props.background_color = color.TOOLBAR_BACKGROUND.get_int() self.props.background_color = color.TOOLBAR_BACKGROUND.get_int()
def get_popup(self): def get_popup(self):
cb_service = clipboardservice.get_instance()
obj = cb_service.get_object(self._object_id)
formats = obj['FORMATS']
self._menu = ClipboardMenu(self._name, self._percent, self._preview, self._menu = ClipboardMenu(self._name, self._percent, self._preview,
self._activity) self._activity,
formats[0] == 'application/vnd.olpc-x-sugar')
self._menu.connect('action', self._popup_action_cb) self._menu.connect('action', self._popup_action_cb)
return self._menu return self._menu
@ -83,15 +89,19 @@ class ClipboardIcon(CanvasIcon):
return self._popup_context return self._popup_context
def set_state(self, name, percent, icon_name, preview, activity): def set_state(self, name, percent, icon_name, preview, activity):
cb_service = clipboardservice.get_instance()
obj = cb_service.get_object(self._object_id)
installable = (obj['FORMATS'][0] == 'application/vnd.olpc-x-sugar')
self._name = name self._name = name
self._percent = percent self._percent = percent
self._preview = preview self._preview = preview
self._activity = activity self._activity = activity
self.set_property("icon_name", icon_name) self.set_property("icon_name", icon_name)
if self._menu: if self._menu:
self._menu.set_state(name, percent, preview, activity) self._menu.set_state(name, percent, preview, activity, installable)
if activity and percent < 100: if (activity or installable) and percent < 100:
self.props.xo_color = XoColor("#000000,#424242") self.props.xo_color = XoColor("#000000,#424242")
else: else:
self.props.xo_color = XoColor("#000000,#FFFFFF") self.props.xo_color = XoColor("#000000,#FFFFFF")
@ -107,27 +117,39 @@ class ClipboardIcon(CanvasIcon):
self._open_file() self._open_file()
def _open_file(self): def _open_file(self):
if self._percent < 100 or not self._activity: if self._percent < 100:
return return
# Get the file path # Get the file path
cb_service = clipboardservice.get_instance() cb_service = clipboardservice.get_instance()
obj = cb_service.get_object(self._object_id) obj = cb_service.get_object(self._object_id)
formats = obj['FORMATS'] formats = obj['FORMATS']
if len(formats) > 0: if len(formats) == 0:
path = cb_service.get_object_data(self._object_id, formats[0]) return
# FIXME: would be better to check for format.onDisk if not self._activity and \
try: not formats[0] == 'application/vnd.olpc-x-sugar':
path_exists = os.path.exists(path) return
except TypeError:
path_exists = False
if path_exists: uri = cb_service.get_object_data(self._object_id, formats[0])
uri = 'file://' + path if not uri.startswith('file://'):
return
path = urlparse.urlparse(uri).path
# FIXME: would be better to check for format.onDisk
try:
path_exists = os.path.exists(path)
except TypeError:
path_exists = False
if path_exists:
if self._activity:
activityfactory.create_with_uri(self._activity, uri) activityfactory.create_with_uri(self._activity, uri)
else: else:
logging.debug("Clipboard item file path %s didn't exist" % path) self._install_xo(path)
else:
logging.debug("Clipboard item file path %s didn't exist" % path)
def _popup_action_cb(self, popup, menu_item): def _popup_action_cb(self, popup, menu_item):
action = menu_item.props.action_id action = menu_item.props.action_id
@ -153,3 +175,9 @@ class ClipboardIcon(CanvasIcon):
self.props.background_color = color.DESKTOP_BACKGROUND.get_int() self.props.background_color = color.DESKTOP_BACKGROUND.get_int()
else: else:
self.props.background_color = color.TOOLBAR_BACKGROUND.get_int() self.props.background_color = color.TOOLBAR_BACKGROUND.get_int()
def _install_xo(self, path):
logging.debug('mec')
if os.spawnlp(os.P_WAIT, 'sugar-install-bundle', 'sugar-install-bundle',
path):
raise RuntimeError, 'An error occurred while extracting the .xo contents.'

View File

@ -33,7 +33,7 @@ class ClipboardMenu(Menu):
ACTION_OPEN = 1 ACTION_OPEN = 1
ACTION_STOP_DOWNLOAD = 2 ACTION_STOP_DOWNLOAD = 2
def __init__(self, name, percent, preview, activity): def __init__(self, name, percent, preview, activity, installable):
Menu.__init__(self, name) Menu.__init__(self, name)
self.props.border = 0 self.props.border = 0
@ -54,10 +54,10 @@ class ClipboardMenu(Menu):
self._preview_text.props.font_desc = font.DEFAULT.get_pango_desc() self._preview_text.props.font_desc = font.DEFAULT.get_pango_desc()
self.append(self._preview_text) self.append(self._preview_text)
self._update_icons(percent, activity) self._update_icons(percent, activity, installable)
def _update_icons(self, percent, activity): def _update_icons(self, percent, activity, installable):
if percent == 100 and activity: if percent == 100 and (activity or installable):
if not self._remove_item: if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE, self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'), _('Remove'),
@ -73,7 +73,7 @@ class ClipboardMenu(Menu):
if self._stop_item: if self._stop_item:
self.remove_item(self._stop_item) self.remove_item(self._stop_item)
self._stop_item = None self._stop_item = None
elif percent == 100 and not activity: elif percent == 100 and (not activity and not installable):
if not self._remove_item: if not self._remove_item:
self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE, self._remove_item = MenuItem(ClipboardMenu.ACTION_DELETE,
_('Remove'), _('Remove'),
@ -102,8 +102,8 @@ class ClipboardMenu(Menu):
self.remove_item(self._open_item) self.remove_item(self._open_item)
self._open_item = None self._open_item = None
def set_state(self, name, percent, preview, activity): def set_state(self, name, percent, preview, activity, installable):
self.set_title(name) self.set_title(name)
if self._progress_bar: if self._progress_bar:
self._progress_bar.set_property('percent', percent) self._progress_bar.set_property('percent', percent)
self._update_icons(percent, activity) self._update_icons(percent, activity, installable)

View File

@ -1,4 +1,8 @@
import shutil
import os
import logging import logging
import urlparse
import hippo import hippo
import gtk import gtk
@ -72,13 +76,29 @@ class ClipboardBox(hippo.CanvasBox):
if not selection.data: if not selection.data:
return return
logging.debug('ClipboardBox: adding type ' + selection.type + '.') logging.debug('ClipboardBox: adding type ' + selection.type + ' ' + selection.data)
cb_service = clipboardservice.get_instance() cb_service = clipboardservice.get_instance()
cb_service.add_object_format(object_id, if selection.type == 'text/uri-list':
selection.type, uris = selection.data.split('\n')
selection.data, if len(uris) > 1:
on_disk = False) raise NotImplementedError('Multiple uris in text/uri-list still not supported.')
uri = urlparse.urlparse(uris[0])
path, file_name = os.path.split(uri.path)
# Copy the file, as it will be deleted when the dnd operation finishes.
new_file_path = os.path.join(path, 'cb' + file_name)
shutil.copyfile(uri.path, new_file_path)
cb_service.add_object_format(object_id,
selection.type,
uri.scheme + "://" + new_file_path,
on_disk=True)
else:
cb_service.add_object_format(object_id,
selection.type,
selection.data,
on_disk=False)
def _object_added_cb(self, cb_service, object_id, name): def _object_added_cb(self, cb_service, object_id, name):
icon = ClipboardIcon(self._popup_context, object_id, name) icon = ClipboardIcon(self._popup_context, object_id, name)
@ -165,15 +185,16 @@ class ClipboardBox(hippo.CanvasBox):
def drag_data_received_cb(self, widget, context, x, y, selection, targetType, time): def drag_data_received_cb(self, widget, context, x, y, selection, targetType, time):
logging.debug('ClipboardBox: got data for target ' + selection.target) logging.debug('ClipboardBox: got data for target ' + selection.target)
if selection: try:
object_id = self._context_map.get_object_id(context) if selection:
self._add_selection(object_id, selection) object_id = self._context_map.get_object_id(context)
else: self._add_selection(object_id, selection)
logging.warn('ClipboardBox: empty selection for target ' + selection.target) else:
logging.warn('ClipboardBox: empty selection for target ' + selection.target)
# If it's the last target to be processed, finish the dnd transaction finally:
if not self._context_map.has_context(context): # If it's the last target to be processed, finish the dnd transaction
context.finish(True, False, time) if not self._context_map.has_context(context):
context.drop_finish(True, gtk.get_current_event_time())
def drag_data_get_cb(self, widget, context, selection, targetType, eventTime): def drag_data_get_cb(self, widget, context, selection, targetType, eventTime):
logging.debug("drag_data_get_cb: requested target " + selection.target) logging.debug("drag_data_get_cb: requested target " + selection.target)

View File

@ -169,7 +169,7 @@ class Frame(object):
if self._animator: if self._animator:
self._animator.stop() self._animator.stop()
self._animator = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO) self._animator = animator.Animator(0.5)
self._animator.add(_Animation(self, 0.0)) self._animator.add(_Animation(self, 0.0))
self._animator.start() self._animator.start()
@ -188,7 +188,7 @@ class Frame(object):
if self._animator: if self._animator:
self._animator.stop() self._animator.stop()
self._animator = animator.Animator(0.5, 30, animator.EASE_OUT_EXPO) self._animator = animator.Animator(0.5)
self._animator.add(_Animation(self, 1.0)) self._animator.add(_Animation(self, 1.0))
self._animator.start() self._animator.start()

View File

@ -24,7 +24,7 @@ from sugar.graphics import units
from view.home.MyIcon import MyIcon from view.home.MyIcon import MyIcon
from view.home.FriendView import FriendView from view.home.FriendView import FriendView
class FriendsBox(SpreadBox, hippo.CanvasItem): class FriendsBox(SpreadBox):
__gtype_name__ = 'SugarFriendsBox' __gtype_name__ = 'SugarFriendsBox'
def __init__(self, shell, menu_shell): def __init__(self, shell, menu_shell):
SpreadBox.__init__(self, background_color=0xe2e2e2ff) SpreadBox.__init__(self, background_color=0xe2e2e2ff)
@ -34,7 +34,7 @@ class FriendsBox(SpreadBox, hippo.CanvasItem):
self._friends = {} self._friends = {}
self._my_icon = MyIcon(units.LARGE_ICON_SCALE) self._my_icon = MyIcon(units.LARGE_ICON_SCALE)
self.append(self._my_icon, hippo.PACK_FIXED) self.set_center_item(self._my_icon)
friends = self._shell.get_model().get_friends() friends = self._shell.get_model().get_friends()
@ -56,10 +56,3 @@ class FriendsBox(SpreadBox, hippo.CanvasItem):
def _friend_removed_cb(self, data_model, key): def _friend_removed_cb(self, data_model, key):
self.remove_item(self._friends[key]) self.remove_item(self._friends[key])
del self._friends[key] del self._friends[key]
def do_allocate(self, width, height, origin_changed):
SpreadBox.do_allocate(self, width, height, origin_changed)
[icon_width, icon_height] = self._my_icon.get_allocation()
self.set_position(self._my_icon, (width - icon_width) / 2,
(height - icon_height) / 2)

View File

@ -19,20 +19,26 @@ import hippo
import cairo import cairo
from sugar.graphics.menushell import MenuShell from sugar.graphics.menushell import MenuShell
from sugar.graphics.window import Window
from sugar.graphics import units
import sugar import sugar
from view.home.MeshBox import MeshBox from view.home.MeshBox import MeshBox
from view.home.HomeBox import HomeBox from view.home.HomeBox import HomeBox
from view.home.FriendsBox import FriendsBox from view.home.FriendsBox import FriendsBox
from view.home.transitionbox import TransitionBox
_HOME_PAGE = 0 _HOME_PAGE = 0
_FRIENDS_PAGE = 1 _FRIENDS_PAGE = 1
_MESH_PAGE = 2 _MESH_PAGE = 2
_TRANSITION_PAGE = 3
class HomeWindow(gtk.Window): class HomeWindow(Window):
def __init__(self, shell): def __init__(self, shell):
gtk.Window.__init__(self) Window.__init__(self)
self._shell = shell self._shell = shell
self._active = False self._active = False
self._level = sugar.ZOOM_HOME
self.set_default_size(gtk.gdk.screen_width(), self.set_default_size(gtk.gdk.screen_width(),
gtk.gdk.screen_height()) gtk.gdk.screen_height())
@ -43,30 +49,15 @@ class HomeWindow(gtk.Window):
self.connect('focus-in-event', self._focus_in_cb) self.connect('focus-in-event', self._focus_in_cb)
self.connect('focus-out-event', self._focus_out_cb) self.connect('focus-out-event', self._focus_out_cb)
self._nb = gtk.Notebook()
self._nb.set_show_border(False)
self._nb.set_show_tabs(False)
self.add(self._nb)
self._nb.show()
canvas = hippo.Canvas()
self._home_box = HomeBox(shell) self._home_box = HomeBox(shell)
canvas.set_root(self._home_box) self._friends_box = FriendsBox(shell, MenuShell(self))
self._nb.append_page(canvas) self._mesh_box = MeshBox(shell, MenuShell(self))
canvas.show() self._transition_box = TransitionBox()
canvas = hippo.Canvas() self.set_root(self._home_box)
box = FriendsBox(shell, MenuShell(canvas))
canvas.set_root(box) self._transition_box.connect('completed',
self._nb.append_page(canvas) self._transition_completed_cb)
canvas.show()
canvas = hippo.Canvas()
self._mesh_box = MeshBox(shell, MenuShell(canvas))
canvas.set_root(self._mesh_box)
self._nb.append_page(canvas)
canvas.show()
def _key_release_cb(self, widget, event): def _key_release_cb(self, widget, event):
keyname = gtk.gdk.keyval_name(event.keyval) keyname = gtk.gdk.keyval_name(event.keyval)
@ -74,7 +65,7 @@ class HomeWindow(gtk.Window):
self._home_box.release() self._home_box.release()
def _update_mesh_state(self): def _update_mesh_state(self):
if self._active and self._nb.get_current_page() == _MESH_PAGE: if self._active and self._level == sugar.ZOOM_MESH:
self._mesh_box.resume() self._mesh_box.resume()
else: else:
self._mesh_box.suspend() self._mesh_box.suspend()
@ -88,12 +79,26 @@ class HomeWindow(gtk.Window):
self._update_mesh_state() self._update_mesh_state()
def set_zoom_level(self, level): def set_zoom_level(self, level):
self._level = level
self.set_root(self._transition_box)
if level == sugar.ZOOM_HOME: if level == sugar.ZOOM_HOME:
self._nb.set_current_page(_HOME_PAGE) scale = units.XLARGE_ICON_SCALE
elif level == sugar.ZOOM_FRIENDS: elif level == sugar.ZOOM_FRIENDS:
self._nb.set_current_page(_FRIENDS_PAGE) scale = units.LARGE_ICON_SCALE
elif level == sugar.ZOOM_MESH: elif level == sugar.ZOOM_MESH:
self._nb.set_current_page(_MESH_PAGE) scale = units.STANDARD_ICON_SCALE
self._transition_box.set_scale(scale)
def _transition_completed_cb(self, transition_box):
if self._level == sugar.ZOOM_HOME:
self.set_root(self._home_box)
elif self._level == sugar.ZOOM_FRIENDS:
self.set_root(self._friends_box)
elif self._level == sugar.ZOOM_MESH:
self.set_root(self._mesh_box)
self._update_mesh_state() self._update_mesh_state()

View File

@ -7,4 +7,5 @@ sugar_PYTHON = \
HomeBox.py \ HomeBox.py \
HomeWindow.py \ HomeWindow.py \
MeshBox.py \ MeshBox.py \
MyIcon.py MyIcon.py \
transitionbox.py

View File

@ -278,7 +278,10 @@ class MeshBox(SpreadBox):
def _add_alone_buddy(self, buddy_model): def _add_alone_buddy(self, buddy_model):
icon = BuddyIcon(self._shell, self._menu_shell, buddy_model) icon = BuddyIcon(self._shell, self._menu_shell, buddy_model)
self.add_item(icon) if buddy_model.is_owner():
self.set_center_item(icon)
else:
self.add_item(icon)
self._buddies[buddy_model.get_key()] = icon self._buddies[buddy_model.get_key()] = icon

View File

@ -0,0 +1,66 @@
# Copyright (C) 2007, Red Hat, 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import hippo
import gobject
from sugar.graphics import units
from sugar.graphics import animator
from sugar.graphics.spreadbox import SpreadBox
from view.home.MyIcon import MyIcon
class _Animation(animator.Animation):
def __init__(self, icon, start_scale, end_scale):
animator.Animation.__init__(self, 0.0, 1.0)
self._icon = icon
self.start_scale = start_scale
self.end_scale = end_scale
def next_frame(self, current):
d = (self.end_scale - self.start_scale) * current
self._icon.props.scale = self.start_scale + d
class TransitionBox(SpreadBox):
__gtype_name__ = 'SugarTransitionBox'
__gsignals__ = {
'completed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([]))
}
def __init__(self):
SpreadBox.__init__(self, background_color=0xe2e2e2ff)
self._scale = units.XLARGE_ICON_SCALE
self._my_icon = MyIcon(self._scale)
self.set_center_item(self._my_icon)
self._animator = animator.Animator(0.3)
self._animator.connect('completed', self._animation_completed_cb)
def _animation_completed_cb(self, anim):
self.emit('completed')
def set_scale(self, scale):
self._animator.remove_all()
self._animator.add(_Animation(self._my_icon, self._scale, scale))
self._animator.start()
self._scale = scale

View File

@ -25,8 +25,6 @@ pygtk.require('2.0')
import gtk import gtk
import gobject import gobject
from sugar import env
def _get_display_number(): def _get_display_number():
"""Find a free display number trying to connect to 6000+ ports""" """Find a free display number trying to connect to 6000+ ports"""
retries = 20 retries = 20
@ -50,22 +48,23 @@ def _get_display_number():
logging.error('Cannot find a free display.') logging.error('Cannot find a free display.')
sys.exit(0) sys.exit(0)
def _start_xephyr(width, height, dpi): def _start_xephyr():
display = _get_display_number() display = _get_display_number()
cmd = [ 'Xephyr' ] cmd = [ 'Xephyr' ]
cmd.append(':%d' % display) cmd.append(':%d' % display)
cmd.append('-ac') cmd.append('-ac')
if width > 0 and height > 0: if gtk.gdk.screen_width() < 1200 or gtk.gdk.screen_height() < 900:
cmd.append('-screen')
cmd.append('%dx%d' % (width, height))
else:
cmd.append('-fullscreen') cmd.append('-fullscreen')
else:
cmd.append('-screen')
cmd.append('%dx%d' % (1200, 900))
dpi = gtk.settings_get_default().get_property('gtk-xft-dpi')
if dpi > 0: if dpi > 0:
cmd.append('-dpi') cmd.append('-dpi')
cmd.append(str(dpi)) cmd.append('%d' % int(dpi/1024))
result = gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) result = gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
pid = result[0] pid = result[0]
@ -73,23 +72,40 @@ def _start_xephyr(width, height, dpi):
os.environ['DISPLAY'] = ":%d" % (display) os.environ['DISPLAY'] = ":%d" % (display)
os.environ['SUGAR_EMULATOR_PID'] = str(pid) os.environ['SUGAR_EMULATOR_PID'] = str(pid)
os.environ['SUGAR_EMULATOR'] = 'yes' def _start_matchbox():
cmd = ['matchbox-window-manager']
cmd.extend(['-use_titlebar', 'no'])
cmd.extend(['-theme', 'olpc'])
gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
def _setup_env():
os.environ['SUGAR_EMULATOR'] = 'yes'
source_dir = os.path.dirname(os.path.abspath(__file__))
if os.path.isfile(os.path.join(source_dir, 'autogen.sh')):
os.environ['SUGAR_PATH'] = source_dir
if os.environ.has_key('PYTHONPATH'):
path = os.environ['PYTHONPATH']
os.environ['PYTHONPATH'] = source_dir + ':' + path
_setup_env()
_start_xephyr()
from sugar import env
if env.is_emulator():
gtkrc_filename = 'sugar.gtkrc'
else:
gtkrc_filename = 'sugar-xo.gtkrc'
os.environ['GTK2_RC_FILES'] = env.get_data_path(gtkrc_filename)
if len(sys.argv) == 1: if len(sys.argv) == 1:
program = 'sugar-shell' program = 'sugar-shell'
else: else:
_start_matchbox()
program = sys.argv[1] program = sys.argv[1]
if gtk.gdk.screen_width() < 1200 or gtk.gdk.screen_height() < 900:
width = -1
height = -1
else:
width = 1200
height = 900
_gtk_xft_dpi = float(gtk.settings_get_default().get_property('gtk-xft-dpi'))
_start_xephyr(width, height, _gtk_xft_dpi / 1024)
os.environ['GTK2_RC_FILES'] = env.get_data_path('gtkrc')
os.execlp('dbus-launch', 'dbus-launch', '--exit-with-session', program) os.execlp('dbus-launch', 'dbus-launch', '--exit-with-session', program)

View File

@ -1,10 +1,11 @@
SUBDIRS = activity browser clipboard graphics p2p presence datastore SUBDIRS = activity browser clipboard graphics p2p presence datastore
sugardir = $(pythondir)/sugar sugardir = $(pythondir)/sugar
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \ __init__.py \
date.py \ date.py \
env.py \ env.py \
logger.py \ logger.py \
profile.py \ ltihooks.py \
profile.py \
util.py util.py

View File

@ -140,7 +140,7 @@ def run_with_args(args):
run(options.bundle_path) run(options.bundle_path)
def run(bundle_path): def run(bundle_path):
sys.path.insert(0, bundle_path) sys.path.append(bundle_path)
bundle = Bundle(bundle_path) bundle = Bundle(bundle_path)

View File

@ -3,4 +3,9 @@
XUL Runner and gtkmozembed and is produced by the PyGTK XUL Runner and gtkmozembed and is produced by the PyGTK
.defs system. .defs system.
""" """
from sugar.browser._sugarbrowser import *
try:
from sugar.browser._sugarbrowser import *
except ImportError:
from sugar import ltihooks
from sugar.browser._sugarbrowser import *

View File

@ -29,6 +29,17 @@ def _get_prefix_path(base, path=None):
else: else:
return os.path.join(prefix, base) return os.path.join(prefix, base)
def _get_sugar_path(base, path=None):
if os.environ.has_key('SUGAR_PATH'):
sugar_path = os.environ['SUGAR_PATH']
else:
sugar_path = _get_prefix_path('share/sugar')
if path:
return os.path.join(sugar_path, base, path)
else:
return os.path.join(sugar_path, base)
def is_emulator(): def is_emulator():
if os.environ.has_key('SUGAR_EMULATOR'): if os.environ.has_key('SUGAR_EMULATOR'):
if os.environ['SUGAR_EMULATOR'] == 'yes': if os.environ['SUGAR_EMULATOR'] == 'yes':
@ -56,17 +67,17 @@ def get_profile_path(path=None):
def get_user_activities_path(): def get_user_activities_path():
return os.path.expanduser('~/Activities') return os.path.expanduser('~/Activities')
def get_bin_path(path=None):
return _get_prefix_path('bin', path)
def get_locale_path(path=None): def get_locale_path(path=None):
return _get_prefix_path('share/locale', path) return _get_prefix_path('share/locale', path)
def get_bin_path(path=None):
return _get_sugar_path('bin', path)
def get_service_path(name): def get_service_path(name):
return _get_prefix_path('share/sugar/services', name) return _get_sugar_path('services', name)
def get_shell_path(path=None): def get_shell_path(path=None):
return _get_prefix_path('share/sugar/shell', path) return _get_sugar_path('shell', path)
def get_data_path(path=None): def get_data_path(path=None):
return _get_prefix_path('share/sugar', path) return _get_sugar_path('data', path)

View File

@ -28,7 +28,7 @@ class Animator(gobject.GObject):
gobject.TYPE_NONE, ([])), gobject.TYPE_NONE, ([])),
} }
def __init__(self, time, fps, easing=EASE_OUT_EXPO): def __init__(self, time, fps=20, easing=EASE_OUT_EXPO):
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._animations = [] self._animations = []
self._time = time self._time = time
@ -39,6 +39,10 @@ class Animator(gobject.GObject):
def add(self, animation): def add(self, animation):
self._animations.append(animation) self._animations.append(animation)
def remove_all(self):
self.stop()
self._animations = []
def start(self): def start(self):
if self._timeout_sid: if self._timeout_sid:
self.stop() self.stop()
@ -51,7 +55,7 @@ class Animator(gobject.GObject):
if self._timeout_sid: if self._timeout_sid:
gobject.source_remove(self._timeout_sid) gobject.source_remove(self._timeout_sid)
self._timeout_sid = 0 self._timeout_sid = 0
self.emit('completed') self.emit('completed')
def _next_frame_cb(self): def _next_frame_cb(self):
current_time = min(self._time, time.time() - self._start_time) current_time = min(self._time, time.time() - self._start_time)

View File

@ -369,6 +369,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
if not self._popup: if not self._popup:
return return
if not self.get_context():
# If we have been detached from our parent, don't show up the popup
# in this case.
return
popup_context = self.get_popup_context() popup_context = self.get_popup_context()
[x, y] = [None, None] [x, y] = [None, None]

View File

@ -20,9 +20,11 @@ import math
import cairo import cairo
import hippo import hippo
_BASE_RADIUS = 65 from sugar.graphics import units
_BASE_RADIUS = units.points_to_pixels(20)
_CHILDREN_FACTOR = 1 _CHILDREN_FACTOR = 1
_FLAKE_DISTANCE = 6 _FLAKE_DISTANCE = units.points_to_pixels(4)
class SnowflakeBox(hippo.CanvasBox, hippo.CanvasItem): class SnowflakeBox(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarSnowflakeBox' __gtype_name__ = 'SugarSnowflakeBox'
@ -67,6 +69,13 @@ class SnowflakeBox(hippo.CanvasBox, hippo.CanvasItem):
self.set_position(child, int(x), int(y)) self.set_position(child, int(x), int(y))
def do_get_height_request(self, for_width):
hippo.CanvasBox.do_get_height_request(self, for_width)
height = for_width
return (height, height)
def do_get_width_request(self): def do_get_width_request(self):
hippo.CanvasBox.do_get_width_request(self) hippo.CanvasBox.do_get_width_request(self)

View File

@ -22,20 +22,31 @@ import gtk
from sugar.graphics import units from sugar.graphics import units
_WIDTH = gtk.gdk.screen_width()
_HEIGHT = gtk.gdk.screen_height()
_CELL_WIDTH = units.grid_to_pixels(1) _CELL_WIDTH = units.grid_to_pixels(1)
_CELL_HEIGHT = units.grid_to_pixels(1) _CELL_HEIGHT = units.grid_to_pixels(1)
_GRID_WIDTH = gtk.gdk.screen_width() / _CELL_WIDTH _GRID_WIDTH = _WIDTH / _CELL_WIDTH
_GRID_HEIGHT = gtk.gdk.screen_height() / _CELL_HEIGHT _GRID_HEIGHT = _HEIGHT / _CELL_HEIGHT
class SpreadBox(hippo.CanvasBox): class SpreadBox(hippo.CanvasBox, hippo.CanvasItem):
__gtype_name__ = 'SugarSpreadBox'
def __init__(self, **kwargs): def __init__(self, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs) hippo.CanvasBox.__init__(self, **kwargs)
self._grid = [] self._grid = []
self._center = None
for i in range(0, _GRID_WIDTH * _GRID_HEIGHT): for i in range(0, _GRID_WIDTH * _GRID_HEIGHT):
self._grid.append(None) self._grid.append(None)
def set_center_item(self, item):
if self._center:
self.remove(self._center)
self._center = item
self.append(item, hippo.PACK_FIXED)
def add_item(self, item): def add_item(self, item):
start_pos = int(random.random() * len(self._grid)) start_pos = int(random.random() * len(self._grid))
@ -60,3 +71,11 @@ class SpreadBox(hippo.CanvasBox):
if self._grid[i] == item: if self._grid[i] == item:
self._grid[i] = None self._grid[i] = None
self.remove(item) self.remove(item)
def do_allocate(self, width, height, origin_changed):
hippo.CanvasBox.do_allocate(self, width, height, origin_changed)
if self._center:
[icon_width, icon_height] = self._center.get_allocation()
self.set_position(self._center, (width - icon_width) / 2,
(height - icon_height) / 2)

View File

@ -0,0 +1,5 @@
sugardir = $(pythondir)/sugar/graphics2
sugar_PYTHON = \
__init__.py \
window.py \
toolbox.py

View File

View File

@ -0,0 +1,32 @@
# 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.
import gtk
class Toolbox(gtk.VBox):
__gtype_name__ = 'SugarToolbox'
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.pack_start(self._notebook)
self._notebook.show()
def add_toolbar(self, name, toolbar):
self._notebook.append_page(toolbar, gtk.Label(name))

49
sugar/graphics2/window.py Normal file
View File

@ -0,0 +1,49 @@
# 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.
import gtk
import hippo
from sugar.graphics2.toolbox import Toolbox
class Window(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
vbox = gtk.VBox()
self.add(vbox)
self.toolbox = Toolbox()
vbox.pack_start(self.toolbox, False)
self.toolbox.show()
self._canvas_box = gtk.VBox()
vbox.pack_start(self._canvas_box)
self._canvas_box.show()
self.canvas = hippo.Canvas()
self._canvas_box.pack_start(self.canvas)
self.canvas.show()
vbox.show()
def set_canvas(self, canvas):
if self.canvas:
self._canvas_box.remove(self.canvas)
self._canvas_box.add(canvas)
self.canvas = canvas

72
sugar/ltihooks.py Normal file
View File

@ -0,0 +1,72 @@
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
# ltihooks.py: python import hooks that understand libtool libraries.
# Copyright (C) 2000 James Henstridge.
# renamed to gstltihooks.py so it does not accidentally get imported by
# an installed copy of gtk
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os, ihooks
class LibtoolHooks(ihooks.Hooks):
def get_suffixes(self):
"""Like normal get_suffixes, but adds .la suffixes to list"""
ret = ihooks.Hooks.get_suffixes(self)
ret.insert(0, ('module.la', 'rb', 3))
ret.insert(0, ('.la', 'rb', 3))
return ret
def load_dynamic(self, name, filename, file=None):
"""Like normal load_dynamic, but treat .la files specially"""
if len(filename) > 3 and filename[-3:] == '.la':
fp = open(filename, 'r')
dlname = ''
installed = 1
line = fp.readline()
while line:
# dlname: the name that we can dlopen
if len(line) > 7 and line[:7] == 'dlname=':
dlname = line[8:-2]
# installed: whether it's already installed
elif len(line) > 10 and line[:10] == 'installed=':
installed = line[10:-1] == 'yes'
line = fp.readline()
fp.close()
if dlname:
if installed:
filename = os.path.join(os.path.dirname(filename),
dlname)
else:
# if .libs already there, don't need to add it again
if os.path.dirname(filename).endswith('.libs'):
filename = os.path.join(os.path.dirname(filename),
dlname)
else:
filename = os.path.join(os.path.dirname(filename),
'.libs', dlname)
return ihooks.Hooks.load_dynamic(self, name, filename, file)
importer = ihooks.ModuleImporter()
importer.set_hooks(LibtoolHooks())
def install():
print 'Installed ltihooks.'
importer.install()
def uninstall():
importer.uninstall()
install()

View File

@ -337,6 +337,19 @@ class PresenceService(gobject.GObject):
reply_handler=lambda *args: self._share_activity_cb(activity, *args), reply_handler=lambda *args: self._share_activity_cb(activity, *args),
error_handler=lambda *args: self._share_activity_error_cb(activity, *args)) error_handler=lambda *args: self._share_activity_error_cb(activity, *args))
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"""
try:
bus_name, object_path = self._ps.GetPreferredConnection()
except dbus.exceptions.DBusException:
return None
return bus_name, object_path
class _MockPresenceService(gobject.GObject): class _MockPresenceService(gobject.GObject):
"""Test fixture allowing testing of items that use PresenceService """Test fixture allowing testing of items that use PresenceService

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
sys.path.insert(0, '/home/tomeu/sugar-jhbuild/source/sugar')
import gtk
import hippo
from sugar.graphics.toolbar import Toolbar
from sugar.graphics.iconbutton import IconButton
from sugar.graphics.toggleiconbutton import ToggleIconButton
from sugar.graphics.button import Button
from sugar.graphics.entry import Entry
def _button_activated_cb(button):
print "_button_activated_cb"
def _toggled_changed_cb(button, pspec):
print "Toggle state: %d" % button.props.toggled
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
window.add(canvas)
canvas.show()
vbox = hippo.CanvasBox()
canvas.set_root(vbox)
for i in [1, 2]:
toolbar = Toolbar()
toolbar.props.box_width = 400
vbox.append(toolbar)
icon_button = IconButton(icon_name='theme:stock-close')
toolbar.append(icon_button)
toggle = ToggleIconButton(icon_name='theme:stock-back')
toggle.connect('notify::toggled', _toggled_changed_cb)
toolbar.append(toggle)
button = Button(text='Click me!', icon_name='theme:stock-close')
button.connect('activated', _button_activated_cb)
toolbar.append(button)
entry = Entry(text='mec')
toolbar.append(entry)
gtk.main()

View File

@ -1,71 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
import hippo
from sugar.graphics.toolbar import Toolbar
from sugar.graphics.frame import Frame
from sugar.graphics.iconbutton import IconButton
from sugar.graphics.entry import Entry
def _entry_activated_cb(entry):
print "_entry_activated_cb"
def _entry_button_activated_cb(entry, action_id):
print "_entry_button_activated_cb: " + str(action_id)
entry.props.text = ''
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
window.add(canvas)
canvas.show()
vbox = hippo.CanvasBox()
canvas.set_root(vbox)
for i in [1, 2]:
toolbar = Toolbar()
vbox.append(toolbar)
button = IconButton('theme:stock-close')
toolbar.append(button)
BUTTON_DELETE = 1
entry = Entry()
entry.props.text = 'mec mac'
entry.add_button('theme:stock-close', BUTTON_DELETE)
entry.connect('activated', _entry_activated_cb)
entry.connect('button-activated', _entry_button_activated_cb)
toolbar.append(entry, hippo.PACK_EXPAND)
entry = Entry()
entry.props.text = 'moc muc'
toolbar.append(entry, hippo.PACK_EXPAND)
gtk_entry = gtk.Entry()
gtk_entry.props.has_frame = False
#gtk_entry.connect("activate", self._entry_activate_cb)
gtk_entry_widget = hippo.CanvasWidget()
gtk_entry_widget.props.widget = gtk_entry
toolbar.append(gtk_entry_widget, hippo.PACK_EXPAND)
gtk.main()

View File

@ -1,36 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
import hippo
from sugar.graphics.frame import Frame
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
frame = Frame()
canvas.set_root(frame)
window.add(canvas)
canvas.show()
gtk.main()

View File

@ -1,51 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
import hippo
from sugar.graphics.toolbar import Toolbar
from sugar.graphics.label import Label
from sugar.graphics.iconbutton import IconButton
BUTTON_DELETE = 1
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
window.add(canvas)
canvas.show()
vbox = hippo.CanvasBox()
canvas.set_root(vbox)
toolbar = Toolbar()
vbox.append(toolbar)
button = IconButton('theme:stock-close')
toolbar.append(button)
label = Label('mec moc')
toolbar.append(label)
label = Label()
label.props.text = 'mac mic'
toolbar.append(label)
gtk.main()

View File

@ -1,64 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
sys.path.insert(0, '/home/tomeu/sugar-jhbuild/source/sugar')
from gettext import gettext as _
import gtk
import hippo
from sugar.graphics.toolbar import Toolbar
from sugar.graphics.optionmenu import OptionMenu
from sugar.graphics.menu import MenuItem
from sugar.graphics.iconbutton import IconButton
def _option_menu_changed_cb(option_menu):
print '_option_menu_activated_cb: %i' % option_menu.props.value
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
window.show()
canvas = hippo.Canvas()
window.add(canvas)
canvas.show()
vbox = hippo.CanvasBox()
canvas.set_root(vbox)
toolbar = Toolbar()
vbox.append(toolbar)
button = IconButton(icon_name='theme:stock-close')
toolbar.append(button)
OPTION_ANYTHING = 1
OPTION_DRAW = 2
OPTION_WRITE = 3
OPTION_CHAT = 4
option_menu = OptionMenu()
option_menu.add_item(MenuItem(OPTION_ANYTHING, 'Anything'))
option_menu.add_separator()
option_menu.add_item(MenuItem(OPTION_DRAW, 'Draw', 'theme:stock-close'))
option_menu.add_item(MenuItem(OPTION_WRITE, 'Write'))
option_menu.add_item(MenuItem(OPTION_CHAT, 'Chat'))
option_menu.connect('changed', _option_menu_changed_cb)
toolbar.append(option_menu)
gtk.main()

View File

@ -33,14 +33,14 @@ from sugar.graphics.canvasicon import CanvasIcon
def _create_snowflake(parent, children): def _create_snowflake(parent, children):
color = XoColor() color = XoColor()
icon = CanvasIcon(scale=0.5, xo_color=color, icon = CanvasIcon(scale=1.0, xo_color=color,
icon_name='theme:object-link') icon_name='theme:object-link')
parent.append(icon, hippo.PACK_FIXED) parent.append(icon, hippo.PACK_FIXED)
parent.set_root(icon) parent.set_root(icon)
for i in range(0, children): for i in range(0, children):
color = XoColor() color = XoColor()
icon = CanvasIcon(scale=1.0, xo_color=color, icon = CanvasIcon(scale=0.5, xo_color=color,
icon_name='theme:stock-buddy') icon_name='theme:stock-buddy')
parent.append(icon, hippo.PACK_FIXED) parent.append(icon, hippo.PACK_FIXED)

View File

@ -1,121 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pygtk
pygtk.require('2.0')
import gtk
# Main window
window = gtk.Window()
window.connect("destroy", lambda w: gtk.main_quit())
#window.set_border_width(10)
# Main VBox
main_vbox = gtk.VBox(homogeneous=False, spacing=0)
window.add(main_vbox)
############################### ##############################
############################### Menus ##############################
############################### ##############################
menu = gtk.Menu()
file_menu = gtk.Menu() # Don't need to show menus
edit_menu = gtk.Menu()
# Create the menu items
dummy_item_1 = gtk.MenuItem("Dummy Item 1")
dummy_item_2 = gtk.MenuItem("Dummy Item 2")
quit_item = gtk.MenuItem("Quit")
dummy_item_3 = gtk.MenuItem("Dummy Item 3")
dummy_item_4 = gtk.MenuItem("Dummy Item 4")
dummy_item_5 = gtk.MenuItem("Dummy Item 5")
# Add them to the menu
file_menu.append(dummy_item_1)
file_menu.append(dummy_item_2)
file_menu.append(quit_item)
edit_menu.append(dummy_item_3)
edit_menu.append(dummy_item_4)
edit_menu.append(dummy_item_5)
# We can attach the Quit menu item to our exit function
quit_item.connect_object ("activate", lambda w: gtk.main_quit (), "file.quit")
# We do need to show menu items
dummy_item_1.show()
dummy_item_2.show()
quit_item.show()
dummy_item_3.show()
dummy_item_4.show()
dummy_item_5.show()
# Pack the menu into the menubar
menu_bar = gtk.MenuBar()
main_vbox.pack_start(menu_bar, False, False, 0)
menu_bar.show()
file_item = gtk.MenuItem("File")
file_item.show()
menu_bar.append(file_item)
file_item.set_submenu(file_menu)
edit_item = gtk.MenuItem("Edit")
edit_item.show()
menu_bar.append(edit_item)
edit_item.set_submenu(edit_menu)
# Scrolled window
scrolled_window = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
#scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
scrolled_window.set_border_width(10)
main_vbox.pack_start(scrolled_window, True, True, 0)
# Vbox inside the scrolled window
vbox = gtk.VBox(homogeneous=False, spacing=10)
scrolled_window.add_with_viewport(vbox)
vbox.set_border_width (10)
# Label
label = gtk.Label("This is a label")
vbox.pack_start(label, False, False, 0)
# Entry
entry = gtk.Entry ()
entry.set_text("Type some text here")
vbox.pack_start(entry, False, False, 0)
# Buttons
buttons_hbox = gtk.HBox(homogeneous=False, spacing=5)
vbox.pack_start(buttons_hbox, False, False, 0)
button_1 = gtk.Button ("Button 1")
buttons_hbox.pack_start(button_1, False, False, 0)
button_2 = gtk.Button ("Button 2")
buttons_hbox.pack_start(button_2, False, False, 0)
button_3 = gtk.Button ("Button 3")
buttons_hbox.pack_start(button_3, False, False, 0)
window.show_all()
gtk.main()

98
tests/test-ui.py Executable file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
from sugar.graphics2.window import Window
class ActivityToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class EditToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class TextToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
button = gtk.ToolButton()
button.set_icon_name('text-format-bold')
self.insert(button, -1)
button.show()
class ImageToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class TableToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class FormatToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
class ViewToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
window = Window()
window.connect("destroy", lambda w: gtk.main_quit())
activity_toolbar = ActivityToolbar()
window.toolbox.add_toolbar('Activity', activity_toolbar)
activity_toolbar.show()
edit_toolbar = EditToolbar()
window.toolbox.add_toolbar('Edit', edit_toolbar)
edit_toolbar.show()
text_toolbar = TextToolbar()
window.toolbox.add_toolbar('Text', text_toolbar)
text_toolbar.show()
image_toolbar = ImageToolbar()
window.toolbox.add_toolbar('Image', image_toolbar)
image_toolbar.show()
table_toolbar = TableToolbar()
window.toolbox.add_toolbar('Table', table_toolbar)
table_toolbar.show()
format_toolbar = FormatToolbar()
window.toolbox.add_toolbar('Format', format_toolbar)
format_toolbar.show()
view_toolbar = ViewToolbar()
window.toolbox.add_toolbar('View', view_toolbar)
view_toolbar.show()
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
window.set_canvas(scrolled_window)
scrolled_window.show()
text_view = gtk.TextView()
scrolled_window.add(text_view)
text_view.show()
window.show()
gtk.main()

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2006, Red Hat, 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pygtk
pygtk.require('2.0')
from sugar.session.UITestSession import UITestSession
session = UITestSession()
session.start()
import gtk
def _show_dialog(window):
dialog = gtk.Dialog(title='No Unviewed Media',
parent=window, flags=gtk.DIALOG_MODAL,
buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
label = gtk.Label('There is no unviewed media to download.')
dialog.vbox.pack_start(label, True, True, 0)
label.show()
response = dialog.run()
dialog.hide()
del dialog
window = gtk.Window()
window.show()
_show_dialog(window)
gtk.main()