Screenlets API
You can find online Api documentation here. If you have installed screenlets-doc package, you can find the latest documentation at file:///usr/share/doc/screenlets/API/index.html in your system.
Translate to your language
You can translate Screenlets Core application (Screenlets Manager etc) here and individual Screenlets here. Translation is provided by Launchpad Translations.
Make your Screenlet translatable
If you want to make an individual Screenlet translatable, first see this section. In Screenlet source code, you have to put every UI string between a gettext call. If you want a string "Main menu" to be translatable in your Screenlet, you decorate it with _("Main Menu"). Rest of it is managed by the gettext, translation file generation scripts and Launchpad.
Some general rules of decorating strings with gettext:
- Do not decorate the name of screenlet in screenlet metadata
- Do not decorate other keywords, which are used internally by the python program
- Do not decorate other non-UI strings
- There's really no point in decorating console error messages (it makes extra work for translators)
- Use __desc__ = __doc__ instead of translating description (i.e. don't do this: __desc__ = _("Whatever descriptive text"), because it just doubles the translation work, since docstring will be added to the translation template anyway)
Some hints on automation
If you are trying to make an existing but not yet localized Screenlet translatable, you should update all the strings manually to be sure, there are no errors, but for minimizing the effort you can use some automation like:
sed -r -i -e "s/([^\"]+)(\"[^[:lower:][:punct:][:space:]]+[^\"]*[[:alpha:]]+[^\"]*\")/\1_(\2)/g" CopyOfYourScreenlet.py
sed -r -i -e "s/([^']+)('[^[:lower:][:punct:][:space:]]+[^']*[[:alpha:]]+[^']*')/\1_(\2)/g" CopyOfYourScreenlet.py
If you run the both sed one liners above on a copy of your Screenlet and then use some utility (meld, kdiff) to merge the automatically generated changes, then you should get things done pretty easily. If you know a better way to automate the process, please let us know.
How to update translations in PPA
Translations are generated with running the ./potfiles.sh script from the project root directory (usually it works fine). It needs gettext, pygettext and diff utilities to be installed. You update source tree from Bazaar, run ./potfiles.sh and commit the changes to Bazaar again. This should update/generate the translation templates.
Potfiles.sh should create or update all the translation files according to gettext decorations found in all python files in all the individual screenlets source directories. Gettext translation templates (POT files) are generated to /src/WhateverScreenlet/po/whatever-screenlet.pot and when the file is commited/pushed into Launchpad project, Launchpad uses its Rosetta translations system to import and manage translations from Launchpad web interface.
When importing new POT file into Launchpad, usually it takes something from 15 minutes to couple of hours to generate translation pages for the new POT. If you update a POT file and commit/push it, this should not usually take more than 15 min to be imported into Rosetta. See also General documentation for translations on Launchpad.
When POT file is imported, then you should preferably use Launchpad interface to deal with translations. You have option to download translation catalog (PO file) from Launchpad and edit the file and upload it again to Launchpad. You have also an option to edit translation strings directly from the web interface. This is a matter of preference, you can use both (but to avoid conflicts, not at the same time, using both at the same time will give you warnings from Launchpad).
Additionally, you can edit translation files also from the source tree of Bazaaar indiv-screenlets project, but then you have to pay attention not overwriting some other peoples changes who may be simultanously using the web interface. Editing directly from Bazaar branch may not give warnings, files are just overwritten.
If you want to update the translations in individual DEB packages in PPA (like foobar-screenlet, notice that translations in screelets-pack-all etc are updated without that), you also have to increase the version number of the screenlet. This is usually found in the screenlet main class header under section default meta-info for Screenlets. You have to change string described by variable __version__. Just increase the last part of version number, for example 0.4.6 -> 0.4.7 etc.
This also applies for updating any code in individual DEB packages of screenlets.
Before commiting anything to Bazaar (or before making changes) it is good practise to pull in latest changes (bzr pull in source directory). This way you avoid conflicts and unnecessary merges with other developers changes.
There are separate locale directories for Screenlet packs and individual debianized Screenlets. Screenlets in Screenlet packs use *.mo files from mo/ directory from Screenlet base directory, debianized Screenlets use /usr/share/locale/. This is needed to make possible to install screenlet packs and individual Screenlets independently and without conflics. However translation files from system *.mo directory are looked for first.
Help to develop Screenlets
Everybody is invited to apply patches to individual Screenlets or even the Screenlets Core Framework. If you want to do it, you have to join developer team for either Screenlets core or individual Screenlets on Launchpad.
Where/how can I get the source code?
The screenlets' sourcecode as well as individual screenlets packages are hosted on Launchpad. Here are the code branches for browsing:
You can easily download (and start to develop) the latest development branch using these commands (you need to install Bazaar VCS first):
bzr branch lp:screenlets
or
bzr branch lp:indiv-screenlets
You are welcome to submit your own Screenlet to one of official Screenlets packages. See Publishing_on_PPA for details.
Modifying the features
If you want to help but you dont have ideas you can check out a bit outdated TODO list here (Click on the TODO file). It might give some ideas. You may also check out the buglists and blueprints at Launchpad pages. But do not implement anything that will render present screenlets unusable!
Submitting the code
After you have made your changes to the code and tested they work, first pull the latest version to avoid unnecessary splitting of the branches:
bzr pull lp:indiv-screenlets
Review the changes you made and make sure everything is correct:
bzr diff
Commit the changes (provide a meaningful commit message):
bzr commit
Push the changes to the repository:
bzr push lp:indiv-screenlets
You can find more on using bzr here.
Why should I add my Screenlet to individual Screenlets project
There are some simple reasons for adding your Screenlet to the project:
- Users can install your Screenlet more easily (especially on Debian/Ubuntu). If user has enabled Screenlets PPA, you can conveniently provide install link as an aptURL. Screenlets as Debian packages also handle automatically the dependencies. You can see gCal@Gnome-look.org for an example.
- Users of the Screenlet (and you yourself, of course) can translate the Screenlet on Launchpad. Translations are automatically packaged into the Debian/Ubuntu version of the Screenlet and automatically published on Screenlets PPA.
- Screenlets from individual Screenlets project are chosen for the official package. If your Screenlet is for general use and polished enough then adding it to the project is the first step toward including it in the official Screenlets package.
- Of course your code will be versioned on Bazaar version control system at Launchpad and nothing can get lost, as well you will have a handy bugtracker, blueprints etc, and be real member of Screenlets developers community.
All you have to do for that is to register on Launchpad (if not already registered) and gain some knowledge of how to use Bazaar version control (which is no big deal, really).
Some information about Screenlets
Builtin controllers
Screenlets can be controlled using builtin variables , for example , if you want to set the scale of the screenlet just do self.scale = 1,2 , or move it self.x = 10 , this will put the screenlet in x = 10 , this also aplys for scale , width , height etc , you can find all this in the api.
Drawing
Screenlets are usualy drawn using cairo , but you can also use gtk to add gtk widgets to it , however gtk widgets wont resize the way cairo does , so you need to resize the gtk widgets manually , this may be done using a gtk.Box and setting a border width , then in on_scale event set the border you want
Theming
Screenlets themes are very costumizable , you can choose to develop a screenlet themes in 3 diferent ways. 1 - Using the builtin theme controls , using funtions like draw_rectangle , draw_triangle , draw_shadow , draw_line , draw_circle and others. 2 - You can use custom images to generate your themes. 3 - You can use both the above
You can also develop themes in a static way , leaving the buttons , text and other stuff also static , or you can change its position , size etc using a theme override .conf file.
You can take a look at the example screenlet for basic theming , and also the radio screenlet (since 0.1.2) that takes the theming to the max.
Is it easy to create screenlets?
Screenlets api is relativly easy , but the easiest way to create a screenlet is to edit a screenlet that is already made , for that you can use the example screenlet , included in the core package , or any other screenlet , because Screenlets is an open source project
Getting Started
File structure
Your screenlet folder should look like this
Clock/ClockScreenlet.py Clock/icon.png or icon.svg Clock/themes/defaut/ (containing the default skin)
Sarting the python file
First we do the basic imports
import screenlets from screenlets.options import StringOption , BoolOption , IntOption , FileOption , DirectoryOption , ListOption , AccountOption , TimeOption , FontOption, ColorOption , ImageOption
We import some more stuff
import gobject import gtk
Lets start the base class
If we want to make a Screenlet translatable with gettext, we put this right before the baseclass.
# use gettext for translation import gettext _ = screenlets.utils.get_translator(__file__) def tdoc(obj): obj.__doc__ = _(obj.__doc__) return obj @tdoc
Then we declare the baseclass itself.
class ExampleScreenlet (screenlets.Screenlet): """A simple example of how to create a Screenlet"""
Lets set some global vars
__name__ = 'ExampleScreenlet' #Name of the screenlet followed by "Screenlet"
__version__ = '0.1.0'
__author__ = 'My name here' #Preferred format: Name Familyname <mail@net.net>
__requires__ = [] #You can add Debian control file style dependencies
# e.g. ['python-gdata (>= 2.0.10)']
#Logical operators not yet supported though
__category__ = 'Foo' #Categories will be harvested by a script
#Choose an existing category if you don't want to
#end up under Misc (Category req: min 3 screenlets)
__desc__ = __doc__ #Set description to docstring of class
#Dont write directly here, use the class docstring!
Lets set some other global vars that we need
test_text = 'Hi.. im a screenlet'
demo_text = ''
demo_number = ''
int_example = 1
bool_example = True
time_example = (7, 30, 0)
account_example = ('','')
color_example =(0.0, 0.0, 0.0, 1)
font_example = "Sans Medium 5"
image_example = ''
file_example = ''
directory_example = ''
list_example = ('','')
hover = False
Call super
def __init__ (self, **keyword_args): screenlets.Screenlet.__init__(self, width=200, height=200, uses_theme=False, **keyword_args)
Set the default theme
self.theme_name = "default"
Add menu items or configurable settings
self.add_options_group('Example', 'This is an example of ' +\
' editable options within an OptionGroup ...')
# add editable option to the group
self.add_option(StringOption('Example','test_text', # attribute-name
self.test_text, # default-value
'Test-Text', # widget-label
'The Test-Text option for this Screenlet ...' # description
))
self.add_option(BoolOption('Example','bool_example',
self.bool_example, 'Option group bool',
'Example options group using bool'))
self.add_option(TimeOption('Example','time_example', self.time_example,
'Option group time', 'Example options group using time'))
self.add_option(IntOption('Example','int_example',
self.int_example, 'Option group integer',
'Example options group using integer',
min=0, max=5000))
self.add_option(FontOption('Example','font_example',
self.font_example, 'Option group font',
'Example options group using font'))
self.add_option(ColorOption('Example','color_example',
self.color_example, 'Option group color',
'Example options group using color'))
self.add_option(AccountOption('Example','account_example',self.account_example,
'Option group account','Using keyring encryption'))
self.add_option(ImageOption('Example','image_example', self.image_example,
'Option group Image', 'Example options group using Image'))
self.add_option(FileOption('Example','file_example', self.file_example,
'Option group file', 'Example options group using file'))
self.add_option(DirectoryOption('Example','directory_example', self.directory_example,
'Option group directory', 'Example options group using directory'))
self.add_option(ListOption('Example','list_example', self.list_example,
'Option group list', 'Example options group using list'))
The event handlers
def on_after_set_atribute(self,name, value):
"""Called after setting screenlet atributes"""
print name + ' is going to change from ' + str(value)
pass
def on_before_set_atribute(self,name, value):
"""Called before setting screenlet atributes"""
print name + ' has changed to ' + str(value)
pass
def on_create_drag_icon (self):
"""Called when the screenlet's drag-icon is created. You can supply
your own icon and mask by returning them as a 2-tuple."""
return (None, None)
def on_composite_changed(self):
"""Called when composite state has changed"""
pass
def on_drag_begin (self, drag_context):
"""Called when the Screenlet gets dragged."""
pass
def on_drag_enter (self, drag_context, x, y, timestamp):
"""Called when something gets dragged into the Screenlets area."""
pass
def on_drag_leave (self, drag_context, timestamp):
"""Called when something gets dragged out of the Screenlets area."""
pass
def on_drop (self, x, y, sel_data, timestamp):
"""Called when a selection is dropped on this Screenlet."""
return False
def on_focus (self, event):
"""Called when the Screenlet's window receives focus."""
pass
def on_hide (self):
"""Called when the Screenlet gets hidden."""
pass
def on_init (self):
"""Called when the Screenlet's options have been applied and the
screenlet finished its initialization. If you want to have your
Screenlet do things on startup you should use this handler."""
print 'i just got started'
# add menu items from xml file
self.add_default_menuitems(DefaultMenuItem.XML)
# add menu item
self.add_menuitem("at_runtime", "A")
# add default menu items
self.add_default_menuitems()
def on_key_down(self, keycode, keyvalue, event):
"""Called when a keypress-event occured in Screenlet's window."""
key = gtk.gdk.keyval_name(event.keyval)
if key == "Return" or key == "Tab":
screenlets.show_message(self, 'This is the ' + self.__name__ +'\n' + 'It is installed in ' + self.__path__)
def on_load_theme (self):
"""Called when the theme is reloaded (after loading, before redraw)."""
pass
def on_menuitem_select (self, id):
"""Called when a menuitem is selected."""
if id == "at_runtime":
screenlets.show_message(self, 'This is an example on a menu created at runtime')
if id == "at_xml":
screenlets.show_message(self, 'This is an example on a menu created in the menu.xml')
pass
def on_mouse_down (self, event):
"""Called when a buttonpress-event occured in Screenlet's window.
Returning True causes the event to be not further propagated."""
return False
def on_mouse_enter (self, event):
"""Called when the mouse enters the Screenlet's window."""
self.theme.show_tooltip("this is a tooltip , it is set to shows on mouse hover",self.x+self.mousex,self.y+self.mousey)
self.hover = True
print 'mouse is over me'
def on_mouse_leave (self, event):
"""Called when the mouse leaves the Screenlet's window."""
self.theme.hide_tooltip()
self.hover = False
print 'mouse leave'
def on_mouse_move(self, event):
"""Called when the mouse moves in the Screenlet's window."""
self.redraw_canvas()
pass
def on_mouse_up (self, event):
"""Called when a buttonrelease-event occured in Screenlet's window.
Returning True causes the event to be not further propagated."""
return False
def on_quit (self):
"""Callback for handling destroy-event. Perform your cleanup here!"""
screenlets.show_question(self, 'Do you like screenlets?')
return True
def on_realize (self):
""""Callback for handling the realize-event."""
def on_scale (self):
"""Called when Screenlet.scale is changed."""
pass
def on_scroll_up (self):
"""Called when mousewheel is scrolled up (button4)."""
pass
def on_scroll_down (self):
"""Called when mousewheel is scrolled down (button5)."""
pass
def on_show (self):
"""Called when the Screenlet gets shown after being hidden."""
pass
def on_switch_widget_state (self, state):
"""Called when the Screenlet enters/leaves "Widget"-state."""
pass
def on_unfocus (self, event):
"""Called when the Screenlet's window loses focus."""
pass
def on_draw (self, ctx):
"""In here we load the theme"""
# if theme is loaded
if self.theme:
# set scale rel. to scale-attribute
ctx.scale(self.scale, self.scale)
ctx.set_source_rgba(self.color_example[2], self.color_example[1], self.color_example[0],0.4)
if self.hover:
self.theme.draw_rounded_rectangle(ctx,0,0,20,self.width,self.height)
self.draw_circle(ctx,0,0,self.width,self.height)
# TEST: render example-bg into context (either PNG or SVG)
self.theme.render(ctx, 'example-bg')
ctx.set_source_rgba( self.color_example[0], self.color_example[1], self.color_example[2],self.color_example[3])
self.draw_text(ctx, self.test_text, 0, 0, self.font_example , 10,self.width,pango.ALIGN_LEFT)
self.draw_line(ctx,0,40,self.width,0,1)
self.draw_text(ctx, 'timer - ' + str(self.number), 0, 130, self.font_example , 10, self.width,pango.ALIGN_LEFT)
self.draw_text(ctx, self.theme_name, 0, 50, self.font_example , 10, self.width,pango.ALIGN_LEFT)
self.draw_text(ctx, 'mouse x ' + str(self.mousex ) + ' \n mouse y ' + str(self.mousey ) , 0, 170, self.font_example , 10,self.width,pango.ALIGN_LEFT)
# render svg-file
#self.theme['example-bg.svg'].render_cairo(ctx)
# render png-file
#ctx.set_source_surface(self.theme['example-test.png'], 0, 0)
#ctx.paint()
def on_draw_shape (self, ctx):
self.on_draw(ctx)
Lets add the screenlet to session
if __name__ == "__main__": # create new session import screenlets.session screenlets.session.create_session(ExampleScreenlet)
Builtin drawing funtion
ctx.scale(self.scale, self.scale)
ctx.set_source_rgba(self.color_example[2], self.color_example[1], self.color_example[0],0.4)
if self.hover:
self.draw_rounded_rectangle(ctx,0,0,20,self.width,self.height)
self.draw_circle(ctx,0,0,self.width,self.height)
# TEST: render example-bg into context (either PNG or SVG)
self.theme.render(ctx, 'example-bg')
ctx.set_source_rgba( self.color_example[0], self.color_example[1], self.color_example[2],self.color_example[3])
self.draw_text(ctx, self.test_text, 0, 0, self.font_example , 10,self.width,pango.ALIGN_LEFT)
self.draw_line(ctx,0,40,self.width,0,1)
self.draw_text(ctx, 'timer - ' + str(self.number), 0, 130, self.font_example , 10, self.width,pango.ALIGN_LEFT)
self.draw_text(ctx, self.theme_name, 0, 50, self.font_example , 10, self.width,pango.ALIGN_LEFT)
self.draw_text(ctx, 'mouse x ' + str(self.mousex ) + ' \n mouse y ' + str(self.mousey ) , 0, 170, self.font_example , 10,self.width,pango.ALIGN_LEFT)
# render svg-file
#self.theme['example-bg.svg'].render_cairo(ctx)
# render png-file
#ctx.set_source_surface(self.theme['example-test.png'], 0, 0)
#ctx.paint()
So where do I start?
I suggest you get the template screenlet and start coding from there (this template is a bit outdated).
Coding Rules
- A Screenlet's classname must end on "Screenlet" (e.g. ClockScreenlet)
- Not more than 80 chars per line (where possible).
- Tabs are 4 char-wide "\t"-characters.
- Classes MUST have a documentation-string.
- After class-headers's documentation, one separating line.
- Internal attributes MUST start with TWO leading underscores.
- Editable options NEVER have leading underscores.
- All functions that are no inherited event-handlers MUST have a documentation-string.
- Constructors of Screenlet-subclasses must implement the **keyword_args parameter as last argument to their __init__-function.
- All screenlet-files MUST have the name of the Screenlet-classes they contain (with a .py-extension).
- All Screenlets MUST have a head-comment containing license/author note,
- Screenlets need to be placed into a directory named like the Screenlet's class (without trailing "Screenlet"). This directory may contain a "themes"-directory where the Screenlet's themes are stored. It may also contain other files (of course).
- Screenlets should supply an icon named "icon.svg" or "icon.png" within their directory.
- When possible set the width and height of the screenlet matching the background theme image , to avoid confusion
- Always call super before anything else ... of course
- Never call exit() or sys.exit().But if you need to check the utils.is_manager_running_me() and only call the exit() if that returns false
Avoiding memory leaks
Old versions of screenlets suffered from memory leaks expecilly in the way they draw text , to avoid this use the built in draw_text function. Also remember to clean all your variables to avoid memory leaks
Packaging a screenlet
Finding a host for your package
The first and easiest way is to post your screenlet on www.gnome-look.org under the Screenlets section, Gnome-Look provides you to upload your content there, so it will remain in their servers forever, and your screenlet will be safe and backed up. If you want you can post it on www.screenlets.org after that and provide the download link from Gnome-Look. The best way is to put your code into individual screenlets project and host the packages in Screenlets PPA (see below).
Zipped package
Use the screenlets-packager tool to package your screenlet for release (e.g.):
screenlets-packager ~/.screenlets/Weather
In order to automatically include translations under po directory (see individual Screenlets project for examples) gettext package has to be installed.
Debian package
Use the screenlets-debianizer tool to package your screenlet for release (e.g.):
screenlets-debianizer ~/.screenlets/Weather
Notice that you need to have installed all kinds of build interface packages (something like that: 'build-essential automake make checkinstall dpatch patchutils autotools-dev debhelper devscripts quilt fakeroot xutils lintian cmake dh-make libtool autoconf git-core subversion bzr gnupg pbuilder'). If you don't know how to deal with it, you probably shouldn't be messing around with Debian packages.
Publishing on PPA
If your screenlet has been tested by users on Gnome-Look or by some other means for some reasonable amount of time, you can add it to Screenlets PPA. First join the Individual Screenlets developers team on Launchpad and read carfully the guidelines on team page. If you have registered yourself all right, then just add your Screenlet to src/ directory among the others. You have to have basic knowledge of how to use Bazaar version control system to do that.
Your added Screenlet will be published automatically on the PPA and after some intervals new Screenlets will be examined and best of them added to official package to be distributed with Screenlets Core. If no errors on package generation occur, the packages should be generated every 24h and uploaded to Launchpad Screenlets developers PPA. By now there is no way to debug the generation process directly.
However, you can test the correctness of your screenlet for generating Debian package with the screenlets-debianizer. You can also see the python source of screenlets-debianzer, if you want to adapt something or see more debug information. If screenlets-debianizer succeeds, it is highly probable that also automatic generation for Launchpad succeeds and packages will be on PPA in 24h. Since the server runs in text mode, the screenlet class must be importable without initializing GTK or X graphics (usually it is, but if there are problems, this is a good thing to look for). The same goes for some exotic modules, which need to be imported for the screenlet.
Notice that any changes to individual screenlet Debian package (whatever-screenlet Debian package) will be uploaded to Launchpad PPA only when version number of Screenlet is increased. However, screenlet packs (screenlet-pack-all, screenlet-pack-basic etc) are updated from the latest snapshot of source tree, so for upating screenlet packs increasing the version number is not needed.
See also the list of reasons why you should add your Screenlet to the project.



