Entries tagged “python”

Autostart/Autorun with Python

Some time ago I wrote a little python module to automatically start programs when a user logs in. It works on Windows and Linux. On windows it creates registry entries, on linux it creates *.desktop files as defined in the Desktop Application Autostart Specification. You can use it like this:
import os
import autorun
autorun.add("myapp", os.path.abspath(__file__))
So here's the code:
#!/usr/bin/env python
"""A simple crossplatform autostart helper"""
from __future__ import with_statement

import os
import sys

if sys.platform == 'win32':
    import _winreg
    _registry = _winreg.ConnectRegistry(None, _winreg.HKEY_CURRENT_USER)
    def get_runonce():
        return _winreg.OpenKey(_registry,
                r"Software\Microsoft\Windows\CurrentVersion\Run", 0,
        _winreg.KEY_ALL_ACCESS)

    def add(name, application):
        """add a new autostart entry"""
        key = get_runonce()
        _winreg.SetValueEx(key, name, 0, _winreg.REG_SZ, application)
        _winreg.CloseKey(key)

    def exists(name):
        """check if an autostart entry exists"""
        key = get_runonce()
        exists = True
        try:
            _winreg.QueryValueEx(key, name)
        except WindowsError:
            exists = False
        _winreg.CloseKey(key)
        return exists

    def remove(name):
        """delete an autostart entry"""
        key = get_runonce()
        _winreg.DeleteValue(key, name)
        _winreg.CloseKey(key)
else:
    _xdg_config_home = os.environ.get("XDG_CONFIG_HOME", "~/.config")
    _xdg_user_autostart = os.path.join(os.path.expanduser(_xdg_config_home),
            "autostart")

    def getfilename(name):
        """get the filename of an autostart (.desktop) file"""
        return os.path.join(_xdg_user_autostart, name + ".desktop")

    def add(name, application):
        """add a new autostart entry"""
        desktop_entry = "[Desktop Entry]\n"\
            "Name=%s\n"\
            "Exec=%s\n"\
            "Type=Application\n"\
            "Terminal=false\n" % (name, application)
        with open(getfilename(name), "w") as f:
            f.write(desktop_entry)

    def exists(name):
        """check if an autostart entry exists"""
        return os.path.exists(getfilename(name))

    def remove(name):
        """delete an autostart entry"""
        os.unlink(getfilename(name))
def test():
    assert not exists("test_xxx")
    try:
        add("test_xxx", "test")
        assert exists("test_xxx")
    finally:
        remove("test_xxx")
    assert not exists("test_xxx")

if __name__ == "__main__":
    test()
I hope that helps somebody. :)

Generating Maps/Mazes with Python

Last weekend I wrote some code to generate random mazes in python. The algorithm creates fully connected mazes using backtracking. So between two points in the maze there is always exactly one path. In a game you would probably want to remove some more walls to create loops.

#!/usr/bin/env python
import sys
from random import shuffle

def shuffled(x):
    y = list(x)
    shuffle(y)
    return y

DIRECTIONS = (
    (0, -1),
    (0, 1),
    (1, 0),
    (-1, 0),
)

def make_maze(width, height, cellsize):
    cellsize1 = cellsize+1 # cellsize including one wall
    field_width = width*cellsize1+1
    field_height = height*cellsize1+1
    field = [0]*(field_width*field_height)
    stack = [(0, 0, shuffled(DIRECTIONS))]
    while stack:
        x, y, directions = stack[-1]
        dx, dy = directions.pop()
        # no other ways to go from here
        if not directions:
            stack.pop()
        # new cell
        nx = x+dx
        ny = y+dy
        # out of bounds
        if not (0 <= nx < width and 0 <= ny < height):
            continue
        # index of new cell in field
        fx = 1+nx*cellsize1
        fy = 1+ny*cellsize1
        fi = fx+fy*field_width
        # already visited
        if field[fi]:
            continue
        # tear down walls
        if dx > 0:
            a = -1
            b = field_width
        elif dx < 0:
            a = cellsize
            b = field_width
        elif dy > 0:
            a = -field_width
            b = 1
        else:
            a = cellsize*field_width
            b = 1
        for offset in xrange(cellsize):
            field[fi+a+b*offset] = 1
        # clear cell
        for y in xrange(0, cellsize):
            for x in xrange(0, cellsize):
                field[fi+x+y*field_width] = 1
        # visit cell
        stack.append([nx, ny, shuffled(DIRECTIONS)])
    return field

if __name__ == "__main__":
    if len(sys.argv) != 4:
        raise SystemExit("Usage: %s width height cellsize" % sys.argv[0])
    width, height, cellsize = map(int, sys.argv[1:])
    fields = make_maze(width, height, cellsize)
    w = (cellsize+1)*width+1
    h = (cellsize+1)*height+1
    for y in xrange(h):
        print "".join(map(lambda x: x and " " or "#", fields[y*w:y*w+w]))

And here some examples of the output:

#############################################################
#     #           #              #        #           #     #
#     #           #              #        #           #     #
#  ####  ####  #  #  ##########  #  #  #  #  #######  #  ####
#     #  #     #     #           #  #  #     #     #  #     #
#     #  #     #     #           #  #  #     #     #  #     #
####  #  #  ##########  #############  #######  #  #  ####  #
#     #  #  #  #     #     #           #     #  #  #        #
#     #  #  #  #     #     #           #     #  #  #        #
#  #######  #  #  #  ####  #######  ####  ####  #  #######  #
#           #     #  #     #        #  #     #  #        #  #
#           #     #  #     #        #  #     #  #        #  #
#############  ####  #  ####  #######  #  #  #  #  #######  #
#                 #           #           #     #           #
#                 #           #           #     #           #
#############################################################
################
#           #  #
#           #  #
#  #  #  #  #  #
#  #  #  #  #  #
#  #  #  #  #  #
#  ####  #  #  #
#        #     #
#        #     #
#############  #
#     #        #
#     #        #
#  ####  #######
#              #
#              #
################

Playitslowly 1.2 released

I finally found time to update playitslowly and make a new release.

Changes

  • Compatibility with python 2.6
  • New keyboard shortcuts
  • Back button
  • Improved setup routine
  • Added file command line argument

Compatibility with python 2.6 means that this release should work with Ubuntu 9.04 etc.

screenshot

Get it Now

Concatenating images using python

To generate sprites for a game I'm working on I needed to concatenate images side by side. I wrote a little python script to do the job. It requires the PIL.

from PIL import Image
import sys

if not len(sys.argv) > 3:
    raise SystemExit("Usage: %s src1 [src2] .. dest" % sys.argv[0])

images = map(Image.open, sys.argv[1:-1])
w = sum(i.size[0] for i in images)
mh = max(i.size[1] for i in images)

result = Image.new("RGBA", (w, mh))

x = 0
for i in images:
    result.paste(i, (x, 0))
    x += i.size[0]

result.save(sys.argv[-1])

Getting the number of workdays in a given timeframe

This is a weird function I wrote once to get the number of workdays (all days except saturday and sunday). Now the objective here was to do it in O(1) and without the use of any flow control. So I came up with this:

def get_work_days(startday, days):
    freq = 7
    startday = startday+1%freq
    sundayPhase = (freq - startday) % freq
    saturdayPhase = (sundayPhase + 6) % freq
    remaining = days % freq
    hasSunday = 0**((sundayPhase+1)/(remaining+1))
    hasSaturday = 0**((saturdayPhase+1)/(remaining+1))
    return (days - remaining) / freq * 5 + remaining - hasSunday - hasSaturday

It's abusing the fact that in most programming languages 0^0 is defined to be 1.

Atomic get and increment in python

For generating continuous unique id's in python I needed a thread safe way to do this:
x = counter
counter += 1
When disassembling this code we will get this:
  2           0 LOAD_FAST                0 (counter)
              3 STORE_FAST               1 (x)

  3           6 LOAD_FAST                0 (counter)
              9 LOAD_CONST               1 (1)
             12 INPLACE_ADD         
             13 STORE_FAST               0 (counter)
As you can see not even counter += 1 is atomic. Now the obvious solution would be to use a lock. The not so obvious solution is to use a itertools.counter(). The counter is implemented in C and doesn't release the GIL so it is atomic. The code would the look like this:
x = counter.next()
which is more pretty anyway.

PyGST messing with sys.argv

While developing play it slowly I noticed some strange behavior of the python gstreamer bindings. For some reason pygst parses argv on import. This illustrates the problem:
[veers@castle ~]$ python
Python 2.6.1 (r261:67515, Dec  7 2008, 08:27:41) 
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys.argv
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named argv
>>> import sys
>>> sys.argv.append("--help")
>>> import gst
Usage:
  . [OPTION...] - GStreamer initialization

Help Options:
  -?, --help                        Show help options
  --help-all                        Show all help options
  --help-gst                        Show GStreamer Options
That's not nice of you pygst! But there's a simple workaround:
argv = sys.argv
sys.argv = []
import gst
sys.argv = argv
I guess I should write a patch instead of a post but.. may be later.

Play it slowly 1.1

And there it is, a new version of play it slowly. It fixes all known bugs and even adds some features

  • Saving of state (file, speed, ..)
  • Improved error handling
  • Pitch scale is using semitones
  • Speed scale allows steps of 0.05
  • New command line argument --sink
  • Fixes all known bugs

If you've got suggestions please write a comment

screenshot

Get it Now

PyGTK Exception Hook

screenshot

I wrote a little exception hook for pygtk applications.

import gtk, gobject
import sys

_ = lambda s: s

def scrolled(widget, shadow=gtk.SHADOW_NONE):
    window = gtk.ScrolledWindow()
    window.set_shadow_type(shadow)
    window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    if widget.set_scroll_adjustments(window.get_hadjustment(),
                                      window.get_vadjustment()):
        window.add(widget)
    else:
        window.add_with_viewport(widget)
    return window


class ExceptionDialog(gtk.MessageDialog):
    def __init__(self, etype, evalue, etb):
        gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_CLOSE, type=gtk.MESSAGE_ERROR)
        self.set_resizable(True)
        self.set_markup(_("An error has occured:\n%r\nYou should save "
                "your work and restart the application. If the error "
                "occurs again please report it to the developer." % evalue))
        import cgitb
        text = cgitb.text((etype, evalue, etb), 5)
        expander = gtk.Expander(_("Exception Details"))
        self.vbox.pack_start(expander)
        textview = gtk.TextView()
        textview.get_buffer().set_text(text)
        expander.add(scrolled(textview))
        self.show_all()

def install_exception_hook(dialog=ExceptionDialog):
    old_hook = sys.excepthook
    def new_hook(etype, evalue, etb):
        if etype not in (KeyboardInterrupt, SystemExit):
            d = dialog(etype, evalue, etb)
            d.run()
            d.destroy()
        old_hook(etype, evalue, etb)
    new_hook.old_hook = old_hook
    sys.excepthook = new_hook

if __name__ == "__main__":
    install_exception_hook()
    gobject.idle_add(lambda: 1/0)
    gobject.idle_add(gtk.main_quit)
    gtk.main()

Stepped scales with pygtk

The Scale Widgets that come with gtk ignore step_incr from the adjustment when the user drags the slider. So there is no way to specify that you only want to use increments of 0.5 or 1.0. So here's how to do it:

class Scale(object):
    """A scale that adheres to increment steps"""
    def __init__(self):
        self.connect("change-value", self.adjust)

    def adjust(self, range, scroll, value):
        adj = self.get_adjustment()
        lower = adj.get_property('lower')
        upper = adj.get_property('upper')
        incr = adj.get_property('step-increment')
        value -= (value % incr)
        self.set_value(min(max(lower, value), upper))
        return True


class VScale(gtk.VScale, Scale):
    def __init__(self, *args):
        gtk.VScale.__init__(self, *args)
        Scale.__init__(self)

class HScale(gtk.HScale, Scale):
    def __init__(self, *args):
        gtk.HScale.__init__(self, *args)
        Scale.__init__(self)